html-snapshots 0.18.3 → 0.19.0

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 (65) hide show
  1. package/.eslintrc.json +4 -5
  2. package/README.md +73 -68
  3. package/examples/custom/myFilter.js +2 -2
  4. package/examples/custom/package-lock.json +939 -4
  5. package/examples/custom/package.json +1 -1
  6. package/examples/custom/snapshot.js +8 -10
  7. package/examples/debug-phantomjs/package-lock.json +6 -6
  8. package/examples/debug-phantomjs/package.json +1 -1
  9. package/examples/debug-phantomjs/snapshot.js +12 -14
  10. package/examples/html5rocks/package-lock.json +1306 -5
  11. package/examples/html5rocks/package.json +1 -1
  12. package/examples/html5rocks/snapshot.js +15 -18
  13. package/examples/process-limit/package-lock.json +1306 -5
  14. package/examples/process-limit/package.json +1 -1
  15. package/examples/process-limit/snapshot.js +8 -10
  16. package/examples/simple-promise/package-lock.json +1306 -5
  17. package/examples/simple-promise/package.json +1 -1
  18. package/examples/simple-promise/snapshot.js +6 -8
  19. package/examples/sitemap-index/package-lock.json +1306 -5
  20. package/examples/sitemap-index/package.json +1 -1
  21. package/examples/sitemap-index/snapshot.js +6 -6
  22. package/examples/utils/index.js +2 -4
  23. package/examples/verbose/package-lock.json +1307 -5
  24. package/examples/verbose/package.json +1 -1
  25. package/examples/verbose/snapshot.js +11 -14
  26. package/lib/async/index.js +0 -1
  27. package/lib/common/index.js +1 -1
  28. package/lib/common/sitemap/index.js +16 -0
  29. package/lib/common/sitemap/sitemap-collection.js +94 -0
  30. package/lib/common/sitemap/sitemap-index.js +68 -0
  31. package/lib/common/{sitemap.js → sitemap/sitemap.js} +68 -57
  32. package/lib/html-snapshots.js +0 -1
  33. package/lib/input-generators/array.js +0 -1
  34. package/lib/input-generators/robots.js +98 -58
  35. package/lib/input-generators/sitemap-index.js +3 -124
  36. package/lib/input-generators/sitemap.js +3 -7
  37. package/lib/input-generators/textfile.js +2 -6
  38. package/package.json +5 -4
  39. package/test/helpers/options.js +4 -2
  40. package/test/mocha/html-snapshots/process-limit.js +46 -48
  41. package/test/mocha/html-snapshots/robots.js +156 -138
  42. package/test/mocha/html-snapshots/server/test_robots.txt +13 -0
  43. package/test/mocha/html-snapshots/server/test_robots_sitemap.txt +15 -0
  44. package/test/mocha/html-snapshots/server/test_robots_sitemap.xml +22 -0
  45. package/test/mocha/html-snapshots/sitemap-index.js +58 -57
  46. package/test/mocha/html-snapshots/snapshot-scripts.js +55 -54
  47. package/test/mocha/html-snapshots/test.js +16 -16
  48. package/test/mocha/html-snapshots/test_robots.txt +0 -2
  49. package/test/mocha/html-snapshots/utils.js +0 -1
  50. package/test/mocha/input-generators/server/test_robots.txt +0 -2
  51. package/test/mocha/input-generators/server/test_robots_bad.txt +0 -2
  52. package/test/mocha/input-generators/server/test_robots_sitemap.txt +17 -0
  53. package/test/mocha/input-generators/server/test_robots_sitemap_bad.txt +17 -0
  54. package/test/mocha/input-generators/server/test_robots_sitemap_multi.txt +18 -0
  55. package/test/mocha/input-generators/server/test_sitemap_index_empty.xml +3 -0
  56. package/test/mocha/input-generators/server/test_sitemap_index_malformed.xml +2 -0
  57. package/test/mocha/input-generators/sitemap-index.js +65 -9
  58. package/test/mocha/input-generators/test.js +276 -280
  59. package/test/mocha/input-generators/test_robots.txt +0 -2
  60. package/test/mocha/input-generators/test_robots_bad.txt +0 -2
  61. package/test/mocha/input-generators/test_robots_sitemap.txt +17 -0
  62. package/test/mocha/input-generators/test_robots_sitemap_bad.txt +17 -0
  63. package/test/mocha/input-generators/test_robots_sitemap_multi.txt +18 -0
  64. package/lib/common/node.js +0 -26
  65. package/test/mocha/common/node.js +0 -100
@@ -7,6 +7,6 @@
7
7
  "start": "node ./snapshot.js"
8
8
  },
9
9
  "dependencies": {
10
- "html-snapshots": "^0.18.2"
10
+ "html-snapshots": "^0.18.3"
11
11
  }
12
12
  }
@@ -5,32 +5,29 @@
5
5
  * Use sitemap.xml to snapshot an entire site.
6
6
  * Use the verbose option to debug the loading of one of the pages.
7
7
  */
8
- var htmlSnapshots = require("html-snapshots");
8
+ const htmlSnapshots = require("html-snapshots");
9
9
 
10
- var origin = "http://enigmatic-refuge-9006.herokuapp.com:80";
11
-
12
- var pageToDebug = origin + "/sample-page";
10
+ const origin = "https://www.sitemaps.org";
11
+ const pageToDebug = `${origin}/en_GB/`;
13
12
 
14
13
  // Make the verbose option as an object keyed with the page to debug.
15
- var verboseOption = {
16
- __default: false
14
+ const verboseOption = {
15
+ __default: false,
16
+ [pageToDebug]: true
17
17
  };
18
- verboseOption[pageToDebug] = true;
19
18
 
20
19
  htmlSnapshots.run({
21
20
  input: "sitemap",
22
- source: origin + "/sitemap.xml",
21
+ source: `${origin}/sitemap.xml`,
23
22
  outputDir: require("path").join(__dirname, "./tmp"),
24
23
  outputDirClean: true,
25
- selector: ".page-content",
26
- timeout: 15000,
24
+ selector: "#mainContent",
25
+ timeout: 10000,
27
26
  processLimit: 1, // to clarify output
28
27
  verbose: verboseOption
29
28
  })
30
- .then(function (completed) {
29
+ .then(completed => {
31
30
  console.log("completed snapshots:");
32
31
  console.log(require("util").inspect(completed));
33
32
  })
34
- .catch(function (err) {
35
- console.error(err);
36
- });
33
+ .catch(console.error);
@@ -7,7 +7,6 @@
7
7
  * Copyright (c) 2013 - 2022, Alex Grant, LocalNerve, contributors
8
8
  * Licensed under the MIT license.
9
9
  */
10
- /* global Promise */
11
10
 
12
11
  const crypto = require("crypto");
13
12
  const AsyncLock = require("async-lock");
@@ -48,7 +48,7 @@ function isUrl (obj) {
48
48
  * If undefined, then pass through undefined.
49
49
  * @param {String} [message] - The message to prepend.
50
50
  * @param {Boolean} [quoteInput] - True if the message should be quoted.
51
- * @returns {Error} The updated Error instance, undefined if no error.
51
+ * @returns {Error|Undefined} The updated Error instance, *undefined if no error*.
52
52
  */
53
53
  function prependMsgToErr (error, message, quoteInput) {
54
54
  let result, prepend;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Reused sitemap modules.
3
+ * Used by robots, sitemap, and sitemap-index input generators.
4
+ *
5
+ * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
6
+ * Licensed under the MIT license.
7
+ */
8
+ const sitemap = require("./sitemap");
9
+ const smc = require("./sitemap-collection");
10
+ const smi = require("./sitemap-index");
11
+
12
+ module.exports = {
13
+ sitemapCollection: { process: smc.processSitemaps.bind(null, sitemap) },
14
+ sitemapIndex: { process: smi.sitemapIndex.bind(null, sitemap) },
15
+ sitemap
16
+ };
@@ -0,0 +1,94 @@
1
+ /**
2
+ * sitemap-index.js (common functions)
3
+ *
4
+ * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
5
+ * Licensed under the MIT license.
6
+ */
7
+ const fs = require("fs");
8
+ const pathLib = require("path");
9
+ const mkdirp = require("mkdirp");
10
+ const common = require("../index");
11
+ const base = require("../../input-generators/_base");
12
+
13
+ /**
14
+ * Make sure path exists so a sitemap write can succeed.
15
+ *
16
+ * @param {String} outputPath - File path and name.
17
+ * @param {Function} callback - Completion callback.
18
+ */
19
+ function prepareWrite (outputPath, callback) {
20
+ const path = pathLib.parse(outputPath);
21
+ const dir = pathLib.join(path.root, path.dir);
22
+ mkdirp(dir).then(() => callback()).catch(callback);
23
+ }
24
+
25
+ /**
26
+ * Parse a sitemap, generate input, and optionally update the local file
27
+ * for sitemapPolicy purposes.
28
+ * This writes the sitemap files out EVEN IF sitemapPolicy is false.
29
+ * The thinking is that it's better/cheaper to do this b/c if sitemapPolicy is
30
+ * enabled later, then there is something to compare against, for more savings.
31
+ *
32
+ * @param {Object} options - Sitemap options.
33
+ * @param {Object} options.__sitemapIndex - Sitemap Index options.
34
+ * @param {String} document - xml document string.
35
+ * @param {Function} callback - A completion callback.
36
+ */
37
+ function processSitemap (smLib, options, document, callback) {
38
+ smLib.parse(options, document, err => {
39
+ const sitemapIndexOpts = options.__sitemapIndex;
40
+ const outputPath = base.outputFile(sitemapIndexOpts, options.source);
41
+
42
+ if (!err && sitemapIndexOpts.sitemapOutputDir) {
43
+ prepareWrite(outputPath, err => {
44
+ if (!err) {
45
+ fs.writeFile(outputPath, document, callback);
46
+ } else {
47
+ callback(common.prependMsgToErr(err, outputPath, true));
48
+ }
49
+ });
50
+ } else {
51
+ callback(err);
52
+ }
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Get and process a sitemaps collection.
58
+ *
59
+ * @param {Object} smLib - Sitemap common module.
60
+ * @param {Object} options - Sitemap options.
61
+ * @param {Array} sitemapUrls - collection of sitemap urls to process.
62
+ * @param {Object} [sitemapIndexOptions] - Sitemap index options.
63
+ * @returns {Promise} resolves when all sitemaps are processed, reject as soon as one fails
64
+ */
65
+ function processSitemaps (smLib, options, sitemapUrls, sitemapIndexOptions) {
66
+ // Edge case message for clarity
67
+ if (sitemapUrls.length === 0) {
68
+ console.log("[*] No sitemaps qualified for processing");
69
+ return Promise.resolve([]);
70
+ }
71
+
72
+ const smiOptions = sitemapIndexOptions || {
73
+ ...options,
74
+ ...{ outputPath: undefined }
75
+ };
76
+
77
+ // Get all the sitemaps, parse them, and generate input for each
78
+ return Promise.all(sitemapUrls.map(sitemapUrl => {
79
+ const sitemapOptions = Object.assign({}, options, {
80
+ source: sitemapUrl,
81
+ input: "sitemap",
82
+ sitemapOutputDir: false,
83
+ __sitemapIndex: smiOptions
84
+ });
85
+
86
+ console.log(`[+] Loading the sitemap: '${sitemapUrl}'`);
87
+
88
+ return smLib.getUrl(sitemapOptions, processSitemap.bind(null, smLib));
89
+ }));
90
+ }
91
+
92
+ module.exports = {
93
+ processSitemaps
94
+ };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * sitemap-index.js
3
+ *
4
+ * Generates the urls for a sitemap-index.
5
+ *
6
+ * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
7
+ * Licensed under the MIT license.
8
+ */
9
+ const _ = require("lodash");
10
+ const smc = require("./sitemap-collection");
11
+
12
+ /**
13
+ * Create a collection of sitemap urls to process by policy, form sitemapIndexOptions.
14
+ *
15
+ * @param {Object} smLib - Sitemap module
16
+ * @param {Object} options - sitemap index options
17
+ * @param {Boolean} options.sitemapPolicy - True if sitemap policy should be used.
18
+ * @param {Object} parseResult - xml2js sax parse result object
19
+ * @returns {Object} The sitemapUrls collection and sitemapIndexOptions
20
+ */
21
+ function sitemapIndexUrls (smLib, options, parseResult) {
22
+ const sitemapUrls = [];
23
+ const sitemapIndexOptions = {
24
+ ...options,
25
+ ...{ outputPath: undefined }
26
+ };
27
+
28
+ // Check if we should process each sitemap in the index.
29
+ _.forEach(
30
+ // if the sitemap index is malformed, just blow up
31
+ parseResult.sitemapindex.sitemap,
32
+ sitemapNode => {
33
+ // optionally ignore current sitemaps by sitemap policy
34
+ const shouldProcess = !options.sitemapPolicy ||
35
+ !smLib.stillCurrent(sitemapNode, sitemapIndexOptions);
36
+
37
+ if (shouldProcess) {
38
+ // if sitemap index is malformed, just blow up
39
+ sitemapUrls.push(sitemapNode.loc[0]);
40
+ }
41
+ }
42
+ );
43
+
44
+ return {
45
+ sitemapUrls,
46
+ sitemapIndexOptions
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Collect sitemap urls from a sitemap index file for processing.
52
+ *
53
+ * @param {Object} smLib - sitemap module
54
+ * @param {Object} options - sitemapIndex options
55
+ * @param {Object} parseResult - xml2js sax parse result object
56
+ * @returns {Promise} resolves when all sitemaps are processed, reject as soon as one fails
57
+ */
58
+ function sitemapIndex (smLib, options, parseResult) {
59
+ const {
60
+ sitemapUrls,
61
+ sitemapIndexOptions
62
+ } = sitemapIndexUrls(smLib, options, parseResult);
63
+ return smc.processSitemaps(smLib, options, sitemapUrls, sitemapIndexOptions);
64
+ }
65
+
66
+ module.exports = {
67
+ sitemapIndex
68
+ };
@@ -9,14 +9,15 @@
9
9
  */
10
10
 
11
11
  const fs = require("fs");
12
+ const util = require("util");
12
13
  const path = require("path");
13
14
  const urlm = require("url");
14
15
  const zlib = require("zlib");
15
16
  const _ = require("lodash");
16
17
  const xml2js = require("xml2js");
17
- const request = require("request");
18
- const common = require("./index");
19
- const base = require("../input-generators/_base");
18
+ const smi = require("./sitemap-index");
19
+ const common = require("../index");
20
+ const base = require("../../input-generators/_base");
20
21
 
21
22
  // the out of date fallback
22
23
  const unixStart = "1970-01-01T00:00:00.000Z";
@@ -44,6 +45,7 @@ const changeFreq = {
44
45
  */
45
46
  function stillCurrent (urlNode, options) {
46
47
  let lesser, greater, oPath;
48
+ const statOpts = {throwIfNoEntry: false};
47
49
  const now = Date.now();
48
50
  const lMod = _.first(urlNode.lastmod);
49
51
  const cFreq = _.first(urlNode.changefreq) ? _.first(urlNode.changefreq).toLowerCase() : null;
@@ -53,7 +55,7 @@ function stillCurrent (urlNode, options) {
53
55
  // if sitemap is malformed, just blow up
54
56
  oPath = base.outputFile(options, urlNode.loc[0]);
55
57
 
56
- lesser = now - ( (fs.existsSync(oPath) && fs.statSync(oPath).mtime.getTime()) || unixStart );
58
+ lesser = now - (fs.statSync(oPath, statOpts)?.mtime?.getTime() ?? unixStart);
57
59
  greater = now - Date.parse(lMod);
58
60
  }
59
61
  // only changefreq specified
@@ -61,7 +63,7 @@ function stillCurrent (urlNode, options) {
61
63
  // if sitemap is malformed, just blow up
62
64
  oPath = base.outputFile(options, urlNode.loc[0]);
63
65
 
64
- lesser = now - ( (fs.existsSync(oPath) && fs.statSync(oPath).mtime.getTime()) || unixStart );
66
+ lesser = now - (fs.statSync(oPath, statOpts)?.mtime?.getTime() ?? unixStart);
65
67
  greater = changeFreq[cFreq] || changeFreq.always;
66
68
  }
67
69
  // both or neither were specified
@@ -91,43 +93,49 @@ function parse (options, document, callback) {
91
93
  normalize: true
92
94
  }, (err, result) => {
93
95
  let source = options.source;
94
-
95
96
  if (!err) {
96
-
97
- // Process the url input, but break if base.input returns false.
98
- // In other words, _.find is looking for a non-falsy err.
99
- // For now, this can only happen if no outputDir is defined,
100
- // which is a fatal bad option problem and will happen immediately.
101
- _.find(
102
- // if the sitemap is malformed, just blow up
103
- result.urlset.url,
104
- urlNode => {
105
- // optionally ignore current urls by sitemap policy
106
- let url;
107
- const process = !options.sitemapPolicy ||
108
- !stillCurrent(urlNode, options);
109
-
110
- if (process) {
111
- // if sitemap is malformed, just blow up
112
- url = urlm.parse(urlNode.loc[0]);
113
- if (!base.input(_.extend({}, options, {
114
- protocol: url.protocol,
115
- auth: url.auth,
116
- hostname: url.hostname,
117
- port: url.port
118
- }),
119
- urlNode.loc[0])
120
- ) {
121
- source = urlNode.loc[0];
122
- err = base.generatorError();
97
+ if (result.sitemapindex) {
98
+ // actually a sitemap index
99
+ return smi.sitemapIndex(module.exports, options, result)
100
+ .then(() => callback())
101
+ .catch(callback);
102
+ } else {
103
+ // Process the url input, but break if base.input returns false.
104
+ // In other words, _.find is looking for a non-falsy err.
105
+ // For now, this can only happen if no outputDir is defined,
106
+ // which is a fatal bad option problem and will happen immediately.
107
+ _.find(
108
+ // if the sitemap is malformed, just blow up
109
+ result.urlset.url,
110
+ urlNode => {
111
+ // optionally ignore current urls by sitemap policy
112
+ let url;
113
+ const process = !options.sitemapPolicy ||
114
+ !stillCurrent(urlNode, options);
115
+
116
+ if (process) {
117
+ // if sitemap is malformed, just blow up
118
+ url = urlm.parse(urlNode.loc[0]);
119
+ if (!base.input(_.extend({}, options, {
120
+ protocol: url.protocol,
121
+ auth: url.auth,
122
+ hostname: url.hostname,
123
+ port: url.port
124
+ }),
125
+ urlNode.loc[0])
126
+ ) {
127
+ source = urlNode.loc[0];
128
+ err = base.generatorError();
129
+ }
123
130
  }
124
- }
125
131
 
126
- return err;
127
- }
128
- );
132
+ return err;
133
+ }
134
+ );
135
+ }
129
136
  }
130
137
 
138
+ // NOTE: if !err, then prependMsgToErr returns `undefined`
131
139
  callback(common.prependMsgToErr(err, source, true));
132
140
  });
133
141
  }
@@ -165,21 +173,25 @@ function convert (options, buffer, next, callback) {
165
173
  * @param {String} options.source - Sitemap URL.
166
174
  * @param {Function} options.timeout - User supplied run timeout.
167
175
  * @param {Function} parseFn - A data parser function.
168
- * @param {Function} callback - A completion callback function.
176
+ * @return {Promise} resolves to data on completion.
169
177
  */
170
- function getUrl (options, parseFn, callback) {
171
- request({
178
+ async function getUrl (options, parseFn) {
179
+ const { default:got } = await import("got");
180
+ return got({
172
181
  url: options.source,
173
- encoding: null,
174
- timeout: options.timeout() // get the default timeout
175
- }, (err, res, body) => {
176
- var error = err || common.checkResponse(res, ["text/xml", "application/xml"]);
177
-
182
+ responseType: "buffer",
183
+ timeout: {
184
+ request: options.timeout() // get the default timeout
185
+ }
186
+ }).then(res => {
187
+ let error = common.checkResponse(res, ["text/xml", "application/xml"]);
178
188
  if (error) {
179
- callback(common.prependMsgToErr(error, options.source, true));
180
- } else {
181
- convert(options, body, parseFn, callback);
189
+ throw new Error(error);
182
190
  }
191
+ const conv = util.promisify(convert);
192
+ return conv(options, res.body, parseFn);
193
+ }).catch(err => {
194
+ throw new Error(common.prependMsgToErr(err, options.source, true));
183
195
  });
184
196
  }
185
197
 
@@ -189,16 +201,15 @@ function getUrl (options, parseFn, callback) {
189
201
  * @param {Object} options - Sitemap options.
190
202
  * @param {String} options.source - Sitemap file path.
191
203
  * @param {Function} parseFn - A data parser function.
192
- * @param {Function} callback - A completion callback.
204
+ * @return {Promise} resolves to data on completion.
193
205
  */
194
- function getFile (options, parseFn, callback) {
195
- fs.readFile(options.source, (err, data) => {
196
- if (err) {
197
- callback(common.prependMsgToErr(err, options.source, true));
198
- } else {
199
- convert(options, data, parseFn, callback);
200
- }
201
- });
206
+ function getFile (options, parseFn) {
207
+ const readFile = util.promisify(fs.readFile);
208
+ const conv = util.promisify(convert);
209
+ return readFile(options.source).then(data => conv(options, data, parseFn))
210
+ .catch(err => {
211
+ throw new Error(common.prependMsgToErr(err, options.source, true));
212
+ });
202
213
  }
203
214
 
204
215
  module.exports = {
@@ -9,7 +9,6 @@
9
9
  * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
10
10
  * Licensed under the MIT license.
11
11
  */
12
- /* global Promise */
13
12
 
14
13
  const spawn = require("child_process").spawn;
15
14
  const path = require("path");
@@ -7,7 +7,6 @@
7
7
  * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
8
8
  * Licensed under the MIT license.
9
9
  */
10
- /* global Promise */
11
10
 
12
11
  const urlm = require("url");
13
12
  const common = require("../common");
@@ -1,19 +1,16 @@
1
- /*
1
+ /**
2
2
  * robots.js
3
3
  *
4
4
  * An input generator for html-snapshots that uses a simple robots.txt file.
5
- * Creates the snapshot arguments driven from robots.txt "Allow: ".
6
- * Does not support wildcards.
7
- * If you need wildcards, use the sitemap input generator.
5
+ * Creates the snapshot arguments driven from robots.txt Sitemap or Allow directives.
8
6
  *
9
7
  * Copyright (c) 2013 - 2022 Alex Grant, LocalNerve, contributors
10
8
  * Licensed under the MIT license.
11
9
  */
12
10
 
13
- const fs = require("fs");
14
- const request = require("request");
11
+ const fs = require("fs").promises;
15
12
  const common = require("../common");
16
- const nodeCall = require("../common/node");
13
+ const { sitemapCollection: smc } = require("../common/sitemap");
17
14
  const base = require("./_base");
18
15
 
19
16
  // The default options
@@ -39,19 +36,81 @@ const robots = {
39
36
  };
40
37
 
41
38
  /**
42
- * Generate input for one line of a simple robots.txt file
43
- * Does not support wildcards.
39
+ * Get all unique `Allow:` url paths from robots.txt.
40
+ * Reads the allow values up to whitespace (end-of-line), or wildcard.
41
+ *
42
+ * @param {String} body - The robots.txt file
43
+ * @returns {Array} collection of allow url paths
44
44
  */
45
- function oneline (line, options) {
46
- const key = "Allow: ",
47
- index = line.indexOf(key);
45
+ function getAllowUrls (body) {
46
+ const allowUrls = new Set();
47
+ const re = /\ballow\b\s*:\s*(?<allowUrl>[^*\s]+)/ig;
48
+ const matches = body.matchAll(re);
49
+ let allowUrl;
48
50
 
49
- if (index !== -1) {
50
- const page = line.substr(index + key.length).replace(/^\s+|\s+$/g, "");
51
- return page.indexOf("*") === -1 && base.input(options, page);
51
+ for (const match of matches) {
52
+ allowUrl = match?.groups?.allowUrl;
53
+ if (allowUrl) {
54
+ allowUrls.add(allowUrl);
55
+ }
52
56
  }
53
57
 
54
- return true;
58
+ return allowUrls.size > 0 ? Array.from(allowUrls) : null;
59
+ }
60
+
61
+ /**
62
+ * Get all the unique sitemap urls from robots.txt.
63
+ *
64
+ * @param {String} body - The robots.txt file
65
+ * @returns {Array} collection of unqiue sitemap urls
66
+ */
67
+ function getSitemapUrls (body) {
68
+ const sitemapUrls = new Set();
69
+ const re = /\bsitemap\b\s*:\s*(?<sitemapUrl>[^\s]+)/ig;
70
+ const matches = body.matchAll(re);
71
+ let sitemapUrl;
72
+
73
+ for (const match of matches) {
74
+ sitemapUrl = match?.groups?.sitemapUrl;
75
+ if (sitemapUrl) {
76
+ sitemapUrls.add(sitemapUrl);
77
+ }
78
+ }
79
+
80
+ return sitemapUrls.size > 0 ? Array.from(sitemapUrls) : null;
81
+ }
82
+
83
+ /**
84
+ * Parse urls from robots.txt and generate input.
85
+ * Look for sitemaps first, then look for allow urls.
86
+ *
87
+ * @param {Object} options - robots.txt options
88
+ * @param {String} body - robots.txt content
89
+ * @returns {Promise} resolves on success
90
+ */
91
+ function processRobotsTxt (options, body) {
92
+ let allowUrls = false;
93
+ const urls = getSitemapUrls(body) || (allowUrls = getAllowUrls(body));
94
+
95
+ if (allowUrls) {
96
+ let result;
97
+ for (const url of urls) {
98
+ result = base.input(options, url);
99
+ // if base.input returns false, then no outputDir was defined,
100
+ // which is a fatal bad option problem and will happen immediately.
101
+ if (!result) {
102
+ throw new Error(
103
+ common.prependMsgToErr(base.generatorError(), url, true)
104
+ );
105
+ }
106
+ }
107
+ return Promise.resolve();
108
+ } else {
109
+ if (urls && urls.length > 0) {
110
+ return smc.process(options, urls);
111
+ }
112
+ throw new Error(`No urls found in ${options.source}`);
113
+ }
55
114
  }
56
115
 
57
116
  /**
@@ -60,54 +119,37 @@ function oneline (line, options) {
60
119
  * @param {Object} options - Robots.txt options
61
120
  * @param {String} options.source - The URL to a robots.txt
62
121
  * @param {Function} options.timeout - Returns the robots.txt request timeout.
63
- * @param {Function} callback - A completion callback.
122
+ * @returns {Promise} resolves to undefined.
64
123
  */
65
- function getRobotsUrl (options, callback) {
66
- request({
124
+ async function getRobotsUrl (options) {
125
+ const { default:got } = await import("got");
126
+ return got({
67
127
  url: options.source,
68
- timeout: options.timeout()
69
- }, (err, res, body) => {
70
- let error = err || common.checkResponse(res, "text/plain");
71
-
128
+ timeout: {
129
+ request: options.timeout()
130
+ }
131
+ }).then(res => {
132
+ const error = common.checkResponse(res, "text/plain");
72
133
  if (error) {
73
- callback(common.prependMsgToErr(error, options.source, true));
74
- } else {
75
- body.toString().split('\n').every(line => {
76
- // Process the line input, but break if base.input returns false.
77
- // For now, this can only happen if no outputDir is defined,
78
- // which is a fatal bad option problem and will happen immediately.
79
- if (!oneline(line, options)) {
80
- error = common.prependMsgToErr(base.generatorError(), line, true);
81
- return false;
82
- }
83
- return true;
84
- });
85
-
86
- callback(error);
134
+ throw new Error(error);
87
135
  }
136
+ return processRobotsTxt(options, res.body.toString());
137
+ }).catch(err => {
138
+ throw new Error(common.prependMsgToErr(err, options.source, true));
88
139
  });
89
140
  }
90
141
 
91
142
  /**
92
143
  * Reads the robots.txt file and parses it.
144
+ *
145
+ * @param {Object} options - Robots.txt options
93
146
  */
94
- function getRobotsFile (options, callback) {
95
- fs.readFile(options.source, (err, data) => {
96
- if (!err) {
97
- data.toString().split('\n').every(line => {
98
- // Process the line input, but break if base.input returns false.
99
- // For now, this can only happen if no outputDir is defined,
100
- // which is a fatal bad option problem and will happen immediately.
101
- if (!oneline(line, options)) {
102
- err = common.prependMsgToErr(base.generatorError(), line, true);
103
- return false;
104
- }
105
- return true;
106
- });
107
- }
108
-
109
- callback(err);
110
- });
147
+ function getRobotsFile (options) {
148
+ return fs.readFile(options.source)
149
+ .then(data => processRobotsTxt(options, data.toString()))
150
+ .catch(err => {
151
+ throw new Error(common.prependMsgToErr(err, options.source, true));
152
+ });
111
153
  }
112
154
 
113
155
  /**
@@ -122,10 +164,8 @@ function getRobotsFile (options, callback) {
122
164
  * @returns {Promise} Resolves to undefined on completion.
123
165
  */
124
166
  function generateInput (options) {
125
- return nodeCall(
126
- common.isUrl(options.source) ? getRobotsUrl : getRobotsFile,
127
- options
128
- )
167
+ const retrieve = common.isUrl(options.source) ? getRobotsUrl : getRobotsFile;
168
+ return retrieve(options)
129
169
  .catch(err => {
130
170
  options._abort(err);
131
171
  })