hdoc-tools 0.17.26 → 0.17.27

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.js CHANGED
@@ -1,1090 +1,1480 @@
1
+ const { extension } = require("mime-types");
2
+
1
3
  (function () {
2
- 'use strict';
3
-
4
- const crypto = require('crypto'),
5
- dree = require('dree'),
6
- fs = require('fs-extra'),
7
- mdfm = require('markdown-it-front-matter'),
8
- path = require('path'),
9
- puppeteer = require('puppeteer'),
10
- URL = require("url").URL,
11
- hdoc_validate = require(path.join(__dirname, 'hdoc-validate.js')),
12
- hdoc = require(path.join(__dirname, 'hdoc-module.js')),
13
- hdoc_build_db = require(path.join(__dirname, 'hdoc-build-db.js')),
14
- hdoc_build_pdf = require(path.join(__dirname, 'hdoc-build-pdf.js')),
15
- hdoc_index = require(path.join(__dirname, 'hdoc-db.js')),
16
- archiver = require('archiver'),
17
- xmlFormat = require('xml-formatter');
18
-
19
- const h_tags_to_search = ['h1', 'h2', 'h3'],
20
- doc_header_template_path = path.join(__dirname, 'templates', 'doc-header.html'),
21
- non_git_doc_header_template_path = path.join(__dirname, 'templates', 'doc-header-non-git.html'),
22
- pdf_header_template_path = path.join(__dirname, 'templates', 'pdf-header.html'),
23
- non_git_pdf_header_template_path = path.join(__dirname, 'templates', 'pdf-header-non-git.html'),
24
- pdf_template_path = path.join(__dirname, 'templates', 'pdf'),
25
- pdf_template_file_path = path.join(pdf_template_path, 'template.html'),
26
- regex_version = /^[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,6}$/,
27
- h1_pattern = /(<h1.*?>)\s*.*\s*(.*<\/h1>)/;
28
-
29
- let bc = {}, // Breadcrumbs map
30
- built_file_hashes = [],
31
- book_read_time = 0,
32
- browser = {},
33
- conversion_attempted = 0,
34
- conversion_success = 0,
35
- conversion_failed = 0,
36
- css_templates = [],
37
- doc_header_template = '',
38
- doc_header_template_non_git = '',
39
- global_source_path = '',
40
- pdf_created = 0,
41
- pdf_enable = false,
42
- pdf_header_template = '',
43
- pdf_header_template_non_git = '',
44
- pdf_template = '',
45
- prod_families = {},
46
- prods_supported = [],
47
- doc_id = '',
48
- git_token = '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', // Github fine-grained personal access token that has minimum read-only access to Hornbill Docs metadata
49
- hdocbook_config = {},
50
- hdocbook_project,
51
- includes_found = 0,
52
- includes_success = 0,
53
- includes_failed = 0,
54
- index_records = [],
55
- md_files = [],
56
- md_files_delete = [],
57
- redirects = {},
58
- static_html_files = [],
59
- work_path_content = '',
60
- verbose = false;
61
-
62
-
63
- const pdf_path_excluded = function (relative_path) {
64
- if (!hdocbook_project.pdfGeneration || hdocbook_project.pdfGeneration.exclude_paths === undefined) {
65
- return false;
66
- }
67
- if (relative_path.startsWith('/')) {
68
- relative_path = relative_path.slice(1, relative_path.length);
4
+ "use strict";
5
+
6
+ const crypto = require("crypto"),
7
+ dree = require("dree"),
8
+ fs = require("fs-extra"),
9
+ mdfm = require("markdown-it-front-matter"),
10
+ path = require("path"),
11
+ puppeteer = require("puppeteer"),
12
+ URL = require("url").URL,
13
+ hdoc_validate = require(path.join(__dirname, "hdoc-validate.js")),
14
+ hdoc = require(path.join(__dirname, "hdoc-module.js")),
15
+ hdoc_build_db = require(path.join(__dirname, "hdoc-build-db.js")),
16
+ hdoc_build_pdf = require(path.join(__dirname, "hdoc-build-pdf.js")),
17
+ hdoc_index = require(path.join(__dirname, "hdoc-db.js")),
18
+ archiver = require("archiver"),
19
+ xmlFormat = require("xml-formatter");
20
+
21
+ const h_tags_to_search = ["h1", "h2", "h3"],
22
+ doc_header_template_path = path.join(
23
+ __dirname,
24
+ "templates",
25
+ "doc-header.html"
26
+ ),
27
+ non_git_doc_header_template_path = path.join(
28
+ __dirname,
29
+ "templates",
30
+ "doc-header-non-git.html"
31
+ ),
32
+ pdf_header_template_path = path.join(
33
+ __dirname,
34
+ "templates",
35
+ "pdf-header.html"
36
+ ),
37
+ non_git_pdf_header_template_path = path.join(
38
+ __dirname,
39
+ "templates",
40
+ "pdf-header-non-git.html"
41
+ ),
42
+ pdf_template_path = path.join(__dirname, "templates", "pdf"),
43
+ pdf_template_file_path = path.join(pdf_template_path, "template.html"),
44
+ regex_version = /^[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,6}$/,
45
+ h1_pattern = /(<h1.*?>)\s*.*\s*(.*<\/h1>)/,
46
+ regex_filename = /^[a-z]+-{0,1}([-a-z0-9]+)*$/;
47
+
48
+ let bc = {}, // Breadcrumbs map
49
+ built_file_hashes = [],
50
+ book_read_time = 0,
51
+ browser = {},
52
+ conversion_attempted = 0,
53
+ conversion_success = 0,
54
+ conversion_failed = 0,
55
+ css_templates = [],
56
+ doc_header_template = "",
57
+ doc_header_template_non_git = "",
58
+ errors_filename = [],
59
+ global_source_path = "",
60
+ pdf_created = 0,
61
+ pdf_enable = false,
62
+ pdf_header_template = "",
63
+ pdf_header_template_non_git = "",
64
+ pdf_template = "",
65
+ prod_families = {},
66
+ prods_supported = [],
67
+ doc_id = "",
68
+ git_token =
69
+ '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', // Github fine-grained personal access token that has minimum read-only access to Hornbill Docs metadata
70
+ hdocbook_config = {},
71
+ hdocbook_project,
72
+ includes_found = 0,
73
+ includes_success = 0,
74
+ includes_failed = 0,
75
+ index_records = [],
76
+ md_files = [],
77
+ md_files_delete = [],
78
+ redirects = {},
79
+ static_html_files = [],
80
+ work_path_content = "",
81
+ verbose = false;
82
+
83
+ const pdf_path_excluded = function (relative_path) {
84
+ if (
85
+ !hdocbook_project.pdfGeneration ||
86
+ hdocbook_project.pdfGeneration.exclude_paths === undefined
87
+ ) {
88
+ return false;
89
+ }
90
+ if (relative_path.startsWith("/")) {
91
+ relative_path = relative_path.slice(1, relative_path.length);
92
+ }
93
+ for (
94
+ let i = 0;
95
+ i < hdocbook_project.pdfGeneration.exclude_paths.length;
96
+ i++
97
+ ) {
98
+ const exclude_path = hdocbook_project.pdfGeneration.exclude_paths[i];
99
+ if (relative_path === exclude_path) return true;
100
+ if (exclude_path.at(-1) === "*") {
101
+ if (relative_path.startsWith(exclude_path.slice(0, -1))) {
102
+ return true;
69
103
  }
70
- for (let i = 0; i < hdocbook_project.pdfGeneration.exclude_paths.length; i++) {
71
- const exclude_path = hdocbook_project.pdfGeneration.exclude_paths[i];
72
- if (relative_path === exclude_path) return true;
73
- if (exclude_path.at(-1) === '*') {
74
- if (relative_path.startsWith(exclude_path.slice(0, -1))) {
75
- return true;
76
- }
77
- }
104
+ }
105
+ }
106
+ return false;
107
+ };
108
+
109
+ const transform_static_html = async function (file_path) {
110
+ if (fs.existsSync(file_path.path)) {
111
+ // Load HTML file
112
+ let html_txt = fs.readFileSync(file_path.path, "utf8");
113
+ html_txt = html_txt.replace(/\r/gm, ""); // Remove CR's so we're just dealing with newlines
114
+
115
+ let fm_headers = [];
116
+ let existing_fm_headers = false;
117
+ let doc_type = "Article";
118
+ let doc_title = "";
119
+
120
+ // Check if we have a frontmatter comment
121
+ const fm_header = hdoc.getHTMLFrontmatterHeader(html_txt);
122
+ if (Object.keys(fm_header.fm_properties).length > 0) {
123
+ existing_fm_headers = true;
124
+
125
+ // We have some frontmatter headers, check if title is one of them
126
+ let fm_title_found = false;
127
+ if (
128
+ fm_header.fm_properties &&
129
+ fm_header.fm_properties.title !== undefined
130
+ ) {
131
+ // We have a title - but does the title have a value
132
+ if (fm_header.fm_properties.title === "") {
133
+ // No value - remove title from the properties map so we don't end up with 2 title properties, one empty and one with a value
134
+ delete fm_header.fm_properties.title;
135
+ } else {
136
+ // We have a value for the title property
137
+ fm_title_found = true;
138
+ doc_title = fm_header.fm_properties.title.trim();
139
+ }
78
140
  }
79
- return false;
80
- };
81
-
82
- const transform_static_html = async function (file_path) {
83
- if (fs.existsSync(file_path.path)) {
84
- // Load HTML file
85
- let html_txt = fs.readFileSync(file_path.path, 'utf8');
86
- html_txt = html_txt.replace(/\r/gm, ''); // Remove CR's so we're just dealing with newlines
87
-
88
- let fm_headers = [];
89
- let existing_fm_headers = false;
90
- let doc_type = 'Article';
91
- let doc_title = '',
92
- doc_description = '';
93
-
94
- // Check if we have a frontmatter comment
95
- const fm_header = hdoc.getHTMLFrontmatterHeader(html_txt);
96
- if (Object.keys(fm_header.fm_properties).length > 0) {
97
- existing_fm_headers = true;
98
-
99
- // We have some frontmatter headers, check if title is one of them
100
- let fm_title_found = false;
101
- if (fm_header.fm_properties && fm_header.fm_properties.title !== undefined) {
102
- // We have a title - but does the title have a value
103
- if (fm_header.fm_properties.title === '') {
104
- // No value - remove title from the properties map so we don't end up with 2 title properties, one empty and one with a value
105
- delete fm_header.fm_properties.title;
106
- } else {
107
- // We have a value for the title property
108
- fm_title_found = true;
109
- doc_title = fm_header.fm_properties.title.trim();
110
- }
111
- }
112
-
113
- // Is reading-time in the fm headers?
114
- if (fm_header.fm_properties['reading-time'] === undefined) {
115
- const read_time_mins = hdoc.get_html_read_time(html_txt);
116
- book_read_time += read_time_mins;
117
- fm_header.fm_properties['reading-time'] = read_time_mins;
118
- }
119
-
120
- for (const key in fm_header.fm_properties) {
121
- if (fm_header.fm_properties.hasOwnProperty(key)) {
122
- if (key === 'type') doc_type = fm_header.fm_properties[key];
123
- else {
124
- fm_headers.push({
125
- id: key,
126
- value: fm_header.fm_properties[key]
127
- });
128
- }
129
- }
130
- }
131
-
132
- if (!fm_title_found && file_path.name !== 'description_ext.md' && file_path.name !== 'article_ext.md') {
133
- // No frontmatter title found in properties
134
- // Go get title from h tags in html
135
- const html_heading = hdoc.getFirstHTMLHeading(html_txt, h_tags_to_search);
136
-
137
- if (html_heading && html_heading[0] && html_heading[0].children && html_heading[0].children[0] && html_heading[0].children[0].data) {
138
- // We've found a heading tag, add that as a title to the existing frontmatter properties
139
- fm_headers.push({
140
- id: 'title',
141
- value: html_heading[0].children[0].data
142
- });
143
- doc_title = html_heading[0].children[0].data;
144
- } else {
145
- // No header tag, no frontmatter title, output a warning
146
- console.log(`[WARNING] No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path.path}`);
147
- }
148
- }
149
-
150
- // Do we have a description header?
151
- if (fm_header.fm_properties && fm_header.fm_properties.description !== undefined) {
152
- if (fm_header.fm_properties.description === '') {
153
- const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ['p']);
154
- if (html_p_tag && html_p_tag[0] && html_p_tag[0].children && html_p_tag[0].children[0] && html_p_tag[0].children[0].data) {
155
- fm_headers.push({
156
- id: 'description',
157
- value: `${doc_title}: ${html_p_tag[0].children[0].data.split('.')[0] + '.'}`.trim()
158
- });
159
- }
160
- } else {
161
- fm_headers.push({
162
- id: 'description',
163
- value: fm_header.fm_properties.description.trim()
164
- });
165
- }
166
- } else {
167
- const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ['p']);
168
- if (html_p_tag && html_p_tag[0] && html_p_tag[0].children && html_p_tag[0].children[0] && html_p_tag[0].children[0].data) {
169
- fm_headers.push({
170
- id: 'description',
171
- value: `${doc_title}: ${html_p_tag[0].children[0].data.split('.')[0] + '.'}`.trim()
172
- });
173
- }
174
- }
175
- } else {
176
- // We have no frontmatter headers, get and build one from the html headings
177
- const html_heading = hdoc.getFirstHTMLHeading(html_txt, h_tags_to_search);
178
- let doc_title = '';
179
- // Add the title
180
- if (html_heading && html_heading[0] && html_heading[0].children && html_heading[0].children[0] && html_heading[0].children[0].data) {
181
- // We've found a heading tag, add that as a title to the frontmatter content
182
- fm_headers.push({
183
- id: 'title',
184
- value: html_heading[0].children[0].data
185
- });
186
- doc_title = html_heading[0].children[0].data;
187
- } else if (file_path.name !== 'description_ext.md' && file_path.name !== 'article_ext.md') {
188
- // No header tag, no frontmatter title, output a warning
189
- console.log(`[WARNING] No frontmatter title property, or ${h_tags_to_search.join(', ')} tags detected in ${file_path.path}`);
190
- }
191
-
192
- // Add the reading time
193
- const read_time_mins = hdoc.get_html_read_time(html_txt);
194
- book_read_time += read_time_mins;
195
- fm_headers.push({
196
- id: 'reading-time',
197
- value: read_time_mins
198
- });
199
-
200
- const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ['p']);
201
- if (html_p_tag && html_p_tag[0] && html_p_tag[0].children && html_p_tag[0].children[0] && html_p_tag[0].children[0].data) {
202
- fm_headers.push({
203
- id: 'description',
204
- value: `${doc_title}: ${html_p_tag[0].children[0].data.split('.')[0] + '.'}`.trim()
205
- });
206
- }
207
- }
208
-
209
- // Add doc type
210
- fm_headers.push({
211
- id: 'type',
212
- value: doc_type
213
- });
214
-
215
- let metadata = {};
216
-
217
- // Remove the first <h1>title</h1> from the HTML as we'll add that in the document header
218
- let html_h1 = h1_pattern.exec(html_txt);
219
- if (html_h1 && html_h1[0])
220
- html_h1 = html_h1[0].replace(/(<h1.*?>)/, '').replace(/(<\/h1>)/, '');
221
-
222
- html_txt = html_txt.replace(h1_pattern, '');
223
-
224
- // Get contributor data from Github, if exists
225
- let contribs = [];
226
- let last_commit = null;
227
- if (hdocbook_config.publicSource && hdocbook_config.publicSource !== '' && hdocbook_config.publicSource.includes('github.com/Hornbill-Docs')) {
228
-
229
- const github_paths = hdoc.get_github_api_path(hdocbook_config.publicSource, file_path.relativePath);
230
- const contributors = hdoc.get_github_contributors(github_paths.api_path, git_token);
231
-
232
- if (!contributors.success) {
233
- console.log(`Error retrieving contributors from Github: ${contributors.error}`);
234
- } else {
235
- last_commit = contributors.last_commit_date;
236
- metadata.last_commit = contributors.last_commit_date;
237
- metadata.contributor_count = contributors.contributor_count;
238
- metadata.edit_url = github_paths.edit_path;
239
- contribs = contributors.contributors;
240
- contributors.editPath = github_paths.edit_path;
241
- fm_headers.push({
242
- id: 'contributor-count',
243
- value: contributors.contributor_count
244
- });
245
- fm_headers.push({
246
- id: 'last-commit',
247
- value: contributors.last_commit_date
248
- });
249
- const target_file = file_path.path.replace(path.extname(file_path.path), '._info.json');
250
- delete contributors.success;
251
- delete contributors.error;
252
- contributors.editPath = github_paths.edit_path;
253
-
254
- }
255
- fm_headers.push({
256
- id: 'edit-path',
257
- value: github_paths.edit_path
258
- });
259
- }
260
-
261
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
262
- fm_headers.push({
263
- id: 'pdf-path',
264
- value: file_path.relativePath.replace(path.extname(file_path.relativePath), '.pdf')
265
- })
266
- }
267
-
268
- let fm_header_content = '<!--[[FRONTMATTER\n';
269
- if (fm_headers.length > 0) {
270
- for (let i = 0; i < fm_headers.length; i++) {
271
- fm_header_content += `${fm_headers[i].id}: ${fm_headers[i].value}\n`;
272
- }
273
- fm_header_content += ']]-->';
274
-
275
- if (existing_fm_headers) {
276
- html_txt = html_txt.replace('<!--' + fm_header.fm_header + '-->', '');
277
- }
278
- }
279
-
280
- let doc_header = '';
281
- let pdf_header = '';
282
- const inline_content = file_path.relativePath.startsWith(`${hdocbook_config.docId}/_inline/`);
283
- if (hdocbook_config.publicSource && hdocbook_config.publicSource.includes('github.com/Hornbill-Docs')) {
284
- // Build doc header from template and frontmatter tags
285
- if (!inline_content)
286
- doc_header = process_doc_header(fm_headers, file_path.relativePath, doc_header_template, html_h1);
287
-
288
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
289
- pdf_header = process_doc_header(fm_headers, file_path.relativePath, pdf_header_template, html_h1);
290
- } else {
291
- if (!inline_content)
292
- doc_header = process_doc_header(fm_headers, file_path.relativePath, doc_header_template_non_git, html_h1);
293
-
294
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
295
- pdf_header = process_doc_header(fm_headers, file_path.relativePath, pdf_header_template_non_git, html_h1);
296
- }
297
-
298
- let pdf_size = 0;
299
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
300
- let pdf_txt = await hdoc_build_pdf.process_images(file_path, html_txt);
301
- pdf_txt = `${pdf_header}\n${pdf_txt}`;
302
-
303
- // Generate PDF file from HTML
304
- const pdf_file_path = file_path.path.replace(path.extname(file_path.path), '.pdf');
305
- pdf_size = await hdoc_build_pdf.generate_pdf(browser, pdf_template_path, pdf_template, hdocbook_config, pdf_txt, pdf_file_path, css_templates, verbose);
306
- }
307
- if (pdf_size > 0) pdf_created++;
308
-
309
- if (inline_content)
310
- html_txt = `${fm_header_content}\n${html_txt}`;
311
- else
312
- html_txt = `${fm_header_content}\n${doc_header}\n${html_txt}`;
313
-
314
- let relative_path = file_path.relativePath;
315
- if (!bc[relative_path.replace('.html', '')] && bc[relative_path.replace('/index.html', '')]) {
316
- relative_path = relative_path.replace('/index.html', '');
317
- }
318
141
 
319
- index_records.push({
320
- relative_path: relative_path,
321
- index_html: hdoc_index.transform_html_for_index(html_txt),
322
- metadata: metadata,
323
- contributors: contribs,
324
- pdf_size: pdf_size,
325
- md5: file_path.hash,
326
- lastmod: last_commit !== null ? last_commit : file_path.hb_lastmod,
327
- inline: inline_content
328
- });
142
+ // Is reading-time in the fm headers?
143
+ if (fm_header.fm_properties["reading-time"] === undefined) {
144
+ const read_time_mins = hdoc.get_html_read_time(html_txt);
145
+ book_read_time += read_time_mins;
146
+ fm_header.fm_properties["reading-time"] = read_time_mins;
147
+ }
329
148
 
330
- // Save HTML into HTML file
331
- try {
332
- fs.writeFileSync(file_path.path, html_txt);
333
- } catch (err) {
334
- console.log('Error writing:', target_file, '\n', err);
149
+ for (const key in fm_header.fm_properties) {
150
+ if (fm_header.fm_properties.hasOwnProperty(key)) {
151
+ if (key === "type") doc_type = fm_header.fm_properties[key];
152
+ else {
153
+ fm_headers.push({
154
+ id: key,
155
+ value: fm_header.fm_properties[key],
156
+ });
335
157
  }
158
+ }
336
159
  }
337
- };
338
-
339
- const transform_markdown_and_save_html = async function (file_path) {
340
- conversion_attempted++;
341
-
342
- if (fs.existsSync(file_path.path)) {
343
- // Load markdown file
344
- let md_txt = hdoc.expand_variables(fs.readFileSync(file_path.path, 'utf8'));
345
-
346
- // Pull in external includes
347
- const includes_processed = await hdoc.process_includes(file_path.path, md_txt, global_source_path);
348
- md_txt = includes_processed.body.toString();
349
- includes_found += includes_processed.found;
350
- includes_success += includes_processed.success;
351
- includes_failed += includes_processed.failed;
352
- if (includes_processed.errors.length > 0) {
353
- for (let i = 0; i < includes_processed.errors.length; i++) {
354
- console.error(includes_processed.errors[i]);
355
- }
356
- }
357
160
 
358
- // One markdown parser per file. Seems wrong, but doesn't work with a global one once past the first md file
359
- // Steve - revisit this
360
- const md = require('markdown-it')({
361
- html: true,
362
- linkify: true,
363
- typographer: true
364
- });
365
- md.linkify.set({
366
- fuzzyEmail: false,
367
- fuzzyLink: false,
368
- fuzzyIP: false
369
- });
370
-
371
- // Process Frontmatter tags
372
- let frontmatter_content = "";
373
- md.use(mdfm, function (fm) {
374
- frontmatter_content = fm;
375
- });
376
-
377
- // Process tips
378
- const tips = require(__dirname + '/custom_modules/tips.js');
379
- md.use(tips, {
380
- links: true
161
+ if (
162
+ !fm_title_found &&
163
+ file_path.name !== "description_ext.md" &&
164
+ file_path.name !== "article_ext.md"
165
+ ) {
166
+ // No frontmatter title found in properties
167
+ // Go get title from h tags in html
168
+ const html_heading = hdoc.getFirstHTMLHeading(
169
+ html_txt,
170
+ h_tags_to_search
171
+ );
172
+
173
+ if (
174
+ html_heading &&
175
+ html_heading[0] &&
176
+ html_heading[0].children &&
177
+ html_heading[0].children[0] &&
178
+ html_heading[0].children[0].data
179
+ ) {
180
+ // We've found a heading tag, add that as a title to the existing frontmatter properties
181
+ fm_headers.push({
182
+ id: "title",
183
+ value: html_heading[0].children[0].data,
381
184
  });
185
+ doc_title = html_heading[0].children[0].data;
186
+ } else {
187
+ // No header tag, no frontmatter title, output a warning
188
+ console.log(
189
+ `[WARNING] No frontmatter title property, or ${h_tags_to_search.join(
190
+ ", "
191
+ )} tags detected in ${file_path.path}`
192
+ );
193
+ }
194
+ }
382
195
 
383
- // Tidy up ```json and ```xml code tags
384
-
385
- if (md_txt.includes('```json') || md_txt.includes('```xml'))
386
- md_txt = tidy_code_tags(md_txt, file_path.relativePath);
387
-
388
- // Render markdown into HTML
389
- let html_txt = md.render(md_txt);
390
-
391
- // Prepare frontmatter headers
392
- let fm_headers = [];
393
- let fm_content = frontmatter_content.split(/\r?\n/);
394
-
395
- let fm_contains_title = false,
396
- fm_contains_reading_time = false,
397
- fm_contains_description = false,
398
- doc_title = '',
399
- doc_type = 'Article';
400
-
401
- if (fm_content.length >= 0) {
402
- fm_content.forEach(function (fm_prop) {
403
- const fm_id = fm_prop.slice(0, fm_prop.indexOf(':'));
404
- const fm_val = fm_prop.slice(fm_prop.indexOf(':') + 1);
405
-
406
- if (fm_id && fm_id.trim().length > 0 && fm_val && fm_val.trim().length > 0) {
407
- fm_headers.push({
408
- id: fm_id.trim(),
409
- value: fm_val.trim()
410
- });
411
-
412
- if (fm_id.trim() === 'title') {
413
- fm_contains_title = true;
414
- doc_title = fm_val.trim();
415
- }
416
- if (fm_id.trim() === 'type') {
417
- doc_type = fm_val.trim();
418
- }
419
- if (fm_id.trim() === 'reading-time') {
420
- book_read_time += parseInt(fm_val.trim(), 10);
421
- fm_contains_reading_time = true;
422
- }
423
- if (fm_id.trim() === 'description') {
424
- fm_contains_description = true;
425
- }
426
- }
427
- });
196
+ // Do we have a description header?
197
+ if (
198
+ fm_header.fm_properties &&
199
+ fm_header.fm_properties.description !== undefined
200
+ ) {
201
+ if (fm_header.fm_properties.description === "") {
202
+ const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ["p"]);
203
+ if (
204
+ html_p_tag &&
205
+ html_p_tag[0] &&
206
+ html_p_tag[0].children &&
207
+ html_p_tag[0].children[0] &&
208
+ html_p_tag[0].children[0].data
209
+ ) {
210
+ fm_headers.push({
211
+ id: "description",
212
+ value: `${doc_title}: ${
213
+ html_p_tag[0].children[0].data.split(".")[0] + "."
214
+ }`.trim(),
215
+ });
428
216
  }
429
-
430
- // Add doc type
217
+ } else {
431
218
  fm_headers.push({
432
- id: 'type',
433
- value: doc_type,
219
+ id: "description",
220
+ value: fm_header.fm_properties.description.trim(),
434
221
  });
435
-
436
- // Does frontmatter tag contain a title property
437
- if (!fm_contains_title) {
438
- // Frontmatter tags don't contain a title property - go pull the first one from the html heading tags
439
- const html_heading = hdoc.getFirstHTMLHeading(html_txt, h_tags_to_search);
440
-
441
- if (html_heading && html_heading[0] && html_heading[0].children && html_heading[0].children[0] && html_heading[0].children[0].data) {
442
- // We've found a heading tag, add that as a title to the frontmatter content
443
- fm_headers.push({
444
- id: 'title',
445
- value: html_heading[0].children[0].data.trim()
446
- });
447
- doc_title = html_heading[0].children[0].data.trim();
448
- } else if (file_path.name !== 'description_ext.md' && file_path.name !== 'article_ext.md') {
449
- // No header tag, no frontmatter title, output a warning
450
- console.log(`[WARNING] No frontmatter title property, or h1, h2 or h3 header tags detected in ${file_path.path}`);
451
- }
452
- }
453
-
454
- // Does frontmatter contain a description header, generate one if not
455
- if (!fm_contains_description) {
456
- const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ['p']);
457
- if (html_p_tag && html_p_tag[0] && html_p_tag[0].children && html_p_tag[0].children[0] && html_p_tag[0].children[0].data) {
458
- fm_headers.push({
459
- id: 'description',
460
- value: `${doc_title}: ${html_p_tag[0].children[0].data.split('.')[0] + '.'}`
461
- });
462
- }
463
- }
464
-
465
- // Does frontmatter tag contain a reading-time property
466
- if (!fm_contains_reading_time) {
467
- const read_time_mins = hdoc.get_html_read_time(html_txt);
468
- book_read_time += read_time_mins;
469
- fm_headers.push({
470
- id: 'reading-time',
471
- value: read_time_mins
472
- });
473
- }
474
- let metadata = {};
475
-
476
- // Remove the first <h1>title</h1> from the HTML as we'll add that in the document header
477
- let html_h1 = h1_pattern.exec(html_txt);
478
- if (html_h1 && html_h1[0])
479
- html_h1 = html_h1[0].replace(/(<h1.*?>)/, '').replace(/(<\/h1>)/, '');
480
-
481
- html_txt = html_txt.replace(h1_pattern, '');
482
-
483
- // Get contributor data from Github, if exists
484
- let contribs = [];
485
- let last_commit = null;
486
- if (hdocbook_config.publicSource && hdocbook_config.publicSource !== '' && hdocbook_config.publicSource.includes('github.com/Hornbill-Docs')) {
487
-
488
- const github_paths = hdoc.get_github_api_path(hdocbook_config.publicSource, file_path.relativePath);
489
- const contributors = await hdoc.get_github_contributors(github_paths.api_path, git_token, hdocbook_config.publicSource);
490
-
491
- if (!contributors.success) {
492
- console.log(`Error retrieving contributors from Github: ${contributors.error}`);
493
- } else {
494
- last_commit = contributors.last_commit_date;
495
- metadata.last_commit = contributors.last_commit_date;
496
- metadata.contributor_count = contributors.contributor_count;
497
- metadata.edit_url = github_paths.edit_path;
498
- contribs = contributors.contributors;
499
- contributors.editPath = github_paths.edit_path;
500
-
501
- fm_headers.push({
502
- id: 'contributor-count',
503
- value: contributors.contributor_count
504
- });
505
- fm_headers.push({
506
- id: 'last-commit',
507
- value: contributors.last_commit_date
508
- });
509
- const target_file = file_path.path.replace(path.extname(file_path.path), '._info.json');
510
- delete contributors.success;
511
- delete contributors.error;
512
- contributors.editPath = github_paths.edit_path;
513
- try {
514
- fs.writeFileSync(target_file, JSON.stringify(contributors, null, 2));
515
- } catch (err) {
516
- console.log('Error writing:', target_file, '\n', err);
517
- }
518
- }
519
- fm_headers.push({
520
- id: 'edit-path',
521
- value: github_paths.edit_path
522
- });
523
- }
524
-
525
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
526
- fm_headers.push({
527
- id: 'pdf-path',
528
- value: file_path.relativePath.replace(path.extname(file_path.relativePath), '.pdf')
529
- })
530
- }
531
-
532
- // Add frontmatter tags as comment
533
- let fm_header = '<!--[[FRONTMATTER\n';
534
- if (fm_headers.length > 0) {
535
- for (let i = 0; i < fm_headers.length; i++) {
536
- fm_header += `${fm_headers[i].id}: ${fm_headers[i].value}\n`;
537
- }
538
- }
539
- fm_header += ']]-->';
540
-
541
- let doc_header = '';
542
- let pdf_header = '';
543
- const inline_content = file_path.relativePath.startsWith(`${hdocbook_config.docId}/_inline/`);
544
- if (hdocbook_config.publicSource && hdocbook_config.publicSource.includes('github.com/Hornbill-Docs')) {
545
- // Build doc header from template and frontmatter tags
546
- if (!inline_content)
547
- doc_header = process_doc_header(fm_headers, file_path.relativePath, doc_header_template, html_h1);
548
-
549
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
550
- pdf_header = process_doc_header(fm_headers, file_path.relativePath, pdf_header_template, html_h1);
551
- } else {
552
- // Build doc header from template and frontmatter tags
553
- if (!inline_content)
554
- doc_header = process_doc_header(fm_headers, file_path.relativePath, doc_header_template_non_git, html_h1);
555
-
556
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
557
- pdf_header = process_doc_header(fm_headers, file_path.relativePath, pdf_header_template_non_git, html_h1);
558
- }
559
-
560
- let pdf_size = 0;
561
- if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
562
- let pdf_txt = await hdoc_build_pdf.process_images(file_path, html_txt);
563
- pdf_txt = `${pdf_header}\n${pdf_txt}`;
564
-
565
- // Generate PDF file from HTML
566
- const pdf_file_path = file_path.path.replace(path.extname(file_path.path), '.pdf');
567
- pdf_size = await hdoc_build_pdf.generate_pdf(browser, pdf_template_path, pdf_template, hdocbook_config, pdf_txt, pdf_file_path, css_templates, verbose);
568
- }
569
- if (pdf_size > 0) pdf_created++;
570
-
571
- if (inline_content)
572
- html_txt = `${fm_header}\n${html_txt}`;
573
- else
574
- html_txt = `${fm_header}\n${doc_header}\n${html_txt}`;
575
-
576
- // Save HTML into HTML file
577
- const target_file = file_path.path.replace(path.extname(file_path.path), '.html');
578
- let relative_path = file_path.relativePath.replace(path.extname(file_path.path), '.html');
579
- try {
580
- fs.writeFileSync(target_file, html_txt);
581
- } catch (err) {
582
- console.log('Error writing:', target_file, '\n', err);
583
- }
584
-
585
- if (!bc[relative_path.replace('.html', '')] && bc[relative_path.replace('/index.html', '')]) {
586
- relative_path = relative_path.replace('/index.html', '');
587
- }
588
-
589
- index_records.push({
590
- relative_path: relative_path,
591
- index_html: hdoc_index.transform_html_for_index(html_txt),
592
- metadata: metadata,
593
- contributors: contribs,
594
- pdf_size: pdf_size,
595
- md5: file_path.hash,
596
- lastmod: last_commit !== null ? last_commit : file_path.hb_lastmod,
597
- inline: inline_content
222
+ }
223
+ } else {
224
+ const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ["p"]);
225
+ if (
226
+ html_p_tag &&
227
+ html_p_tag[0] &&
228
+ html_p_tag[0].children &&
229
+ html_p_tag[0].children[0] &&
230
+ html_p_tag[0].children[0].data
231
+ ) {
232
+ fm_headers.push({
233
+ id: "description",
234
+ value: `${doc_title}: ${
235
+ html_p_tag[0].children[0].data.split(".")[0] + "."
236
+ }`.trim(),
598
237
  });
599
-
600
- // Add MD file to delete queue
601
- md_files_delete.push(file_path.path)
602
-
603
- conversion_success++;
604
- return true;
238
+ }
605
239
  }
606
- conversion_failed++;
607
- console.error('MD file does not exist:', file_path.path);
608
- return false;
609
- };
610
-
611
- const tidy_code_tags = function (markdown, file) {
612
- const json_to_tidy = markdown.match(/```json[\r\n](\s|.)*?```/g);
613
- if (json_to_tidy && json_to_tidy.length > 0) {
614
- for (let i = 0; i < json_to_tidy.length; i++) {
615
- if (json_to_tidy[i] !== '') {
616
- let json_tidy = json_to_tidy[i].replace('```json', '').replace('```', '');
617
- try {
618
- json_tidy = JSON.stringify(JSON.parse(json_tidy), null, 2);
619
- } catch (e) {
620
- console.log(`[WARNING] Could not tidy JSON in file [${file}]: ${e}`);
621
- }
622
- markdown = markdown.replace(json_to_tidy[i], '```json\n' + json_tidy + '\n```');
623
- }
624
- }
240
+ } else {
241
+ // We have no frontmatter headers, get and build one from the html headings
242
+ const html_heading = hdoc.getFirstHTMLHeading(
243
+ html_txt,
244
+ h_tags_to_search
245
+ );
246
+ let doc_title = "";
247
+ // Add the title
248
+ if (
249
+ html_heading &&
250
+ html_heading[0] &&
251
+ html_heading[0].children &&
252
+ html_heading[0].children[0] &&
253
+ html_heading[0].children[0].data
254
+ ) {
255
+ // We've found a heading tag, add that as a title to the frontmatter content
256
+ fm_headers.push({
257
+ id: "title",
258
+ value: html_heading[0].children[0].data,
259
+ });
260
+ doc_title = html_heading[0].children[0].data;
261
+ } else if (
262
+ file_path.name !== "description_ext.md" &&
263
+ file_path.name !== "article_ext.md"
264
+ ) {
265
+ // No header tag, no frontmatter title, output a warning
266
+ console.log(
267
+ `[WARNING] No frontmatter title property, or ${h_tags_to_search.join(
268
+ ", "
269
+ )} tags detected in ${file_path.path}`
270
+ );
625
271
  }
626
272
 
627
- const xml_to_tidy = markdown.match(/```xml[\r\n](\s|.)*?```/g);
628
- if (xml_to_tidy && xml_to_tidy.length > 0) {
629
- for (let i = 0; i < xml_to_tidy.length; i++) {
630
- if (xml_to_tidy[i] !== '') {
631
- const xml_tidy = xml_to_tidy[i].replace('```xml', '').replace('```', '');
632
- let new_xml_string = xml_tidy;
633
- try {
634
- new_xml_string = xmlFormat(xml_tidy, {
635
- indentation: ' ',
636
- collapseContent: true,
637
- lineSeparator: '\n'
638
- });
639
- } catch (e) {
640
- console.log(`[WARNING] Could not tidy XML in file [${file}]: ${e}`);
641
- }
642
- markdown = markdown.replace(xml_to_tidy[i], '```xml\n' + new_xml_string + '\n```');
643
- }
644
- }
273
+ // Add the reading time
274
+ const read_time_mins = hdoc.get_html_read_time(html_txt);
275
+ book_read_time += read_time_mins;
276
+ fm_headers.push({
277
+ id: "reading-time",
278
+ value: read_time_mins,
279
+ });
280
+
281
+ const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ["p"]);
282
+ if (
283
+ html_p_tag &&
284
+ html_p_tag[0] &&
285
+ html_p_tag[0].children &&
286
+ html_p_tag[0].children[0] &&
287
+ html_p_tag[0].children[0].data
288
+ ) {
289
+ fm_headers.push({
290
+ id: "description",
291
+ value: `${doc_title}: ${
292
+ html_p_tag[0].children[0].data.split(".")[0] + "."
293
+ }`.trim(),
294
+ });
645
295
  }
646
- return markdown;
647
- };
648
-
649
- const process_doc_header = function (fm_headers, doc_path, template, h1) {
650
- let wip_doc_header = template;
651
- let used_h1 = false;
652
- if (h1 && h1 !== '') {
653
- wip_doc_header = wip_doc_header.replaceAll('{{title}}', h1);
654
- used_h1 = true;
296
+ }
297
+
298
+ // Add doc type
299
+ fm_headers.push({
300
+ id: "type",
301
+ value: doc_type,
302
+ });
303
+
304
+ let metadata = {};
305
+
306
+ // Remove the first <h1>title</h1> from the HTML as we'll add that in the document header
307
+ let html_h1 = h1_pattern.exec(html_txt);
308
+ if (html_h1 && html_h1[0])
309
+ html_h1 = html_h1[0].replace(/(<h1.*?>)/, "").replace(/(<\/h1>)/, "");
310
+
311
+ html_txt = html_txt.replace(h1_pattern, "");
312
+
313
+ // Get contributor data from Github, if exists
314
+ let contribs = [];
315
+ let last_commit = null;
316
+ if (
317
+ hdocbook_config.publicSource &&
318
+ hdocbook_config.publicSource !== "" &&
319
+ hdocbook_config.publicSource.includes("github.com/Hornbill-Docs")
320
+ ) {
321
+ const github_paths = hdoc.get_github_api_path(
322
+ hdocbook_config.publicSource,
323
+ file_path.relativePath
324
+ );
325
+ const contributors = hdoc.get_github_contributors(
326
+ github_paths.api_path,
327
+ git_token
328
+ );
329
+
330
+ if (!contributors.success) {
331
+ console.log(
332
+ `Error retrieving contributors from Github: ${contributors.error}`
333
+ );
334
+ } else {
335
+ last_commit = contributors.last_commit_date;
336
+ metadata.last_commit = contributors.last_commit_date;
337
+ metadata.contributor_count = contributors.contributor_count;
338
+ metadata.edit_url = github_paths.edit_path;
339
+ contribs = contributors.contributors;
340
+ contributors.editPath = github_paths.edit_path;
341
+ fm_headers.push({
342
+ id: "contributor-count",
343
+ value: contributors.contributor_count,
344
+ });
345
+ fm_headers.push({
346
+ id: "last-commit",
347
+ value: contributors.last_commit_date,
348
+ });
349
+ const target_file = file_path.path.replace(
350
+ path.extname(file_path.path),
351
+ "._info.json"
352
+ );
353
+ delete contributors.success;
354
+ delete contributors.error;
355
+ contributors.editPath = github_paths.edit_path;
655
356
  }
656
- // Process fm_headers properties first
357
+ fm_headers.push({
358
+ id: "edit-path",
359
+ value: github_paths.edit_path,
360
+ });
361
+ }
362
+
363
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
364
+ fm_headers.push({
365
+ id: "pdf-path",
366
+ value: file_path.relativePath.replace(
367
+ path.extname(file_path.relativePath),
368
+ ".pdf"
369
+ ),
370
+ });
371
+ }
372
+
373
+ let fm_header_content = "<!--[[FRONTMATTER\n";
374
+ if (fm_headers.length > 0) {
657
375
  for (let i = 0; i < fm_headers.length; i++) {
658
- switch (fm_headers[i].id) {
659
- case 'title':
660
- if (!used_h1)
661
- wip_doc_header = wip_doc_header.replaceAll('{{title}}', fm_headers[i].value);
662
- break;
663
- case 'reading-time':
664
- wip_doc_header = wip_doc_header.replaceAll('{{reading-time}}', fm_headers[i].value);
665
- break;
666
- case 'contributor-count':
667
- wip_doc_header = wip_doc_header.replaceAll('{{contributor-count}}', fm_headers[i].value);
668
- break;
669
- case 'type':
670
- wip_doc_header = wip_doc_header.replaceAll('{{doc-type}}', fm_headers[i].value);
671
- break;
672
- case 'edit-path':
673
- wip_doc_header = wip_doc_header.replaceAll('{{edit-url}}', fm_headers[i].value);
674
- break;
675
- case 'last-commit':
676
- let last_commit_date = fm_headers[i].value;
677
- if (last_commit_date !== 'No Commit Date Available') {
678
- last_commit_date = new Date(fm_headers[i].value).toDateString();
679
- }
680
- wip_doc_header = wip_doc_header.replaceAll('{{last-update}}', last_commit_date);
681
- break;
682
- }
376
+ fm_header_content += `${fm_headers[i].id}: ${fm_headers[i].value}\n`;
683
377
  }
378
+ fm_header_content += "]]-->";
684
379
 
685
- // Now sort out breadcrumbs
686
- const logical_path = doc_path.replace(path.extname(doc_path), '');
687
- const bc_for_path = bc[logical_path];
688
- let bc_tags = '\n';
689
- if (bc_for_path) {
690
- for (let i = 0; i < bc_for_path.length - 1; i++) {
691
- let bc_link = '/';
692
- if (redirects[bc_for_path[i].link]) {
693
- if (redirects[bc_for_path[i].link].location) {
694
- bc_link += redirects[bc_for_path[i].link].location;
695
- }
696
- } else {
697
- if (bc_for_path[i].link) {
698
- bc_link = bc_for_path[i].link.startsWith('/') ? bc_for_path[i].link : `/${bc_for_path[i].link}`;
699
- } else {
700
- bc_link = '';
701
- }
702
-
703
- }
704
- if (bc_link !== '') {
705
- bc_tags += `\t\t\t\t<li class="mt-0 nav-bar-item"><a href="${bc_link}" class="ps-0 pe-0 text-decoration-none">${bc_for_path[i].text}</a></li>\n`;
706
- } else {
707
- bc_tags += `\t\t\t\t<li class="mt-0 nav-bar-item">${bc_for_path[i].text}</li>\n`;
708
- }
709
- }
710
- } else {
711
- if (verbose) {
712
- console.log(`[WARNING] Path is not present in navigation items: ${logical_path}`);
713
- }
380
+ if (existing_fm_headers) {
381
+ html_txt = html_txt.replace("<!--" + fm_header.fm_header + "-->", "");
714
382
  }
715
- bc_tags += '\t\t\t';
716
- wip_doc_header = wip_doc_header.replaceAll('{{breadcrumbs}}', bc_tags);
717
- return wip_doc_header;
718
- };
719
-
720
- // File callback for build scan
721
- const build_file_callback = function (element) {
722
- if (element.extension === 'md') {
723
- element.hb_source_path = path.join(global_source_path, element.relativePath);
724
- const fstats = fs.statSync(element.hb_source_path);
725
- element.hb_lastmod = `${fstats.mtime.toISOString().slice(0, 19)}Z`;
726
- md_files.push(element);
727
- } else {
728
- // File is html, see if there's a matching md file and if there is then ignore the html
729
- const md_path = element.path.replace(path.extname(element.path), '.md');
730
- if (fs.existsSync(md_path)) {
731
- return;
732
- }
733
- element.hb_source_path = path.join(global_source_path, element.relativePath);
734
- const fstats = fs.statSync(element.hb_source_path);
735
- element.hb_lastmod = `${fstats.mtime.toISOString().slice(0, 19)}Z`;
736
- static_html_files.push(element);
383
+ }
384
+
385
+ let doc_header = "";
386
+ let pdf_header = "";
387
+ const inline_content = file_path.relativePath.startsWith(
388
+ `${hdocbook_config.docId}/_inline/`
389
+ );
390
+ if (
391
+ hdocbook_config.publicSource &&
392
+ hdocbook_config.publicSource.includes("github.com/Hornbill-Docs")
393
+ ) {
394
+ // Build doc header from template and frontmatter tags
395
+ if (!inline_content)
396
+ doc_header = process_doc_header(
397
+ fm_headers,
398
+ file_path.relativePath,
399
+ doc_header_template,
400
+ html_h1
401
+ );
402
+
403
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
404
+ pdf_header = process_doc_header(
405
+ fm_headers,
406
+ file_path.relativePath,
407
+ pdf_header_template,
408
+ html_h1
409
+ );
410
+ } else {
411
+ if (!inline_content)
412
+ doc_header = process_doc_header(
413
+ fm_headers,
414
+ file_path.relativePath,
415
+ doc_header_template_non_git,
416
+ html_h1
417
+ );
418
+
419
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
420
+ pdf_header = process_doc_header(
421
+ fm_headers,
422
+ file_path.relativePath,
423
+ pdf_header_template_non_git,
424
+ html_h1
425
+ );
426
+ }
427
+
428
+ let pdf_size = 0;
429
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
430
+ let pdf_txt = await hdoc_build_pdf.process_images(file_path, html_txt);
431
+ pdf_txt = `${pdf_header}\n${pdf_txt}`;
432
+
433
+ // Generate PDF file from HTML
434
+ const pdf_file_path = file_path.path.replace(
435
+ path.extname(file_path.path),
436
+ ".pdf"
437
+ );
438
+ pdf_size = await hdoc_build_pdf.generate_pdf(
439
+ browser,
440
+ pdf_template_path,
441
+ pdf_template,
442
+ hdocbook_config,
443
+ pdf_txt,
444
+ pdf_file_path,
445
+ css_templates,
446
+ verbose
447
+ );
448
+ }
449
+ if (pdf_size > 0) pdf_created++;
450
+
451
+ if (inline_content) html_txt = `${fm_header_content}\n${html_txt}`;
452
+ else html_txt = `${fm_header_content}\n${doc_header}\n${html_txt}`;
453
+
454
+ let relative_path = file_path.relativePath;
455
+ if (
456
+ !bc[relative_path.replace(".html", "")] &&
457
+ bc[relative_path.replace("/index.html", "")]
458
+ ) {
459
+ relative_path = relative_path.replace("/index.html", "");
460
+ }
461
+
462
+ index_records.push({
463
+ relative_path: relative_path,
464
+ index_html: hdoc_index.transform_html_for_index(html_txt),
465
+ metadata: metadata,
466
+ contributors: contribs,
467
+ pdf_size: pdf_size,
468
+ md5: file_path.hash,
469
+ lastmod: last_commit !== null ? last_commit : file_path.hb_lastmod,
470
+ inline: inline_content,
471
+ });
472
+
473
+ // Save HTML into HTML file
474
+ try {
475
+ fs.writeFileSync(file_path.path, html_txt);
476
+ } catch (err) {
477
+ console.log("Error writing:", target_file, "\n", err);
478
+ }
479
+ }
480
+ };
481
+
482
+ const transform_markdown_and_save_html = async function (file_path) {
483
+ conversion_attempted++;
484
+
485
+ if (fs.existsSync(file_path.path)) {
486
+ // Load markdown file
487
+ let md_txt = hdoc.expand_variables(
488
+ fs.readFileSync(file_path.path, "utf8")
489
+ );
490
+
491
+ // Pull in external includes
492
+ const includes_processed = await hdoc.process_includes(
493
+ file_path.path,
494
+ md_txt,
495
+ global_source_path
496
+ );
497
+ md_txt = includes_processed.body.toString();
498
+ includes_found += includes_processed.found;
499
+ includes_success += includes_processed.success;
500
+ includes_failed += includes_processed.failed;
501
+ if (includes_processed.errors.length > 0) {
502
+ for (let i = 0; i < includes_processed.errors.length; i++) {
503
+ console.error(includes_processed.errors[i]);
737
504
  }
738
- };
739
-
740
- // File & folder callback for MD5 hash of built content
741
- const hash_callback = function (element) {
742
- if (element.extension !== 'db') {
743
- built_file_hashes.push({
744
- path: element.relativePath,
745
- hash: element.hash
505
+ }
506
+
507
+ // One markdown parser per file. Seems wrong, but doesn't work with a global one once past the first md file
508
+ // Steve - revisit this
509
+ const md = require("markdown-it")({
510
+ html: true,
511
+ linkify: true,
512
+ typographer: true,
513
+ });
514
+ md.linkify.set({
515
+ fuzzyEmail: false,
516
+ fuzzyLink: false,
517
+ fuzzyIP: false,
518
+ });
519
+
520
+ // Process Frontmatter tags
521
+ let frontmatter_content = "";
522
+ md.use(mdfm, function (fm) {
523
+ frontmatter_content = fm;
524
+ });
525
+
526
+ // Process tips
527
+ const tips = require(__dirname + "/custom_modules/tips.js");
528
+ md.use(tips, {
529
+ links: true,
530
+ });
531
+
532
+ // Tidy up ```json and ```xml code tags
533
+
534
+ if (md_txt.includes("```json") || md_txt.includes("```xml"))
535
+ md_txt = tidy_code_tags(md_txt, file_path.relativePath);
536
+
537
+ // Render markdown into HTML
538
+ let html_txt = md.render(md_txt);
539
+
540
+ // Prepare frontmatter headers
541
+ let fm_headers = [];
542
+ let fm_content = frontmatter_content.split(/\r?\n/);
543
+
544
+ let fm_contains_title = false,
545
+ fm_contains_reading_time = false,
546
+ fm_contains_description = false,
547
+ doc_title = "",
548
+ doc_type = "Article";
549
+
550
+ if (fm_content.length >= 0) {
551
+ fm_content.forEach(function (fm_prop) {
552
+ const fm_id = fm_prop.slice(0, fm_prop.indexOf(":"));
553
+ const fm_val = fm_prop.slice(fm_prop.indexOf(":") + 1);
554
+
555
+ if (
556
+ fm_id &&
557
+ fm_id.trim().length > 0 &&
558
+ fm_val &&
559
+ fm_val.trim().length > 0
560
+ ) {
561
+ fm_headers.push({
562
+ id: fm_id.trim(),
563
+ value: fm_val.trim(),
746
564
  });
747
- }
748
- };
749
-
750
- const dreeOptions = {
751
- hash: true,
752
- extensions: ['md', 'html', 'htm'],
753
- normalize: true,
754
- stat: true
755
- };
756
-
757
- const md5DreeOptions = {
758
- hash: true,
759
- normalize: true,
760
- sorted: true
761
- };
762
-
763
- exports.run = async function (source_path, verbose_output, github_api_token, validate, gen_exclude, build_version = '') {
764
- if (github_api_token !== '') {
765
- git_token = github_api_token;
766
- }
767
- global_source_path = source_path;
768
- verbose = verbose_output;
769
-
770
- const start_time = Date.now();
771
- // GERRY: The purpose of this function is to create a zip file containing the hdocbook content,
772
- // * Create a _work folder
773
- // * copy the hdocbook content to the work folder
774
- // * Render all markdown into side-by-side HTML file
775
- // * Replace SERVER_VARS embedded in documents with the right version information etc.
776
- // * Build an index (sqlite FTS5) by extracting text from all HTML content in the work
777
- // folder, conceptually we are making a little mini website crawler to index all of the content
778
- // within the book.
779
- // * Package everything up into a ZIP file, ready for the build controller to package and publish
780
-
781
- console.log('Hornbill HDocBook Build', '\n');
782
- console.log(' Document Path:', source_path, '\n');
783
- const build_start_dt = new Date().toLocaleString();
784
-
785
- // Load the hdocbook-project.json file to get the docId
786
- // use the docId to get the book config
787
- const hdocbook_project_config_path = path.join(source_path, 'hdocbook-project.json');
788
- try {
789
- hdocbook_project = require(hdocbook_project_config_path);
790
- } catch (e) {
791
- console.log('File not found: hdocbook-project.json\n');
792
- console.log('hdoc build/validate needs to be run in the root of a HDoc Book.\n');
793
- process.exit(1);
794
- }
795
- doc_id = hdocbook_project.docId;
796
-
797
- if (!validate && hdocbook_project.pdfGeneration !== undefined && hdocbook_project.pdfGeneration.enable !== undefined) {
798
- pdf_enable = hdocbook_project.pdfGeneration.enable;
799
- }
800
565
 
801
- if (hdocbook_project.redirects && hdocbook_project.redirects instanceof Array) {
802
- for (let i = 0; i < hdocbook_project.redirects.length; i++) {
803
- const redirect_key = hdocbook_project.redirects[i].url.indexOf('/') == 0 ? hdocbook_project.redirects[i].url.substring(1) : hdocbook_project.redirects[i].url;
804
- redirects[redirect_key] = hdocbook_project.redirects[i];
566
+ if (fm_id.trim() === "title") {
567
+ fm_contains_title = true;
568
+ doc_title = fm_val.trim();
805
569
  }
806
- }
807
-
808
- console.log(`Loading hdocbook config...`);
809
-
810
- const book_path = path.join(source_path, doc_id),
811
- hdocbook_path = path.join(book_path, 'hdocbook.json'),
812
- work_path = path.join(source_path, '_work'),
813
- work_hdocbook_path = path.join(work_path, doc_id, 'hdocbook.json');
814
-
815
- hdocbook_config = require(hdocbook_path);
816
- if (build_version !== '') {
817
- if (build_version.match(regex_version)) {
818
- hdocbook_config.version = build_version;
819
- } else {
820
- console.log(`\n[WARNING] Argument build version [${build_version}] does not match expected pattern, defaulting to version specified in book [${hdocbook_config.version}]\n`);
570
+ if (fm_id.trim() === "type") {
571
+ doc_type = fm_val.trim();
572
+ }
573
+ if (fm_id.trim() === "reading-time") {
574
+ book_read_time += parseInt(fm_val.trim(), 10);
575
+ fm_contains_reading_time = true;
821
576
  }
577
+ if (fm_id.trim() === "description") {
578
+ fm_contains_description = true;
579
+ }
580
+ }
581
+ });
582
+ }
583
+
584
+ // Add doc type
585
+ fm_headers.push({
586
+ id: "type",
587
+ value: doc_type,
588
+ });
589
+
590
+ // Does frontmatter tag contain a title property
591
+ if (!fm_contains_title) {
592
+ // Frontmatter tags don't contain a title property - go pull the first one from the html heading tags
593
+ const html_heading = hdoc.getFirstHTMLHeading(
594
+ html_txt,
595
+ h_tags_to_search
596
+ );
597
+
598
+ if (
599
+ html_heading &&
600
+ html_heading[0] &&
601
+ html_heading[0].children &&
602
+ html_heading[0].children[0] &&
603
+ html_heading[0].children[0].data
604
+ ) {
605
+ // We've found a heading tag, add that as a title to the frontmatter content
606
+ fm_headers.push({
607
+ id: "title",
608
+ value: html_heading[0].children[0].data.trim(),
609
+ });
610
+ doc_title = html_heading[0].children[0].data.trim();
611
+ } else if (
612
+ file_path.name !== "description_ext.md" &&
613
+ file_path.name !== "article_ext.md"
614
+ ) {
615
+ // No header tag, no frontmatter title, output a warning
616
+ console.log(
617
+ `[WARNING] No frontmatter title property, or h1, h2 or h3 header tags detected in ${file_path.path}`
618
+ );
822
619
  }
823
-
824
- if (!hdocbook_config.version.match(regex_version)) {
825
- console.log(`ERROR: Version number does not match required format - ${hdocbook_config.version}\n`);
826
- process.exit(1);
620
+ }
621
+
622
+ // Does frontmatter contain a description header, generate one if not
623
+ if (!fm_contains_description) {
624
+ const html_p_tag = hdoc.getFirstHTMLHeading(html_txt, ["p"]);
625
+ if (
626
+ html_p_tag &&
627
+ html_p_tag[0] &&
628
+ html_p_tag[0].children &&
629
+ html_p_tag[0].children[0] &&
630
+ html_p_tag[0].children[0].data
631
+ ) {
632
+ fm_headers.push({
633
+ id: "description",
634
+ value: `${doc_title}: ${
635
+ html_p_tag[0].children[0].data.split(".")[0] + "."
636
+ }`,
637
+ });
827
638
  }
828
-
829
- if (hdocbook_config.publicSource && hdocbook_config.publicSource.endsWith('.git')) hdocbook_config.publicSource = hdocbook_config.publicSource.substring(0, hdocbook_config.publicSource.length - 4);
830
-
831
- console.log(`Loading product families...`);
832
- const prods = await hdoc.load_product_families();
833
- if (!prods.success) {
834
- console.log(`${prods.errors}\n`);
835
- process.exit(1);
639
+ }
640
+
641
+ // Does frontmatter tag contain a reading-time property
642
+ if (!fm_contains_reading_time) {
643
+ const read_time_mins = hdoc.get_html_read_time(html_txt);
644
+ book_read_time += read_time_mins;
645
+ fm_headers.push({
646
+ id: "reading-time",
647
+ value: read_time_mins,
648
+ });
649
+ }
650
+ let metadata = {};
651
+
652
+ // Remove the first <h1>title</h1> from the HTML as we'll add that in the document header
653
+ let html_h1 = h1_pattern.exec(html_txt);
654
+ if (html_h1 && html_h1[0])
655
+ html_h1 = html_h1[0].replace(/(<h1.*?>)/, "").replace(/(<\/h1>)/, "");
656
+
657
+ html_txt = html_txt.replace(h1_pattern, "");
658
+
659
+ // Get contributor data from Github, if exists
660
+ let contribs = [];
661
+ let last_commit = null;
662
+ if (
663
+ hdocbook_config.publicSource &&
664
+ hdocbook_config.publicSource !== "" &&
665
+ hdocbook_config.publicSource.includes("github.com/Hornbill-Docs")
666
+ ) {
667
+ const github_paths = hdoc.get_github_api_path(
668
+ hdocbook_config.publicSource,
669
+ file_path.relativePath
670
+ );
671
+ const contributors = await hdoc.get_github_contributors(
672
+ github_paths.api_path,
673
+ git_token,
674
+ hdocbook_config.publicSource
675
+ );
676
+
677
+ if (!contributors.success) {
678
+ console.log(
679
+ `Error retrieving contributors from Github: ${contributors.error}`
680
+ );
836
681
  } else {
837
- prod_families = prods.prod_families;
838
- prods_supported = prods.prods_supported;
682
+ last_commit = contributors.last_commit_date;
683
+ metadata.last_commit = contributors.last_commit_date;
684
+ metadata.contributor_count = contributors.contributor_count;
685
+ metadata.edit_url = github_paths.edit_path;
686
+ contribs = contributors.contributors;
687
+ contributors.editPath = github_paths.edit_path;
688
+
689
+ fm_headers.push({
690
+ id: "contributor-count",
691
+ value: contributors.contributor_count,
692
+ });
693
+ fm_headers.push({
694
+ id: "last-commit",
695
+ value: contributors.last_commit_date,
696
+ });
697
+ const target_file = file_path.path.replace(
698
+ path.extname(file_path.path),
699
+ "._info.json"
700
+ );
701
+ delete contributors.success;
702
+ delete contributors.error;
703
+ contributors.editPath = github_paths.edit_path;
704
+ try {
705
+ fs.writeFileSync(
706
+ target_file,
707
+ JSON.stringify(contributors, null, 2)
708
+ );
709
+ } catch (err) {
710
+ console.log("Error writing:", target_file, "\n", err);
711
+ }
839
712
  }
840
-
841
- if (!validate) {
842
- console.log('Caching CSS for PDF generation...');
843
- const css_files = [
844
- path.join(pdf_template_path, 'css', 'custom-block.css'),
845
- path.join(pdf_template_path, 'css', 'hdocs-pdf.css'),
846
- path.join(pdf_template_path, 'css', 'vars.css')
847
- ];
848
- for (let i = 0; i < css_files.length; i++) {
849
- try {
850
- css_templates.push(fs.readFileSync(css_files[i], 'utf8'));
851
- } catch (e) {
852
- console.log(`Error reading file[${css_files[i]}]: ${e}`);
853
- }
854
- }
713
+ fm_headers.push({
714
+ id: "edit-path",
715
+ value: github_paths.edit_path,
716
+ });
717
+ }
718
+
719
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
720
+ fm_headers.push({
721
+ id: "pdf-path",
722
+ value: file_path.relativePath.replace(
723
+ path.extname(file_path.relativePath),
724
+ ".pdf"
725
+ ),
726
+ });
727
+ }
728
+
729
+ // Add frontmatter tags as comment
730
+ let fm_header = "<!--[[FRONTMATTER\n";
731
+ if (fm_headers.length > 0) {
732
+ for (let i = 0; i < fm_headers.length; i++) {
733
+ fm_header += `${fm_headers[i].id}: ${fm_headers[i].value}\n`;
855
734
  }
735
+ }
736
+ fm_header += "]]-->";
737
+
738
+ let doc_header = "";
739
+ let pdf_header = "";
740
+ const inline_content = file_path.relativePath.startsWith(
741
+ `${hdocbook_config.docId}/_inline/`
742
+ );
743
+ if (
744
+ hdocbook_config.publicSource &&
745
+ hdocbook_config.publicSource.includes("github.com/Hornbill-Docs")
746
+ ) {
747
+ // Build doc header from template and frontmatter tags
748
+ if (!inline_content)
749
+ doc_header = process_doc_header(
750
+ fm_headers,
751
+ file_path.relativePath,
752
+ doc_header_template,
753
+ html_h1
754
+ );
755
+
756
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
757
+ pdf_header = process_doc_header(
758
+ fm_headers,
759
+ file_path.relativePath,
760
+ pdf_header_template,
761
+ html_h1
762
+ );
763
+ } else {
764
+ // Build doc header from template and frontmatter tags
765
+ if (!inline_content)
766
+ doc_header = process_doc_header(
767
+ fm_headers,
768
+ file_path.relativePath,
769
+ doc_header_template_non_git,
770
+ html_h1
771
+ );
772
+
773
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath))
774
+ pdf_header = process_doc_header(
775
+ fm_headers,
776
+ file_path.relativePath,
777
+ pdf_header_template_non_git,
778
+ html_h1
779
+ );
780
+ }
781
+
782
+ let pdf_size = 0;
783
+ if (pdf_enable && !pdf_path_excluded(file_path.relativePath)) {
784
+ let pdf_txt = await hdoc_build_pdf.process_images(file_path, html_txt);
785
+ pdf_txt = `${pdf_header}\n${pdf_txt}`;
786
+
787
+ // Generate PDF file from HTML
788
+ const pdf_file_path = file_path.path.replace(
789
+ path.extname(file_path.path),
790
+ ".pdf"
791
+ );
792
+ pdf_size = await hdoc_build_pdf.generate_pdf(
793
+ browser,
794
+ pdf_template_path,
795
+ pdf_template,
796
+ hdocbook_config,
797
+ pdf_txt,
798
+ pdf_file_path,
799
+ css_templates,
800
+ verbose
801
+ );
802
+ }
803
+ if (pdf_size > 0) pdf_created++;
804
+
805
+ if (inline_content) html_txt = `${fm_header}\n${html_txt}`;
806
+ else html_txt = `${fm_header}\n${doc_header}\n${html_txt}`;
807
+
808
+ // Save HTML into HTML file
809
+ const target_file = file_path.path.replace(
810
+ path.extname(file_path.path),
811
+ ".html"
812
+ );
813
+ let relative_path = file_path.relativePath.replace(
814
+ path.extname(file_path.path),
815
+ ".html"
816
+ );
817
+ try {
818
+ fs.writeFileSync(target_file, html_txt);
819
+ } catch (err) {
820
+ console.log("Error writing:", target_file, "\n", err);
821
+ }
822
+
823
+ if (
824
+ !bc[relative_path.replace(".html", "")] &&
825
+ bc[relative_path.replace("/index.html", "")]
826
+ ) {
827
+ relative_path = relative_path.replace("/index.html", "");
828
+ }
829
+
830
+ index_records.push({
831
+ relative_path: relative_path,
832
+ index_html: hdoc_index.transform_html_for_index(html_txt),
833
+ metadata: metadata,
834
+ contributors: contribs,
835
+ pdf_size: pdf_size,
836
+ md5: file_path.hash,
837
+ lastmod: last_commit !== null ? last_commit : file_path.hb_lastmod,
838
+ inline: inline_content,
839
+ });
840
+
841
+ // Add MD file to delete queue
842
+ md_files_delete.push(file_path.path);
843
+
844
+ conversion_success++;
845
+ return true;
846
+ }
847
+ conversion_failed++;
848
+ console.error("MD file does not exist:", file_path.path);
849
+ return false;
850
+ };
851
+
852
+ const tidy_code_tags = function (markdown, file) {
853
+ const json_to_tidy = markdown.match(/```json[\r\n](\s|.)*?```/g);
854
+ if (json_to_tidy && json_to_tidy.length > 0) {
855
+ for (let i = 0; i < json_to_tidy.length; i++) {
856
+ if (json_to_tidy[i] !== "") {
857
+ let json_tidy = json_to_tidy[i]
858
+ .replace("```json", "")
859
+ .replace("```", "");
860
+ try {
861
+ json_tidy = JSON.stringify(JSON.parse(json_tidy), null, 2);
862
+ } catch (e) {
863
+ console.log(
864
+ `[WARNING] Could not tidy JSON in file [${file}]: ${e}`
865
+ );
866
+ }
867
+ markdown = markdown.replace(
868
+ json_to_tidy[i],
869
+ "```json\n" + json_tidy + "\n```"
870
+ );
871
+ }
872
+ }
873
+ }
856
874
 
857
- console.log(`Building: ${doc_id} v${hdocbook_config.version}...\n`);
858
-
859
- // Make _work folder to copy everything into
860
- work_path_content = path.join(work_path, doc_id);
861
- if (fs.existsSync(work_path)) {
862
- fs.rmSync(work_path, {
863
- recursive: true,
864
- force: true
875
+ const xml_to_tidy = markdown.match(/```xml[\r\n](\s|.)*?```/g);
876
+ if (xml_to_tidy && xml_to_tidy.length > 0) {
877
+ for (let i = 0; i < xml_to_tidy.length; i++) {
878
+ if (xml_to_tidy[i] !== "") {
879
+ const xml_tidy = xml_to_tidy[i]
880
+ .replace("```xml", "")
881
+ .replace("```", "");
882
+ let new_xml_string = xml_tidy;
883
+ try {
884
+ new_xml_string = xmlFormat(xml_tidy, {
885
+ indentation: " ",
886
+ collapseContent: true,
887
+ lineSeparator: "\n",
865
888
  });
889
+ } catch (e) {
890
+ console.log(`[WARNING] Could not tidy XML in file [${file}]: ${e}`);
891
+ }
892
+ markdown = markdown.replace(
893
+ xml_to_tidy[i],
894
+ "```xml\n" + new_xml_string + "\n```"
895
+ );
866
896
  }
867
- fs.mkdirSync(work_path);
897
+ }
898
+ }
899
+ return markdown;
900
+ };
901
+
902
+ const process_doc_header = function (fm_headers, doc_path, template, h1) {
903
+ let wip_doc_header = template;
904
+ let used_h1 = false;
905
+ if (h1 && h1 !== "") {
906
+ wip_doc_header = wip_doc_header.replaceAll("{{title}}", h1);
907
+ used_h1 = true;
908
+ }
909
+ // Process fm_headers properties first
910
+ for (let i = 0; i < fm_headers.length; i++) {
911
+ switch (fm_headers[i].id) {
912
+ case "title":
913
+ if (!used_h1)
914
+ wip_doc_header = wip_doc_header.replaceAll(
915
+ "{{title}}",
916
+ fm_headers[i].value
917
+ );
918
+ break;
919
+ case "reading-time":
920
+ wip_doc_header = wip_doc_header.replaceAll(
921
+ "{{reading-time}}",
922
+ fm_headers[i].value
923
+ );
924
+ break;
925
+ case "contributor-count":
926
+ wip_doc_header = wip_doc_header.replaceAll(
927
+ "{{contributor-count}}",
928
+ fm_headers[i].value
929
+ );
930
+ break;
931
+ case "type":
932
+ wip_doc_header = wip_doc_header.replaceAll(
933
+ "{{doc-type}}",
934
+ fm_headers[i].value
935
+ );
936
+ break;
937
+ case "edit-path":
938
+ wip_doc_header = wip_doc_header.replaceAll(
939
+ "{{edit-url}}",
940
+ fm_headers[i].value
941
+ );
942
+ break;
943
+ case "last-commit":
944
+ let last_commit_date = fm_headers[i].value;
945
+ if (last_commit_date !== "No Commit Date Available") {
946
+ last_commit_date = new Date(fm_headers[i].value).toDateString();
947
+ }
948
+ wip_doc_header = wip_doc_header.replaceAll(
949
+ "{{last-update}}",
950
+ last_commit_date
951
+ );
952
+ break;
953
+ }
954
+ }
868
955
 
869
- // Copy files from book into _work-doc_id folder
870
- console.log(`Copying content into work folder...`);
871
- try {
872
- fs.copySync(path.join(source_path, doc_id), work_path_content);
873
- } catch (e) {
874
- console.error('Error copying from source_path:\n', e);
875
- process.exit(1);
956
+ // Now sort out breadcrumbs
957
+ const logical_path = doc_path.replace(path.extname(doc_path), "");
958
+ const bc_for_path = bc[logical_path];
959
+ let bc_tags = "\n";
960
+ if (bc_for_path) {
961
+ for (let i = 0; i < bc_for_path.length - 1; i++) {
962
+ let bc_link = "/";
963
+ if (redirects[bc_for_path[i].link]) {
964
+ if (redirects[bc_for_path[i].link].location) {
965
+ bc_link += redirects[bc_for_path[i].link].location;
966
+ }
967
+ } else {
968
+ if (bc_for_path[i].link) {
969
+ bc_link = bc_for_path[i].link.startsWith("/")
970
+ ? bc_for_path[i].link
971
+ : `/${bc_for_path[i].link}`;
972
+ } else {
973
+ bc_link = "";
974
+ }
975
+ }
976
+ if (bc_link !== "") {
977
+ bc_tags += `\t\t\t\t<li class="mt-0 nav-bar-item"><a href="${bc_link}" class="ps-0 pe-0 text-decoration-none">${bc_for_path[i].text}</a></li>\n`;
978
+ } else {
979
+ bc_tags += `\t\t\t\t<li class="mt-0 nav-bar-item">${bc_for_path[i].text}</li>\n`;
876
980
  }
981
+ }
982
+ } else {
983
+ if (verbose) {
984
+ console.log(
985
+ `[WARNING] Path is not present in navigation items: ${logical_path}`
986
+ );
987
+ }
988
+ }
989
+ bc_tags += "\t\t\t";
990
+ wip_doc_header = wip_doc_header.replaceAll("{{breadcrumbs}}", bc_tags);
991
+ return wip_doc_header;
992
+ };
993
+
994
+ // File callback for build scan
995
+ const build_file_callback = function (element) {
996
+ if (element.extension === "md") {
997
+ element.hb_source_path = path.join(
998
+ global_source_path,
999
+ element.relativePath
1000
+ );
1001
+ const fstats = fs.statSync(element.hb_source_path);
1002
+ element.hb_lastmod = `${fstats.mtime.toISOString().slice(0, 19)}Z`;
1003
+ md_files.push(element);
1004
+ } else {
1005
+ // File is html, see if there's a matching md file and if there is then ignore the html
1006
+ const md_path = element.path.replace(path.extname(element.path), ".md");
1007
+ if (fs.existsSync(md_path)) {
1008
+ return;
1009
+ }
1010
+ element.hb_source_path = path.join(
1011
+ global_source_path,
1012
+ element.relativePath
1013
+ );
1014
+ const fstats = fs.statSync(element.hb_source_path);
1015
+ element.hb_lastmod = `${fstats.mtime.toISOString().slice(0, 19)}Z`;
1016
+ static_html_files.push(element);
1017
+ }
1018
+ };
1019
+
1020
+ // File & folder callback for MD5 hash of built content
1021
+ const hash_callback = function (element) {
1022
+ if (element.extension !== "db") {
1023
+ built_file_hashes.push({
1024
+ path: element.relativePath,
1025
+ hash: element.hash,
1026
+ });
1027
+ }
1028
+ };
1029
+
1030
+ // File scan callback for filename validation
1031
+ const filename_validation_callback = function (element) {
1032
+ if (element.relativePath.startsWith('_inline/')) return;
1033
+ if (element.name === 'article_ext.md' || element.name === 'description_ext.md') return;
1034
+ const file_no_ext = element.name.replace(`.${element.extension}`, '');
1035
+ if (!file_no_ext.match(regex_filename)) errors_filename.push(element.relativePath);
1036
+ };
1037
+
1038
+ const dreeOptions = {
1039
+ hash: true,
1040
+ extensions: ["md", "html", "htm"],
1041
+ normalize: true,
1042
+ stat: true,
1043
+ };
1044
+
1045
+ const dreeOptionsAllFiles = {
1046
+ descendants: true,
1047
+ excludeEmptyDirectories: true,
1048
+ hash: false,
1049
+ normalize: true,
1050
+ size: false,
1051
+ sizeInBytes: false,
1052
+ symbolicLinks: false,
1053
+ };
1054
+
1055
+ const md5DreeOptions = {
1056
+ hash: true,
1057
+ normalize: true,
1058
+ sorted: true,
1059
+ };
1060
+
1061
+ exports.run = async function (
1062
+ source_path,
1063
+ verbose_output,
1064
+ github_api_token,
1065
+ validate,
1066
+ gen_exclude,
1067
+ build_version = ""
1068
+ ) {
1069
+ if (github_api_token !== "") {
1070
+ git_token = github_api_token;
1071
+ }
1072
+ global_source_path = source_path;
1073
+ verbose = verbose_output;
1074
+
1075
+ const start_time = Date.now();
1076
+ // GERRY: The purpose of this function is to create a zip file containing the hdocbook content,
1077
+ // * Create a _work folder
1078
+ // * copy the hdocbook content to the work folder
1079
+ // * Render all markdown into side-by-side HTML file
1080
+ // * Replace SERVER_VARS embedded in documents with the right version information etc.
1081
+ // * Build an index (sqlite FTS5) by extracting text from all HTML content in the work
1082
+ // folder, conceptually we are making a little mini website crawler to index all of the content
1083
+ // within the book.
1084
+ // * Package everything up into a ZIP file, ready for the build controller to package and publish
1085
+
1086
+ console.log("Hornbill HDocBook Build", "\n");
1087
+ console.log(" Document Path:", source_path, "\n");
1088
+ const build_start_dt = new Date().toLocaleString();
1089
+
1090
+ // Load the hdocbook-project.json file to get the docId
1091
+ // use the docId to get the book config
1092
+ const hdocbook_project_config_path = path.join(
1093
+ source_path,
1094
+ "hdocbook-project.json"
1095
+ );
1096
+ try {
1097
+ hdocbook_project = require(hdocbook_project_config_path);
1098
+ } catch (e) {
1099
+ console.log("File not found: hdocbook-project.json\n");
1100
+ console.log(
1101
+ "hdoc build/validate needs to be run in the root of a HDoc Book.\n"
1102
+ );
1103
+ process.exit(1);
1104
+ }
1105
+ doc_id = hdocbook_project.docId;
1106
+
1107
+ if (
1108
+ !validate &&
1109
+ hdocbook_project.pdfGeneration !== undefined &&
1110
+ hdocbook_project.pdfGeneration.enable !== undefined
1111
+ ) {
1112
+ pdf_enable = hdocbook_project.pdfGeneration.enable;
1113
+ }
877
1114
 
878
- // Create MD5 hash of content before build
879
- console.log(`Creating Hash...`);
1115
+ if (
1116
+ hdocbook_project.redirects &&
1117
+ hdocbook_project.redirects instanceof Array
1118
+ ) {
1119
+ for (let i = 0; i < hdocbook_project.redirects.length; i++) {
1120
+ const redirect_key =
1121
+ hdocbook_project.redirects[i].url.indexOf("/") == 0
1122
+ ? hdocbook_project.redirects[i].url.substring(1)
1123
+ : hdocbook_project.redirects[i].url;
1124
+ redirects[redirect_key] = hdocbook_project.redirects[i];
1125
+ }
1126
+ }
880
1127
 
881
- dree.scan(work_path_content, md5DreeOptions, hash_callback);
882
- let concat_hash = '|';
883
- for (let i = 0; i < built_file_hashes.length; i++) {
884
- concat_hash += built_file_hashes[i].path + ':' + built_file_hashes[i].hash + '|';
885
- }
886
- if (concat_hash === '|') {
887
- console.log('No hash of content has been returned.');
888
- process.exit(1);
889
- }
1128
+ console.log(`Loading hdocbook config...`);
1129
+
1130
+ const book_path = path.join(source_path, doc_id),
1131
+ hdocbook_path = path.join(book_path, "hdocbook.json"),
1132
+ work_path = path.join(source_path, "_work"),
1133
+ work_hdocbook_path = path.join(work_path, doc_id, "hdocbook.json");
1134
+
1135
+ hdocbook_config = require(hdocbook_path);
1136
+ if (build_version !== "") {
1137
+ if (build_version.match(regex_version)) {
1138
+ hdocbook_config.version = build_version;
1139
+ } else {
1140
+ console.log(
1141
+ `\n[WARNING] Argument build version [${build_version}] does not match expected pattern, defaulting to version specified in book [${hdocbook_config.version}]\n`
1142
+ );
1143
+ }
1144
+ }
890
1145
 
891
- // Create hash and write file
892
- const hash = crypto.createHash("md5").update(concat_hash).digest("hex");
893
- const checksum_path = path.join(work_path_content, 'checksum.md5');
894
- try {
895
- fs.writeFileSync(checksum_path, hash);
896
- console.log('Hash file creation success:', checksum_path);
897
- } catch (e) {
898
- console.log('\nError creating', checksum_path, ':', e);
899
- process.exit(1);
900
- }
1146
+ if (!hdocbook_config.version.match(regex_version)) {
1147
+ console.log(
1148
+ `ERROR: Version number does not match required format - ${hdocbook_config.version}\n`
1149
+ );
1150
+ process.exit(1);
1151
+ }
901
1152
 
902
- // Load document header templates
903
- console.log(`Loading templates...`);
1153
+ if (
1154
+ hdocbook_config.publicSource &&
1155
+ hdocbook_config.publicSource.endsWith(".git")
1156
+ )
1157
+ hdocbook_config.publicSource = hdocbook_config.publicSource.substring(
1158
+ 0,
1159
+ hdocbook_config.publicSource.length - 4
1160
+ );
1161
+
1162
+ console.log(`Loading product families...`);
1163
+ const prods = await hdoc.load_product_families();
1164
+ if (!prods.success) {
1165
+ console.log(`${prods.errors}\n`);
1166
+ process.exit(1);
1167
+ } else {
1168
+ prod_families = prods.prod_families;
1169
+ prods_supported = prods.prods_supported;
1170
+ }
1171
+
1172
+ if (!validate) {
1173
+ console.log("Caching CSS for PDF generation...");
1174
+ const css_files = [
1175
+ path.join(pdf_template_path, "css", "custom-block.css"),
1176
+ path.join(pdf_template_path, "css", "hdocs-pdf.css"),
1177
+ path.join(pdf_template_path, "css", "vars.css"),
1178
+ ];
1179
+ for (let i = 0; i < css_files.length; i++) {
904
1180
  try {
905
- doc_header_template = fs.readFileSync(doc_header_template_path, 'utf8');
906
- doc_header_template_non_git = fs.readFileSync(non_git_doc_header_template_path, 'utf8');
907
- pdf_header_template = fs.readFileSync(pdf_header_template_path, 'utf8');
908
- pdf_header_template_non_git = fs.readFileSync(non_git_pdf_header_template_path, 'utf8');
909
- } catch (err) {
910
- console.log(`Error reading document header template: ${err}`);
911
- process.exit(1);
1181
+ css_templates.push(fs.readFileSync(css_files[i], "utf8"));
1182
+ } catch (e) {
1183
+ console.log(`Error reading file[${css_files[i]}]: ${e}`);
912
1184
  }
1185
+ }
1186
+ }
913
1187
 
914
- if (pdf_enable) {
915
- // Load PDF templates
916
- try {
917
- pdf_template = fs.readFileSync(pdf_template_file_path, 'utf8');
918
- } catch (err) {
919
- console.log(`Error reading PDF template: ${err}`);
920
- process.exit(1);
921
- }
922
- }
923
- console.log(`Processing navigation breadcrumbs...`);
924
- const bc_build = hdoc.build_breadcrumbs(hdocbook_config.navigation.items);
925
- if (bc_build.errors.length > 0) {
926
- console.log('\r\n-----------------------');
927
- console.log(' Validation Output ');
928
- console.log('-----------------------');
929
- console.log(`\n${bc_build.errors.length} errors found when processing navigation:\n`);
930
- console.log(` - ${bc_build.errors.join('\n\n - ')}`);
931
- console.log('\n');
932
- process.exit(1);
933
- }
934
- bc = bc_build.bc;
935
- console.log(`Processing content...`);
936
- // Get a list of MD files in work_path
937
- dree.scan(work_path, dreeOptions, build_file_callback);
938
-
939
- if (pdf_enable) {
940
- // Create a Chromium browser instance generate PDFs with
941
- browser = await puppeteer.launch({ headless: 'new' });
942
- }
1188
+ // Validate all filenames first
1189
+ console.log(`Validating book filenames meet kebab-case requirements...`);
1190
+ dree.scan(book_path, dreeOptionsAllFiles, filename_validation_callback);
1191
+ if (errors_filename.length > 0) {
1192
+ console.log("\r\n-----------------------");
1193
+ console.log(" Validation Output ");
1194
+ console.log("-----------------------");
1195
+ console.log(`${errors_filename.length} files do not meet filename requirements:`);
1196
+ console.log(` - ${errors_filename.join("\n - ")}`);
1197
+ console.log()
1198
+ process.exit(1);
1199
+ }
943
1200
 
944
- // Work through MD files and convert to HTML
945
- let mdPromiseArray = [];
946
- for (let i = 0; i < md_files.length; i++) {
947
- mdPromiseArray.push(md_files[i]);
948
- }
949
- const chunkSize = 8;
950
- for (let i = 0; i < mdPromiseArray.length; i += chunkSize) {
951
- const chunk = mdPromiseArray.slice(i, i + chunkSize);
952
- // do whatever
953
- await Promise.all(chunk.map(async (file) => {
954
- await transform_markdown_and_save_html(file);
955
- }));
956
- }
1201
+ console.log(`Building: ${doc_id} v${hdocbook_config.version}...\n`);
957
1202
 
1203
+ // Make _work folder to copy everything into
1204
+ work_path_content = path.join(work_path, doc_id);
1205
+ if (fs.existsSync(work_path)) {
1206
+ fs.rmSync(work_path, {
1207
+ recursive: true,
1208
+ force: true,
1209
+ });
1210
+ }
1211
+ fs.mkdirSync(work_path);
1212
+
1213
+ // Copy files from book into _work-doc_id folder
1214
+ console.log(`Copying content into work folder...`);
1215
+ try {
1216
+ fs.copySync(path.join(source_path, doc_id), work_path_content);
1217
+ } catch (e) {
1218
+ console.error("Error copying from source_path:\n", e);
1219
+ process.exit(1);
1220
+ }
958
1221
 
1222
+ // Create MD5 hash of content before build
1223
+ console.log(`Creating Hash...`);
959
1224
 
960
- // Work through Static HTML files and add Frontmatter tags
961
- let htmlPromiseArray = [];
962
- for (let i = 0; i < static_html_files.length; i++) {
963
- htmlPromiseArray.push(static_html_files[i]);
964
- }
965
- for (let i = 0; i < htmlPromiseArray.length; i += chunkSize) {
966
- const chunk = htmlPromiseArray.slice(i, i + chunkSize);
967
- await Promise.all(chunk.map(async (file) => {
968
- await transform_static_html(file);
969
- }));
970
- }
1225
+ dree.scan(work_path_content, md5DreeOptions, hash_callback);
1226
+ let concat_hash = "|";
1227
+ for (let i = 0; i < built_file_hashes.length; i++) {
1228
+ concat_hash +=
1229
+ built_file_hashes[i].path + ":" + built_file_hashes[i].hash + "|";
1230
+ }
1231
+ if (concat_hash === "|") {
1232
+ console.log("No hash of content has been returned.");
1233
+ process.exit(1);
1234
+ }
971
1235
 
972
- if (pdf_enable) {
973
- // Close the Chromium browser instance
974
- await browser.close();
975
- }
1236
+ // Create hash and write file
1237
+ const hash = crypto.createHash("md5").update(concat_hash).digest("hex");
1238
+ const checksum_path = path.join(work_path_content, "checksum.md5");
1239
+ try {
1240
+ fs.writeFileSync(checksum_path, hash);
1241
+ console.log("Hash file creation success:", checksum_path);
1242
+ } catch (e) {
1243
+ console.log("\nError creating", checksum_path, ":", e);
1244
+ process.exit(1);
1245
+ }
976
1246
 
977
- // Output to console
978
- console.log(`\n MD files found: ${conversion_attempted}`);
979
- console.log(`Successfully converted to HTML: ${conversion_success}`);
980
- console.log(` Failed to convert: ${conversion_failed}\n`);
981
- console.log(` Includes Found: ${includes_found}`);
982
- console.log(` Includes Success: ${includes_success}`);
983
- console.log(` Includes Failed: ${includes_failed}\n`);
984
- console.log(` Static HTML Files Found: ${static_html_files.length}\n`);
985
- if (!validate) {
986
- console.log(` PDF Files Created: ${pdf_created}\n`);
987
- }
1247
+ // Load document header templates
1248
+ console.log(`Loading templates...`);
1249
+ try {
1250
+ doc_header_template = fs.readFileSync(doc_header_template_path, "utf8");
1251
+ doc_header_template_non_git = fs.readFileSync(
1252
+ non_git_doc_header_template_path,
1253
+ "utf8"
1254
+ );
1255
+ pdf_header_template = fs.readFileSync(pdf_header_template_path, "utf8");
1256
+ pdf_header_template_non_git = fs.readFileSync(
1257
+ non_git_pdf_header_template_path,
1258
+ "utf8"
1259
+ );
1260
+ } catch (err) {
1261
+ console.log(`Error reading document header template: ${err}`);
1262
+ process.exit(1);
1263
+ }
988
1264
 
989
- // Validate content
990
- const validation_success = await hdoc_validate.run(work_path, doc_id, verbose, hdocbook_config, hdocbook_project, bc, prod_families, prods_supported, gen_exclude, redirects);
991
- if (!validation_success) {
992
- const end_time = Date.now();
993
- console.log(`\nTime Taken: ${get_duration(start_time, end_time)}\n`);
994
- process.exit(1);
995
- }
1265
+ if (pdf_enable) {
1266
+ // Load PDF templates
1267
+ try {
1268
+ pdf_template = fs.readFileSync(pdf_template_file_path, "utf8");
1269
+ } catch (err) {
1270
+ console.log(`Error reading PDF template: ${err}`);
1271
+ process.exit(1);
1272
+ }
1273
+ }
1274
+ console.log(`Processing navigation breadcrumbs...`);
1275
+ const bc_build = hdoc.build_breadcrumbs(hdocbook_config.navigation.items);
1276
+ if (bc_build.errors.length > 0) {
1277
+ console.log("\r\n-----------------------");
1278
+ console.log(" Validation Output ");
1279
+ console.log("-----------------------");
1280
+ console.log(
1281
+ `\n${bc_build.errors.length} errors found when processing navigation:\n`
1282
+ );
1283
+ console.log(` - ${bc_build.errors.join("\n\n - ")}`);
1284
+ console.log("\n");
1285
+ process.exit(1);
1286
+ }
1287
+ bc = bc_build.bc;
1288
+ console.log(`Processing content...`);
1289
+ // Get a list of MD files in work_path
1290
+ dree.scan(work_path, dreeOptions, build_file_callback);
1291
+
1292
+ if (pdf_enable) {
1293
+ // Create a Chromium browser instance generate PDFs with
1294
+ browser = await puppeteer.launch({ headless: "new" });
1295
+ }
996
1296
 
997
- // Delete markdown files
998
- console.log(`Performing Markdown Cleanup`);
1297
+ // Work through MD files and convert to HTML
1298
+ let mdPromiseArray = [];
1299
+ for (let i = 0; i < md_files.length; i++) {
1300
+ mdPromiseArray.push(md_files[i]);
1301
+ }
1302
+ const chunkSize = 8;
1303
+ for (let i = 0; i < mdPromiseArray.length; i += chunkSize) {
1304
+ const chunk = mdPromiseArray.slice(i, i + chunkSize);
1305
+ // do whatever
1306
+ await Promise.all(
1307
+ chunk.map(async (file) => {
1308
+ await transform_markdown_and_save_html(file);
1309
+ })
1310
+ );
1311
+ }
999
1312
 
1000
- let filePromiseArray = [];
1001
- for (let i = 0; i < md_files_delete.length; i++) {
1002
- filePromiseArray.push(md_files_delete[i]);
1003
- }
1004
- await Promise.all(filePromiseArray.map(async (file) => {
1005
- fs.unlink(file, (err => {
1006
- if (err) console.log(`Error deleting ${file}: ${e}`);
1007
- }));
1008
- }));
1009
-
1010
- // Add book read timing to the hdocbook.json
1011
- hdocbook_config.readingTime = Math.ceil(book_read_time + ((book_read_time / 100) * 10));
1012
- hdocbook_config.navigation.items = hdoc.strip_drafts(hdocbook_config.navigation.items);
1013
- try {
1014
- fs.writeFileSync(work_hdocbook_path, JSON.stringify(hdocbook_config, null, 2));
1015
- console.log('\nhdocbook.json update success:', work_hdocbook_path);
1016
- } catch (e) {
1017
- console.log('\nError creating', work_hdocbook_path, ':', e);
1018
- process.exit(1);
1019
- }
1313
+ // Work through Static HTML files and add Frontmatter tags
1314
+ let htmlPromiseArray = [];
1315
+ for (let i = 0; i < static_html_files.length; i++) {
1316
+ htmlPromiseArray.push(static_html_files[i]);
1317
+ }
1318
+ for (let i = 0; i < htmlPromiseArray.length; i += chunkSize) {
1319
+ const chunk = htmlPromiseArray.slice(i, i + chunkSize);
1320
+ await Promise.all(
1321
+ chunk.map(async (file) => {
1322
+ await transform_static_html(file);
1323
+ })
1324
+ );
1325
+ }
1020
1326
 
1021
- // Build the index
1022
- // Create the DB and tables
1023
- console.log(`Building the Index`);
1024
- let db = hdoc_build_db.create_db(work_path, doc_id);
1025
- if (db.error && db.error !== null) {
1026
- console.log(db.error);
1027
- process.exit(1);
1028
- }
1029
- // Populate primary index tables
1030
- const index = await hdoc_build_db.populate_index(db.db, doc_id, hdocbook_config, index_records, verbose);
1031
- if (!index.success) {
1032
- console.log(index.error);
1033
- process.exit(1);
1034
- }
1327
+ if (pdf_enable) {
1328
+ // Close the Chromium browser instance
1329
+ await browser.close();
1330
+ }
1035
1331
 
1036
- // Populate redirect index table records
1037
- if (hdocbook_project.redirects && hdocbook_project.redirects instanceof Array && hdocbook_project.redirects.length > 0) {
1038
- const redirects_index = hdoc_build_db.populate_redirects(db.db, hdocbook_project.redirects, verbose);
1039
- if (!redirects_index.success) {
1040
- for (let i = 0; i < index.errors.length; i++) {
1041
- console.log(index.errors[i]);
1042
- }
1043
- process.exit(1);
1044
- }
1045
- }
1332
+ // Output to console
1333
+ console.log(`\n MD files found: ${conversion_attempted}`);
1334
+ console.log(`Successfully converted to HTML: ${conversion_success}`);
1335
+ console.log(` Failed to convert: ${conversion_failed}\n`);
1336
+ console.log(` Includes Found: ${includes_found}`);
1337
+ console.log(` Includes Success: ${includes_success}`);
1338
+ console.log(` Includes Failed: ${includes_failed}\n`);
1339
+ console.log(
1340
+ ` Static HTML Files Found: ${static_html_files.length}\n`
1341
+ );
1342
+ if (!validate) {
1343
+ console.log(` PDF Files Created: ${pdf_created}\n`);
1344
+ }
1046
1345
 
1047
- if (!validate) {
1048
- try {
1049
- const zip_path = path.join(work_path, doc_id + '.zip');
1346
+ // Validate content
1347
+ const validation_success = await hdoc_validate.run(
1348
+ work_path,
1349
+ doc_id,
1350
+ verbose,
1351
+ hdocbook_config,
1352
+ hdocbook_project,
1353
+ bc,
1354
+ prod_families,
1355
+ prods_supported,
1356
+ gen_exclude,
1357
+ redirects
1358
+ );
1359
+ if (!validation_success) {
1360
+ const end_time = Date.now();
1361
+ console.log(`\nTime Taken: ${get_duration(start_time, end_time)}\n`);
1362
+ process.exit(1);
1363
+ }
1050
1364
 
1051
- var output = fs.createWriteStream(zip_path);
1052
- var archive = archiver('zip');
1053
- archive.on('error', function(err){
1054
- throw err;
1055
- });
1056
- archive.pipe(output);
1365
+ // Delete markdown files
1366
+ console.log(`Performing Markdown Cleanup`);
1057
1367
 
1058
- // append files from a sub-directory, putting its contents at the root of archive
1059
- archive.directory(work_path_content, false);
1060
- archive.finalize();
1368
+ let filePromiseArray = [];
1369
+ for (let i = 0; i < md_files_delete.length; i++) {
1370
+ filePromiseArray.push(md_files_delete[i]);
1371
+ }
1372
+ await Promise.all(
1373
+ filePromiseArray.map(async (file) => {
1374
+ fs.unlink(file, (err) => {
1375
+ if (err) console.log(`Error deleting ${file}: ${e}`);
1376
+ });
1377
+ })
1378
+ );
1379
+
1380
+ // Add book read timing to the hdocbook.json
1381
+ hdocbook_config.readingTime = Math.ceil(
1382
+ book_read_time + (book_read_time / 100) * 10
1383
+ );
1384
+ hdocbook_config.navigation.items = hdoc.strip_drafts(
1385
+ hdocbook_config.navigation.items
1386
+ );
1387
+ try {
1388
+ fs.writeFileSync(
1389
+ work_hdocbook_path,
1390
+ JSON.stringify(hdocbook_config, null, 2)
1391
+ );
1392
+ console.log("\nhdocbook.json update success:", work_hdocbook_path);
1393
+ } catch (e) {
1394
+ console.log("\nError creating", work_hdocbook_path, ":", e);
1395
+ process.exit(1);
1396
+ }
1061
1397
 
1062
- //await zip(work_path_content, zip_path);
1063
- console.log(`\nZIP Creation Success: ${zip_path}\n`);
1064
- console.log(' Build Started:', build_start_dt);
1065
- console.log(`Build Completed: ${new Date().toLocaleString()}\n`);
1398
+ // Build the index
1399
+ // Create the DB and tables
1400
+ console.log(`Building the Index`);
1401
+ let db = hdoc_build_db.create_db(work_path, doc_id);
1402
+ if (db.error && db.error !== null) {
1403
+ console.log(db.error);
1404
+ process.exit(1);
1405
+ }
1406
+ // Populate primary index tables
1407
+ const index = await hdoc_build_db.populate_index(
1408
+ db.db,
1409
+ doc_id,
1410
+ hdocbook_config,
1411
+ index_records,
1412
+ verbose
1413
+ );
1414
+ if (!index.success) {
1415
+ console.log(index.error);
1416
+ process.exit(1);
1417
+ }
1066
1418
 
1067
- } catch (e) {
1068
- console.log('\nError creating ZIP: ' + e);
1069
- }
1070
- } else {
1071
- console.log('\nValidation Complete\n');
1072
- }
1073
- const end_time = Date.now();
1074
- console.log(`Time Taken: ${get_duration(start_time, end_time)}\n`);
1075
- };
1076
-
1077
- const get_duration = function (start, end) {
1078
- const total_time = new Date(end - start).toISOString().slice(11, 19);
1079
- const duration_arr = total_time.split(':');
1080
- let duration = '';
1081
- if (parseInt(duration_arr[0], 10) > 0) {
1082
- duration += parseInt(duration_arr[0], 10) + 'h ';
1419
+ // Populate redirect index table records
1420
+ if (
1421
+ hdocbook_project.redirects &&
1422
+ hdocbook_project.redirects instanceof Array &&
1423
+ hdocbook_project.redirects.length > 0
1424
+ ) {
1425
+ const redirects_index = hdoc_build_db.populate_redirects(
1426
+ db.db,
1427
+ hdocbook_project.redirects,
1428
+ verbose
1429
+ );
1430
+ if (!redirects_index.success) {
1431
+ for (let i = 0; i < index.errors.length; i++) {
1432
+ console.log(index.errors[i]);
1083
1433
  }
1084
- if (duration !== '' || parseInt(duration_arr[1], 10)) {
1085
- duration += parseInt(duration_arr[1], 10) + 'm ';
1086
- }
1087
- duration += parseInt(duration_arr[2], 10) + 's';
1088
- return duration;
1434
+ process.exit(1);
1435
+ }
1436
+ }
1437
+
1438
+ if (!validate) {
1439
+ try {
1440
+ const zip_path = path.join(work_path, doc_id + ".zip");
1441
+
1442
+ var output = fs.createWriteStream(zip_path);
1443
+ var archive = archiver("zip");
1444
+ archive.on("error", function (err) {
1445
+ throw err;
1446
+ });
1447
+ archive.pipe(output);
1448
+
1449
+ // append files from a sub-directory, putting its contents at the root of archive
1450
+ archive.directory(work_path_content, false);
1451
+ archive.finalize();
1452
+
1453
+ //await zip(work_path_content, zip_path);
1454
+ console.log(`\nZIP Creation Success: ${zip_path}\n`);
1455
+ console.log(" Build Started:", build_start_dt);
1456
+ console.log(`Build Completed: ${new Date().toLocaleString()}\n`);
1457
+ } catch (e) {
1458
+ console.log("\nError creating ZIP: " + e);
1459
+ }
1460
+ } else {
1461
+ console.log("\nValidation Complete\n");
1462
+ }
1463
+ const end_time = Date.now();
1464
+ console.log(`Time Taken: ${get_duration(start_time, end_time)}\n`);
1465
+ };
1466
+
1467
+ const get_duration = function (start, end) {
1468
+ const total_time = new Date(end - start).toISOString().slice(11, 19);
1469
+ const duration_arr = total_time.split(":");
1470
+ let duration = "";
1471
+ if (parseInt(duration_arr[0], 10) > 0) {
1472
+ duration += parseInt(duration_arr[0], 10) + "h ";
1473
+ }
1474
+ if (duration !== "" || parseInt(duration_arr[1], 10)) {
1475
+ duration += parseInt(duration_arr[1], 10) + "m ";
1089
1476
  }
1477
+ duration += parseInt(duration_arr[2], 10) + "s";
1478
+ return duration;
1479
+ };
1090
1480
  })();