markdown_link_checker_sc 0.0.13 → 0.0.116

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +66 -12
  2. package/biglog.txt +0 -0
  3. package/index.js +181 -435
  4. package/package.json +6 -1
  5. package/src/errors.js +148 -0
  6. package/src/helpers.js +41 -0
  7. package/src/links.js +194 -0
  8. package/src/output_errors.js +49 -0
  9. package/src/process_image_orphans.js +97 -0
  10. package/src/process_internal_url_links.js +20 -0
  11. package/src/process_local_image_links.js +57 -0
  12. package/src/process_markdown.js +400 -0
  13. package/src/process_orphans.js +145 -0
  14. package/src/process_relative_links.js +116 -0
  15. package/src/shared_data.js +2 -0
  16. package/src/slugify.js +17 -0
  17. package/tests/errortype/current_file_missing_anchor/heading_present_for_anchor.md +13 -0
  18. package/tests/errortype/current_file_missing_anchor/missing_heading.md +5 -0
  19. package/tests/errortype/linked_file_missing_anchor/file_with_broken_heading_link.md +10 -0
  20. package/tests/errortype/linked_file_missing_anchor/file_without_heading.md +10 -0
  21. package/tests/errortype/linked_internal_file_html/file_exists.html +5 -0
  22. package/tests/errortype/linked_internal_file_html/file_exists_as_markdown.md +6 -0
  23. package/tests/errortype/linked_internal_file_html/links_to_file_that_is_html.md +10 -0
  24. package/tests/errortype/linked_internal_file_missing/file_present_relative_link_no_error.md +5 -0
  25. package/tests/errortype/linked_internal_file_missing/file_present_should_be_no_error.md +5 -0
  26. package/tests/errortype/linked_internal_file_missing/links_to_file_that_is_not_present.md +8 -0
  27. package/tests/errortype/local_image_not_found/page_with_missing_image.md +9 -0
  28. package/tests/errortype/local_image_not_found/test.png +0 -0
  29. package/tests/errortype/orphan_images/assets/image1_not_linked.png +0 -0
  30. package/tests/errortype/orphan_images/assets/image2_not_linked.png +0 -0
  31. package/tests/errortype/orphan_images/test/image3_not_linked.png +0 -0
  32. package/tests/errortype/orphan_images/test/image4_linked.png +0 -0
  33. package/tests/errortype/orphan_images/test/intro.md +11 -0
  34. package/tests/errortype/page_not_in_toc/page1intoc_should_not_error.md +7 -0
  35. package/tests/errortype/page_not_in_toc/page2intoc_should_not_error.md +7 -0
  36. package/tests/errortype/page_not_in_toc/page3NOTinTOC.md +7 -0
  37. package/tests/errortype/page_not_in_toc/toc.md +9 -0
  38. package/tests/errortype/url_to_local_site/mylocalsite_dot_com.md +8 -0
  39. package/tests/links/tests1.md +93 -0
  40. package/tests/links/tests2.md +3 -0
  41. package/tests/tests1.md +0 -42
package/index.js CHANGED
@@ -1,17 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require("fs");
4
- const path = require("path");
5
- const { program } = require("commander");
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { sharedData } from "./src/shared_data.js";
6
+ //const path = require("path");
7
+ import { program } from "commander";
8
+ //const { program } = require("commander");
9
+ import { logToFile, isMarkdown, isHTML, isImage } from "./src/helpers.js";
10
+ import { outputErrors } from "./src/output_errors.js";
11
+
12
+ import { slugifyVuepress } from "./src/slugify.js";
13
+ import { processMarkdown } from "./src/process_markdown.js";
14
+ import { processRelativeLinks } from "./src/process_relative_links.js";
15
+ import { checkLocalImageLinks } from "./src/process_local_image_links.js";
16
+ import { processUrlsToLocalSource } from "./src/process_internal_url_links.js";
17
+ import {
18
+ checkPageOrphans,
19
+ getPageWithMostLinks,
20
+ } from "./src/process_orphans.js";
21
+ import { checkImageOrphansGlobal } from "./src/process_image_orphans.js";
6
22
 
7
23
  program
8
24
  .option(
9
- "-d, --directory [directory]",
10
- "The directory to search for markdown and html files",
25
+ "-r, --root <path>",
26
+ "Root directory of your source (i.e. root of github repo). Use -d as well to specify a folder if docs are not in the root, or to just run on particular subfolder. Defaults to current directory.",
11
27
  process.cwd()
12
28
  )
13
29
  .option(
14
- "-s, --headingAnchorSlugify [value]",
30
+ "-d, --directory [directory]",
31
+ "The directory to search for markdown and html files, relative to root - such as: `en` for an English subfolder. Default empty (same as -r directory)",
32
+ ""
33
+ )
34
+ .option(
35
+ "-i, --imagedir [directory]",
36
+ "The directory to search for all image files for global orphan checking, relative to root - such as: `assets` or `en`. Default empty if not explicitly set, and global orphan checking will not be done",
37
+ ""
38
+ )
39
+ .option(
40
+ "-c, --headingAnchorSlugify [value]",
15
41
  "Slugify approach for turning markdown headings into heading anchors. Currently support vuepress only and always",
16
42
  "vuepress"
17
43
  )
@@ -20,36 +46,66 @@ program
20
46
  "Try a markdown file extension check if a link to HTML fails.",
21
47
  true
22
48
  )
23
- .option("-l, --log [value]", "Export some logs for debugging. ", false)
49
+ .option(
50
+ "-l, --log <types...>",
51
+ "Export logs for debugging. Types: allerrors, filterederrors, allresults etc."
52
+ )
24
53
  .option(
25
54
  "-f, --files <path>",
26
55
  "JSON file with array of files to report on (default is all files). Paths are relative relative to -d by default, but -r can be used to set a different root.",
27
56
  ""
28
57
  )
58
+
29
59
  .option(
30
- "-r, --root <path>",
31
- "Directory to prepend before file paths in the JSON directory. Default is same as directory. Useful if directory is not your repo root",
32
- ""
60
+ "-s, --toc [value]",
61
+ "full filename of TOC/Summary file in file system. If not specified, inferred from file with most links to other files"
33
62
  )
63
+ .option(
64
+ "-u, --site_url [value]",
65
+ "Site base url in form dev.example.com (used to catch absolute urls to local files)"
66
+ )
67
+
34
68
  .parse(process.argv);
35
69
 
36
70
  // TODO PX4 special parsing - errors or pages we exclude by default.
37
71
  // Particular error types on particular pages?
38
72
 
39
- const options = program.opts();
73
+ //const options = program.opts();
74
+ sharedData.options = program.opts();
75
+ sharedData.options.log ? null : (sharedData.options.log = []);
76
+ sharedData.allMarkdownFiles = new Set([]);
77
+ sharedData.allHTMLFiles = new Set([]);
78
+ sharedData.allImageFiles = new Set([]);
79
+ sharedData.allOtherFiles = new Set([]);
80
+
81
+
82
+ const markdownDirectory = path.join(sharedData.options.root, sharedData.options.directory);
83
+ if (sharedData.options.log == "fast") {
84
+ console.log(`MARKDOWN DIR ${markdownDirectory}`);
85
+ }
86
+
87
+ async () => {
88
+ // Load JSON file containing file paths and reassign as array to the JSON path
89
+ sharedData.options.files
90
+ ? (sharedData.options.files = await loadJSONFileToReportOn(sharedData.options.files))
91
+ : (sharedData.options.files = []);
92
+ if (sharedData.options.log == "quick") {
93
+ for (const file of sharedData.options.files) {
94
+ console.log(file);
95
+ }
96
+ }
97
+ };
40
98
 
41
99
  // Function for loading JSON file that contains files to report on
42
100
  async function loadJSONFileToReportOn(filePath) {
101
+ sharedData.options.log.includes("functions")
102
+ ? console.log(`Function: loadJSONFileToReportOn(): filePath: ${filePath}`)
103
+ : null;
43
104
  try {
44
105
  const fileContent = await fs.promises.readFile(filePath, "utf8");
45
106
  let filesArray = JSON.parse(fileContent);
46
- // Prepend the full path - either from root or directory
47
- if (options.root) {
48
- filesArray = filesArray.map((str) => path.join(options.root, str));
49
- } else {
50
- //
51
- filesArray = filesArray.map((str) => path.join(options.directory, str));
52
- }
107
+ // Array relative to root, so update to have full path
108
+ filesArray = filesArray.map((str) => path.join(sharedData.options.root, str));
53
109
 
54
110
  //console.log(filesArray);
55
111
  return filesArray;
@@ -59,193 +115,30 @@ async function loadJSONFileToReportOn(filePath) {
59
115
  }
60
116
  }
61
117
 
62
- const isMarkdown = (file) => path.extname(file).toLowerCase() === ".md";
63
- const isHtml = (file) => path.extname(file).toLowerCase() === ".html";
118
+
64
119
  const replaceDelimiter = (str, underscore) =>
65
120
  underscore ? str.replace(/\s+/g, "_") : str.replace(/\s+/g, "-");
66
121
 
67
- function slugifyVuepress(str) {
68
- const slug = str
69
- .toLowerCase()
70
- .replace(/\/+/g, "-") // replace / with hyphens
71
- .replace(/[^A-Za-z0-9/]+/g, "-") // replace non-word characters except / with hyphens
72
- .replace(/[\s_-]+/g, "-") // Replace spaces and underscores with hyphens
73
- .replace(/^-+|-+$/g, ""); // Remove extra hyphens from the beginning or end of the string
74
-
75
- if (str.includes("/")) {
76
- //console.log(`DEBUG: SLUG: str: ${str} slug: ${slug}`);
77
- }
78
- return `${slug}`;
79
- }
80
-
81
- const processHeading = (line, slugifyApproach) => {
82
- const matches = line.match(/^#+\s+(.+)$/);
83
- if (matches) {
84
- //slugifyApproach is currently only slugifyVuepress so we do no test.
85
- return slugifyVuepress(matches[1]);
86
- }
87
- return null;
88
- };
89
-
90
- const processMarkdownLink = (
91
- line,
92
- relativeLinks,
93
- relativeImageLinks,
94
- absoluteLinks,
95
- absoluteImageLinks,
96
- unHandledLinkTypes
97
- ) => {
98
- const matches = line.matchAll(/([!@]?)\[([^\]]+)\]\((\S+?)\)/g);
99
-
100
- // TODO - THIS matches @[youtube](gjHj6YsxcZk) valid link which is used for vuepress plugin URLs. We probably want to exclude it and deal with it separately
101
- // Maybe a backwards lookup on @
102
- // Not sure if we can generalize
103
-
104
- for (const match of matches) {
105
- const isMarkdownImageLink = match[1] == "!" ? true : false;
106
- const isVuepressYouTubeLink = match[1] == "@" ? true : false;
107
-
108
- const linkText = match[2];
109
- let linkUrl = match[3];
110
- const linkAnchorSplit = linkUrl.split("#");
111
- linkUrl = linkAnchorSplit[0].trim();
112
- const linkAnchor = linkAnchorSplit[1] ? linkAnchorSplit[1] : null;
113
-
114
- const link = { linkText, linkUrl, linkAnchor };
115
-
116
- if (isVuepressYouTubeLink) {
117
- if (linkUrl.startsWith("http")) {
118
- absoluteLinks.push(link);
119
- } else {
120
- unHandledLinkTypes.push(link); // Not going to handle this (yet)
121
- // TODO - prepend the standard URL
122
- }
123
- } else if (linkUrl.startsWith("http")) {
124
- isMarkdownImageLink
125
- ? absoluteImageLinks.push(link)
126
- : absoluteLinks.push(link);
127
- } else if (
128
- linkUrl.startsWith("ftp:") ||
129
- linkUrl.startsWith("ftps") ||
130
- linkUrl.startsWith("mailto")
131
- ) {
132
- // One of the types we specifically do not handle
133
- unHandledLinkTypes.push(link);
134
- } else if (
135
- linkUrl.endsWith(".png") ||
136
- linkUrl.endsWith(".jpg") ||
137
- linkUrl.endsWith(".jpeg") ||
138
- linkUrl.endsWith(".gif") ||
139
- linkUrl.endsWith(".webp")
140
- ) {
141
- //console.log("???Markdown");
142
- //Catch case where image link is inside
143
- relativeImageLinks.push(link);
144
- } else {
145
- isMarkdownImageLink
146
- ? relativeImageLinks.push(link)
147
- : relativeLinks.push(link);
148
- }
149
- }
150
-
151
- //Match for html img and a - append to the lists
152
- const regexHTMLLinks =
153
- /<(a|img)[^>]*(href|src)="([^"]+)"[^>]*(?:title="([^"]+)"|>([^<]+)<\/\1>)/gi;
154
-
155
- for (const match of line.matchAll(regexHTMLLinks)) {
156
- const isMarkdownImageLink = match[1] == "img" ? true : false;
157
- //const tagType = match[1];
158
- //const hrefOrSrc = match[2];
159
- let linkUrl = match[3];
160
- const linkText = match[4] || match[5] || "";
161
- const linkAnchorSplit = linkUrl.split("#");
162
- linkUrl = linkAnchorSplit[0];
163
- const linkAnchor = linkAnchorSplit[1] ? linkAnchorSplit[1] : null;
164
- const link = { linkText, linkUrl, linkAnchor };
165
-
166
- if (linkUrl.startsWith("http")) {
167
- isMarkdownImageLink
168
- ? absoluteImageLinks.push(link)
169
- : absoluteLinks.push(link);
170
- } else {
171
- isMarkdownImageLink
172
- ? relativeImageLinks.push(link)
173
- : relativeLinks.push(link);
174
- }
175
- }
176
-
177
- return {
178
- relativeLinks,
179
- absoluteLinks,
180
- absoluteImageLinks,
181
- relativeImageLinks,
182
- };
183
- };
184
-
185
- const processFile = async (file, slugifyApproach) => {
122
+ const processFile = async (file) => {
123
+ sharedData.options.log.includes("functions")
124
+ ? console.log(`Function: processFile(): file: ${file}`)
125
+ : null;
186
126
  try {
187
127
  const contents = await fs.promises.readFile(file, "utf8");
188
- const lines = contents.split(/\r?\n/);
189
- const anchors = [];
190
- const htmlAnchors = []; //{};
191
- const relativeLinks = [];
192
- const absoluteLinks = [];
193
- const absoluteImageLinks = [];
194
- const relativeImageLinks = [];
195
- const unHandledLinkTypes = [];
196
- const allErrors = [];
197
- for (let i = 0; i < lines.length; i++) {
198
- const line = lines[i];
199
- const heading = processHeading(line, slugifyApproach);
200
- if (heading) {
201
- anchors.push(heading);
202
- }
203
-
204
- const links = processMarkdownLink(
205
- line,
206
- relativeLinks,
207
- relativeImageLinks,
208
- absoluteLinks,
209
- absoluteImageLinks,
210
- unHandledLinkTypes
211
- );
212
- }
213
-
214
- const htmlTagsWithIdsMatches = contents.match(
215
- /<([a-z]+)(?:\s+[^>]*?\bid=(["'])(.*?)\2[^>]*?)?>/gi
216
- );
217
- if (htmlTagsWithIdsMatches) {
218
- htmlTagsWithIdsMatches.forEach((match) => {
219
- const tagMatches = match.match(/^<([a-z]+)/i);
220
- const idMatches = match.match(/id=(["'])(.*?)\1/);
221
- if (tagMatches && idMatches) {
222
- const tag = tagMatches[1].toLowerCase();
223
- const id = idMatches[2];
224
-
225
- if (tag && id) {
226
- /*
227
- if (!htmlAnchors[tag]) {
228
- htmlAnchors[tag] = [];
229
- }
230
- htmlAnchors[tag].push(id);
231
- */
232
- htmlAnchors.push(id);
233
- }
234
- }
235
- });
236
- }
128
+ const resultsForFile = processMarkdown(contents, file);
129
+ resultsForFile["page_file"] = file;
130
+
131
+ // Call slugify slugifyVuepress() on each of the headings
132
+ // Update resultsForFile[''] with values
133
+ // return slugifyVuepress(matches[1]);
134
+ const anchorArray = [];
135
+ resultsForFile.headings.forEach((item) => {
136
+ anchorArray.push(slugifyVuepress(item));
137
+ });
138
+ resultsForFile["anchors_auto_headings"] = anchorArray;
139
+ //console.log(resultsForFile);
237
140
 
238
- return {
239
- page_file: file,
240
- anchors_auto_headings: anchors,
241
- anchors_tag_ids: htmlAnchors,
242
- relativeLinks,
243
- absoluteLinks,
244
- absoluteImageLinks,
245
- relativeImageLinks,
246
- unHandledLinkTypes,
247
- allErrors,
248
- };
141
+ return resultsForFile;
249
142
  } catch (err) {
250
143
  console.error(`Error processing file ${file}: ${err.message}`);
251
144
  console.error(err);
@@ -253,165 +146,60 @@ const processFile = async (file, slugifyApproach) => {
253
146
  }
254
147
  };
255
148
 
256
- const processDirectory = async (dir, slugifyApproach) => {
149
+ const processDirectory = async (dir) => {
150
+ sharedData.options.log.includes("functions")
151
+ ? console.log(`Function: processDirectory(): dir: ${dir}`)
152
+ : null;
257
153
  const files = await fs.promises.readdir(dir, { withFileTypes: true });
258
154
  const results = [];
259
155
  for (let i = 0; i < files.length; i++) {
260
156
  const file = path.join(dir, files[i].name);
157
+ //console.log(`XxxxXprocessDirectory: file: ${file}`);
261
158
  if (files[i].isDirectory()) {
262
- const subResults = await processDirectory(file, slugifyApproach);
159
+ const subResults = await processDirectory(file);
263
160
  results.push(...subResults);
264
- } else if (isMarkdown(file) || isHtml(file)) {
265
- const result = await processFile(file, slugifyApproach);
161
+ } else if (isMarkdown(file)) {
162
+ sharedData.allMarkdownFiles.add(file);
163
+ const result = await processFile(file);
266
164
  if (result) {
267
165
  results.push(result);
268
166
  }
269
167
  }
168
+
169
+ else if (isHTML(file)) {
170
+ sharedData.allHTMLFiles.add(file);
171
+ const result = await processFile(file);
172
+ if (result) {
173
+ results.push(result);
174
+ }
175
+ }
176
+
177
+ else if (isImage(file)) {
178
+ sharedData.allImageFiles.add(file);
179
+ }
180
+ else {
181
+ sharedData.allOtherFiles.add(file);
182
+ }
270
183
  }
271
184
  return results;
272
185
  };
273
186
 
274
- function processRelativeLinks(results) {
275
- if (!results.allErrors) {
276
- results["allErrors"] = [];
277
- }
278
- results.forEach((page, index, array) => {
279
- //console.log(page);
280
-
281
- page.relativeLinks.forEach((link, index, array) => {
282
- //console.log(link);
283
- //resolve the path for the link
284
- const page_rel_path = page.page_file.split(options.directory)[1];
285
- if (link.linkUrl === "") {
286
- //page local link - check current page for headings
287
- //console.log(link);
288
-
289
- if (
290
- page.anchors_auto_headings.includes(link.linkAnchor) ||
291
- page.anchors_tag_ids.includes(link.linkAnchor)
292
- ) {
293
- //do nothing - we're good
294
- } else {
295
- const error = {
296
- type: "InternalLocalMissingAnchor",
297
- page: `${page.page_file}`,
298
- linkAnchor: `${link.linkAnchor}`,
299
- linkText: `${link.linkText}`,
300
- };
301
-
302
- results.allErrors.push(error);
303
- //console.log(error);
304
- //console.log( `ERROR: ${page_rel_path}: Missing local anchor [${link.linkText}](#${link.linkAnchor})` );
305
- }
306
- } else {
307
- // relative link on another page.
308
-
309
- //find the path of the linked page.
310
- const linkAbsoluteFilePath = path.resolve(
311
- path.dirname(page.page_file),
312
- link.linkUrl
313
- );
314
-
315
- // Get the matching file matching our link, if it exists
316
- let linkedFile =
317
- results.find(
318
- (linkedFile) =>
319
- linkedFile.hasOwnProperty("page_file") &&
320
- path.normalize(linkedFile.page_file) === linkAbsoluteFilePath
321
- ) || null;
322
-
323
- if (!linkedFile) {
324
- if (
325
- options.tryMarkdownforHTML &&
326
- linkAbsoluteFilePath.endsWith(".html")
327
- ) {
328
- // The file was HTML so it might be a file extension mistake (linking to html instead of md)
329
- // In this case we'll try find it.
330
-
331
- const markdownAbsoluteFilePath = `${
332
- linkAbsoluteFilePath.split(".html")[0]
333
- }.md`;
334
- const linkedHTMLFile =
335
- results.find(
336
- (linkedHTMLFile) =>
337
- linkedHTMLFile.hasOwnProperty("page_file") &&
338
- path.normalize(linkedHTMLFile.page_file) ===
339
- markdownAbsoluteFilePath
340
- ) || null;
341
-
342
- if (linkedHTMLFile) {
343
- const error = {
344
- type: "InternalLinkToHTML",
345
- page: `${page.page_file}`,
346
- linkUrl: `${link.linkUrl}`,
347
- linkText: `${link.linkText}`,
348
- linkUrlFilePath: `${linkAbsoluteFilePath}`,
349
- };
350
- results.allErrors.push(error);
351
- // console.log(`: ${page_rel_path}: WARN: Link to .html not .md '${link.linkUrl}' with text '${link.linkText}' (${linkAbsoluteFilePath} )` );
352
- linkedFile = linkedHTMLFile;
353
- }
354
- }
355
- }
356
-
357
- if (!linkedFile) {
358
- //File not found as .html or md
359
- const error = {
360
- type: "InternalLinkMissingFile",
361
- page: `${page.page_file}`,
362
- linkUrl: `${link.linkUrl}`,
363
- linkText: `${link.linkText}`,
364
- linkUrlFilePath: `${linkAbsoluteFilePath}`,
365
- };
366
- results.allErrors.push(error);
367
- // console.log(`ERROR: ${page_rel_path}: ERROR Broken rel. link '${link.linkUrl}' with text '${link.linkText}' (${linkAbsoluteFilePath} )` );
368
- } else {
369
- // There is a link, so now see if there are anchors, and whether they work
370
- if (!link.linkAnchor) {
371
- //null
372
- return;
373
- } else if (
374
- linkedFile.anchors_auto_headings.includes(link.linkAnchor) ||
375
- linkedFile.anchors_tag_ids.includes(link.linkAnchor)
376
- ) {
377
- //
378
- //do nothing - we're good
379
- } else {
380
- // Link exists, but anchor broken
381
-
382
- const link_rel_path = linkedFile.page_file.split(
383
- options.directory
384
- )[1];
385
- const error = {
386
- type: "InternalMissingAnchor",
387
- page: `${page.page_file}`,
388
- linkAnchor: `${link.linkAnchor}`,
389
- linkUrl: `${link.linkUrl}`,
390
- linkText: `${link.linkText}`,
391
- linkUrlFilePath: `${linkAbsoluteFilePath}`,
392
- };
393
- results.allErrors.push(error);
394
- //console.log( `WARN: ${page_rel_path}: Missing anchor \`${link.linkAnchor}\` linked in '${link_rel_path}' (linkText '${link.linkText}')` );
395
- //console.log(`ERRORS CAUSED BY INCORRECT GUESS ABOUT FORMAT OF / in the new URL - e.g. mounting/orientation`)
396
- }
397
- }
398
- }
399
- });
400
- });
401
- }
402
-
403
187
  function filterErrors(errors) {
188
+ sharedData.options.log.includes("functions")
189
+ ? console.log(`Function: filterErrors()`)
190
+ : null;
404
191
  // This method filters all errors against settings in the command line - such as pages to output.
405
192
  let filteredErrors = errors;
406
193
  // Filter results on specified file names (if any specified)
407
- //console.log(`Number pages to filter: ${options.files.length}`);
408
- if (options.files.length > 0) {
194
+ //console.log(`Number pages to filter: ${sharedData.options.files.length}`);
195
+ if (sharedData.options.files.length > 0) {
409
196
  filteredErrors = errors.filter((error) => {
410
- //console.log(`Error: ${error}`);
197
+ //console.log(`UError: ${error}`);
411
198
  //console.log(JSON.stringify(error, null, 2));
412
- //console.log(`Error page: ${error.page}`);
413
-
414
- return options.files.includes(error.page);
199
+ //console.log(`UError page: ${error.page}`);
200
+ const filterResult = sharedData.options.files.includes(error.page);
201
+ //console.log(`filterResult: ${filterResult}`);
202
+ return filterResult;
415
203
  });
416
204
  }
417
205
  // Filter on other things - such as errors.
@@ -420,115 +208,73 @@ function filterErrors(errors) {
420
208
  return filteredErrors;
421
209
  }
422
210
 
423
- function outputErrors(results) {
424
- //console.log(results.allErrors);
425
-
426
- // Strip out any files that are not in options.files
427
- // if this is empty skip step
428
- // These are path relative, so we will need to
429
- //console.log(`File options: ${options.files}`);
430
-
431
- //Sort results by page and type.
432
- // Perhaps next step is to create only get info for particular pages.
433
- const sortedByPageErrors = {};
434
-
435
- //for (const error of results.allErrors) {
436
- for (const error of results) {
437
- //Report errors for listed pages or all
438
- //console.log("error:");
439
- //console.log(error);
440
- //console.log(error.page);
441
- if (!sortedByPageErrors[error.page]) {
442
- sortedByPageErrors[error.page] = [];
443
- }
444
- sortedByPageErrors[error.page].push(error);
211
+ //main function, after options et have been set up.
212
+ (async () => {
213
+ // process containing markdown, return results which includes links, headings, id anchors
214
+ const results = await processDirectory(markdownDirectory);
445
215
 
446
- // Sort by type as well.
447
- for (const page in sortedByPageErrors) {
448
- sortedByPageErrors[page].sort((a, b) => a.type.localeCompare(b.type));
449
- }
216
+ // Process just the relative links to find errors like missing files, anchors
217
+ const errorsFromRelativeLinks = processRelativeLinks(results);
218
+ if (!results.allErrors) {
219
+ results.allErrors = [];
450
220
  }
221
+ results["allErrors"].push(...errorsFromRelativeLinks);
451
222
 
452
- //console.log(sortedByPageErrors);
453
- for (page in sortedByPageErrors) {
454
- let pageFromRoot;
455
- if (options.root) {
456
- pageFromRoot = page.split(options.root)[1];
457
- } else {
458
- pageFromRoot = page.split(options.directory)[1];
459
- }
460
-
461
- console.log(`\n${pageFromRoot}`);
462
- for (const error of sortedByPageErrors[page]) {
463
- if (error.type == "InternalLinkMissingFile") {
464
- console.log(`- ${error.type}: ${error.linkUrl}`);
465
- //console.log(` ${error.type}: ${error.linkAnchor}, linkURL: ${error.linkUrl}`);
466
- // { "type": "InternalLinkMissingFile", "page": `${page.page_file}`, "linkUrl": `${link.linkUrl}`, "linkText": `${link.linkText}`, "linkUrlFilePath": `${linkAbsoluteFilePath}` };
467
- } else if (error.type == "InternalLocalMissingAnchor") {
468
- // missing anchor in linked file that exists.
469
- //console.log(error);
470
- console.log(
471
- `- ${error.type}: ` +
472
- "`[" +
473
- `${error.linkText}](#${error.linkAnchor})` +
474
- "` (Internal link without matching heading name or element id)"
475
- );
476
- //console.log( `- ${error.type}: #${error.linkAnchor} (Internal link without matching heading name or element id)` );
477
- //console.log(` ${error.type}: #${error.linkAnchor} (heading/anchor missing?)`);
478
- //console.log(` #${error.linkAnchor} - Internal anchor not found`);
479
- //console.log(` [${error.linkText}](#${error.linkAnchor}) - Anchor not found`);
480
- //console.log(` Internal anchor not found: #${error.linkAnchor} `);
481
- // `{ "type": "InternalLocalMissingAnchor", "page": "${page.page_file}", "anchor": "${link.linkAnchor}", "linktext", "${link.linkText}" }`;
482
- } else if (error.type == "InternalMissingAnchor") {
483
- // missing anchor in linked file that exists.
484
- //console.log(error);
485
- console.log(
486
- `- ${error.type}: #${error.linkAnchor} not found in ${error.linkUrlFilePath}`
487
- );
488
- //console.log(` ${error.type}: #${error.linkAnchor} (heading/anchor missing?)`);
489
- //console.log(` #${error.linkAnchor} - Internal anchor not found`);
490
- //console.log(` [${error.linkText}](#${error.linkAnchor}) - Anchor not found`);
491
- //console.log(` Internal anchor not found: #${error.linkAnchor} `);
492
- // { "type": "InternalMissingAnchor", "page": `${page.page_file}`, "linkAnchor": `${link.linkAnchor}`, "linkUrl": `${link.linkUrl}`, "linktext": `${link.linkText}`, "linkUrlFilePath": `${linkAbsoluteFilePath}` };
493
- } else if (error.type == "InternalLinkToHTML") {
494
- console.log(`- ${error.type}: ${error.linkUrl} (should be ".md"?)`);
495
- //console.log(` ${error.type}: linkURL: ${error.linkUrl} ends in ".html"`);
496
- // { "type": "InternalLinkToHTML", "page": `${page.page_file}`, "linkUrl": `${link.linkUrl}`, "linkText": `${link.linkText}`, "linkUrlFilePath": `${linkAbsoluteFilePath}` };
497
- } else {
498
- console.log(error);
499
- }
500
- }
501
- //console.log(page)
502
- //console.log(page.errors);
503
- }
504
- }
223
+ // Process just images linked in local file system - find errors like missing images.
224
+ const errorsFromLocalImageLinks = await checkLocalImageLinks(
225
+ results
226
+ );
227
+ //console.log(errorsFromLocalImageLinks)
228
+ results["allErrors"].push(...errorsFromLocalImageLinks);
505
229
 
506
- (async () => {
507
- options.files
508
- ? (options.files = await loadJSONFileToReportOn(options.files))
509
- : (options.files = []);
510
- if (options.log == "quick") {
511
- for (const file of options.files) {
512
- console.log(file);
513
- }
514
- }
230
+ // Process links to current site URL - should be relative links normally.
231
+ const errorsFromUrlsToLocalSite = await processUrlsToLocalSource(
232
+ results
233
+ );
234
+ //console.log(errorsFromUrlsToLocalSite)
235
+ results["allErrors"].push(...errorsFromUrlsToLocalSite);
515
236
 
516
- //const object = filesToProcessJSONFilePath ? await convertFileToObject(filePath) : [];
237
+ // Check for page orphans - markdown files not linked anywhere and not in summary.
238
+ // Guesses the table of contents file if not specified in options.toc
239
+ sharedData.options.toc ? null : (sharedData.options.toc = getPageWithMostLinks(results));
240
+ checkPageOrphans(results); // Perhaps should follow pattern of returning errors - currently updates results
517
241
 
518
- const results = await processDirectory(
519
- options.directory,
520
- options.headingAnchorSlugify
242
+ const errorsGlobalImageOrphanCheck = await checkImageOrphansGlobal(
243
+ results
521
244
  );
245
+ results["allErrors"].push(...errorsGlobalImageOrphanCheck);
522
246
 
523
- processRelativeLinks(results);
247
+ // Filter the errors based on the settings in options.
248
+ // At time of writing just filters on specific set of pages.
524
249
  const filteredResults = filterErrors(results.allErrors);
250
+
251
+ // Output the errors as console.logs
525
252
  outputErrors(filteredResults);
526
253
 
527
- //console.log(JSON.stringify(results, null, 2));
528
- //console.log("AllErrors");
529
- if (options.log == "allerrors") {
530
- console.log(JSON.stringify(results.allErrors, null, 2));
254
+ //make array and document options? ie. if includes ...
255
+ const jsonFilteredErrors = JSON.stringify(filteredResults, null, 2);
256
+ logToFile("./logs/filteredErrors.json", jsonFilteredErrors);
257
+
258
+ // Log filtered errors to standard out
259
+ if (sharedData.options.log.includes("filterederrors")) {
260
+ console.log(jsonFilteredErrors);
261
+ }
262
+
263
+ //make array and document options? ie. if includes ...
264
+ const jsonAllResults = JSON.stringify(results, null, 2);
265
+ logToFile("./logs/allResults.json", jsonAllResults);
266
+ if (sharedData.options.log.includes("allresults")) {
267
+ console.log(jsonAllResults);
268
+ }
269
+
270
+ //make array and document options? ie. if includes ...
271
+ const jsonAllErrors = JSON.stringify(results.allErrors, null, 2);
272
+ logToFile("./logs/allErrors.json", jsonAllErrors);
273
+
274
+ if (sharedData.options.log.includes("allerrors")) {
275
+ console.log(jsonAllErrors);
531
276
  }
277
+ //console.log(`OPTIONS.LOG ${options.log}`);
532
278
  })();
533
279
 
534
280
  //OpenQuestions