pagean 15.6.5 → 16.0.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.
- package/README.md +4 -0
- package/bin/pageanrc-lint.js +5 -3
- package/lib/external-file-utilities.js +6 -5
- package/lib/link-utilities.js +22 -29
- package/lib/schema-errors.js +3 -2
- package/lib/sitemap.js +5 -4
- package/lib/utilities.js +1 -3
- package/package.json +17 -18
package/README.md
CHANGED
|
@@ -15,6 +15,10 @@ Install Pagean globally (as shown below), or locally, via
|
|
|
15
15
|
npm install -g pagean
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
If `puppeteer` issues are encountered during installation, see the
|
|
19
|
+
[system requirements](https://github.com/puppeteer/puppeteer/blob/main/docs/guides/system-requirements.md)
|
|
20
|
+
for complete details. These should be standard for most installations.
|
|
21
|
+
|
|
18
22
|
## Usage
|
|
19
23
|
|
|
20
24
|
Pagean runs as a command line tool and is executed as follows:
|
package/bin/pageanrc-lint.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Levels, log } from 'ci-logger';
|
|
4
|
-
import kleur from 'kleur';
|
|
5
4
|
import { program } from 'commander';
|
|
5
|
+
import { styleText } from 'node:util';
|
|
6
6
|
|
|
7
7
|
import { formatErrors } from '../lib/schema-errors.js';
|
|
8
8
|
import { lintConfigFile } from '../lib/config.js';
|
|
@@ -35,9 +35,11 @@ const outputJsonResults = (configFileName, lintResults) => {
|
|
|
35
35
|
|
|
36
36
|
const outputConsoleResults = (configFileName, lintResults) => {
|
|
37
37
|
if (lintResults.isValid) {
|
|
38
|
-
log({
|
|
38
|
+
log({
|
|
39
|
+
message: `\n${configFileName}: ${styleText('green', 'valid')}\n`
|
|
40
|
+
});
|
|
39
41
|
} else {
|
|
40
|
-
logError(`\n${
|
|
42
|
+
logError(`\n${styleText('underline', configFileName)}`, false);
|
|
41
43
|
for (const error of formatErrors(lintResults.errors)) {
|
|
42
44
|
logError(error, false);
|
|
43
45
|
}
|
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
9
|
|
|
10
|
-
import axios from 'axios';
|
|
11
|
-
|
|
12
10
|
const externalFilePath = 'pagean-external-scripts';
|
|
13
11
|
|
|
14
12
|
/**
|
|
@@ -58,14 +56,17 @@ const saveExternalScript = async (script) => {
|
|
|
58
56
|
// Path generated above from URL, not from user input.
|
|
59
57
|
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
60
58
|
if (!fs.existsSync(pathName)) {
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
const response = await fetch(script);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
throw new Error(`HTTP ${response.status}`);
|
|
62
|
+
}
|
|
63
|
+
const data = await response.text();
|
|
63
64
|
// Path generated above from URL, not from user input.
|
|
64
65
|
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
65
66
|
fs.mkdirSync(path.dirname(pathName), { recursive: true });
|
|
66
67
|
// Path generated above from URL, not from user input.
|
|
67
68
|
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
68
|
-
fs.writeFileSync(pathName,
|
|
69
|
+
fs.writeFileSync(pathName, data);
|
|
69
70
|
}
|
|
70
71
|
result.localFile = pathName;
|
|
71
72
|
} catch (error) {
|
package/lib/link-utilities.js
CHANGED
|
@@ -4,9 +4,7 @@
|
|
|
4
4
|
* @module link-utilities
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
import axios from 'axios';
|
|
7
|
+
import { Agent } from 'undici';
|
|
10
8
|
import cssesc from 'cssesc';
|
|
11
9
|
import normalizeUrl from 'normalize-url';
|
|
12
10
|
|
|
@@ -128,7 +126,7 @@ const checkExternalPageLinkBrowser = async (page, link) => {
|
|
|
128
126
|
};
|
|
129
127
|
|
|
130
128
|
/**
|
|
131
|
-
* Checks the provided link for validity by requesting with
|
|
129
|
+
* Checks the provided link for validity by requesting with fetch. If useGet is false,
|
|
132
130
|
* a HEAD request is made for efficiency. If useGet is true, a full GET request is made.
|
|
133
131
|
*
|
|
134
132
|
* @param {Page} page A Puppeteer page object.
|
|
@@ -144,43 +142,38 @@ const checkExternalPageLink = async (page, link, useGet = false) => {
|
|
|
144
142
|
return 'Cannot check "file:" URLs';
|
|
145
143
|
}
|
|
146
144
|
|
|
147
|
-
// Get user-agent from page so
|
|
145
|
+
// Get user-agent from page so fetch uses the same value for requesting links
|
|
148
146
|
const userAgent = await page.evaluate('navigator.userAgent');
|
|
149
147
|
|
|
150
148
|
try {
|
|
151
|
-
const
|
|
152
|
-
decompress: false,
|
|
149
|
+
const fetchOptions = {
|
|
153
150
|
headers: { 'User-Agent': userAgent },
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// HEAD response, see https://github.com/axios/axios/issues/5102.
|
|
157
|
-
// The response body is also not used, so more efficient to skip.
|
|
151
|
+
method: useGet ? 'GET' : 'HEAD',
|
|
152
|
+
signal: AbortSignal.timeout(timeoutSeconds * msPerSec)
|
|
158
153
|
};
|
|
159
154
|
// Using internal browser property since not exposed
|
|
160
155
|
/* eslint-disable-next-line no-underscore-dangle -- require to match
|
|
161
156
|
Puppeteer API */
|
|
162
157
|
if (page.browser()._ignoreHTTPSErrors) {
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
fetchOptions.dispatcher = new Agent({
|
|
159
|
+
connect: { rejectUnauthorized: false }
|
|
160
|
+
});
|
|
165
161
|
}
|
|
166
|
-
const
|
|
167
|
-
const response = await httpMethod(link, options);
|
|
168
|
-
return response.status;
|
|
169
|
-
} catch (error) {
|
|
162
|
+
const response = await fetch(link, fetchOptions);
|
|
170
163
|
// Some servers respond invalid for head request (e.g. a lot of 405 Method
|
|
171
|
-
// Not Allowed), so if a response was returned, is not a response
|
|
172
|
-
// skip a retry (e.g. 429), and the request was head then retry
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
!noRetryResponses.has(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return
|
|
164
|
+
// Not Allowed), so if a non-2xx response was returned, is not a response
|
|
165
|
+
// that should skip a retry (e.g. 429), and the request was head then retry
|
|
166
|
+
// with get.
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
if (!noRetryResponses.has(response.status) && !useGet) {
|
|
169
|
+
return checkExternalPageLink(page, link, true);
|
|
170
|
+
}
|
|
171
|
+
return response.status;
|
|
179
172
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
return error.
|
|
173
|
+
return response.status;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
// Fetch throws for network/execution errors; return the error code or name
|
|
176
|
+
return error.code ?? error.name;
|
|
184
177
|
}
|
|
185
178
|
};
|
|
186
179
|
|
package/lib/schema-errors.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module schema-errors
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import { styleText } from 'node:util';
|
|
8
8
|
|
|
9
9
|
const getDataKey = (instancePath) => {
|
|
10
10
|
const baseKey = '<pageanrc>';
|
|
@@ -64,7 +64,8 @@ const formatErrors = (errors) => {
|
|
|
64
64
|
}
|
|
65
65
|
return errors.map(
|
|
66
66
|
(error) =>
|
|
67
|
-
` ${error.dataKey.padEnd(maxLength + margin)}${
|
|
67
|
+
` ${error.dataKey.padEnd(maxLength + margin)}${styleText(
|
|
68
|
+
'red',
|
|
68
69
|
error.formattedMessage
|
|
69
70
|
)}`
|
|
70
71
|
);
|
package/lib/sitemap.js
CHANGED
|
@@ -5,8 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
|
-
|
|
9
|
-
import axios from 'axios';
|
|
10
8
|
import { parseStringPromise } from 'xml2js';
|
|
11
9
|
|
|
12
10
|
/**
|
|
@@ -21,8 +19,11 @@ import { parseStringPromise } from 'xml2js';
|
|
|
21
19
|
const getSitemap = async (url) => {
|
|
22
20
|
try {
|
|
23
21
|
if (url.startsWith('http')) {
|
|
24
|
-
const response = await
|
|
25
|
-
|
|
22
|
+
const response = await fetch(url);
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
throw new Error(`HTTP ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
return await response.text();
|
|
26
27
|
}
|
|
27
28
|
// Allow users to specify a local sitemap filename.
|
|
28
29
|
// nosemgrep: eslint.detect-non-literal-fs-filename
|
package/lib/utilities.js
CHANGED
|
@@ -12,9 +12,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
12
12
|
*
|
|
13
13
|
* @type {string}
|
|
14
14
|
*/
|
|
15
|
-
|
|
16
|
-
match CJS name; per https://github.com/nodejs/node/commit/e21b37d9df there were no code changes,
|
|
17
|
-
just documenting the promotion in Node 22.16.0. */
|
|
15
|
+
// eslint-disable-next-line no-underscore-dangle -- match CJS name
|
|
18
16
|
const __dirname = import.meta.dirname;
|
|
19
17
|
|
|
20
18
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pagean",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "16.0.0",
|
|
4
4
|
"description": "Pagean is a web page analysis tool designed to automate tests requiring web pages to be loaded in a browser window (e.g. horizontal scrollbar, console errors)",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pagean": "bin/pagean.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"author": "Aaron Goldenthal <npm@aarongoldenthal.com>",
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"engines": {
|
|
45
|
-
"node": "^
|
|
45
|
+
"node": "^22.19.0 || ^24.11.0 || >=26"
|
|
46
46
|
},
|
|
47
47
|
"files": [
|
|
48
48
|
"index.js",
|
|
@@ -56,32 +56,31 @@
|
|
|
56
56
|
},
|
|
57
57
|
"homepage": "https://gitlab.com/gitlab-ci-utils/pagean",
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@aarongoldenthal/eslint-config-standard": "
|
|
60
|
-
"@aarongoldenthal/stylelint-config-standard": "
|
|
61
|
-
"@vitest/coverage-v8": "4.1.
|
|
62
|
-
"bin-tester": "
|
|
59
|
+
"@aarongoldenthal/eslint-config-standard": "46.0.1",
|
|
60
|
+
"@aarongoldenthal/stylelint-config-standard": "24.0.0",
|
|
61
|
+
"@vitest/coverage-v8": "4.1.7",
|
|
62
|
+
"bin-tester": "8.0.0",
|
|
63
63
|
"c8": "11.0.0",
|
|
64
|
-
"eslint": "10.
|
|
65
|
-
"globals": "17.
|
|
66
|
-
"markdownlint-cli2": "0.22.
|
|
64
|
+
"eslint": "10.4.0",
|
|
65
|
+
"globals": "17.6.0",
|
|
66
|
+
"markdownlint-cli2": "0.22.1",
|
|
67
67
|
"nyc": "18.0.0",
|
|
68
|
-
"prettier": "3.8.
|
|
69
|
-
"stylelint": "17.
|
|
70
|
-
"vitest": "4.1.
|
|
68
|
+
"prettier": "3.8.3",
|
|
69
|
+
"stylelint": "17.12.0",
|
|
70
|
+
"vitest": "4.1.7"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"ajv": "8.
|
|
73
|
+
"ajv": "8.20.0",
|
|
74
74
|
"ajv-errors": "3.0.0",
|
|
75
|
-
"
|
|
76
|
-
"ci-logger": "8.0.1",
|
|
75
|
+
"ci-logger": "9.0.0",
|
|
77
76
|
"commander": "14.0.3",
|
|
78
77
|
"cssesc": "3.0.0",
|
|
79
78
|
"handlebars": "4.7.9",
|
|
80
79
|
"htmlhint": "1.9.2",
|
|
81
|
-
"
|
|
82
|
-
"normalize-url": "9.0.0",
|
|
80
|
+
"normalize-url": "9.0.1",
|
|
83
81
|
"protocolify": "4.0.0",
|
|
84
|
-
"puppeteer": "
|
|
82
|
+
"puppeteer": "25.0.4",
|
|
83
|
+
"undici": "8.3.0",
|
|
85
84
|
"xml2js": "0.6.2"
|
|
86
85
|
}
|
|
87
86
|
}
|