pagean 8.0.4 → 10.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/LICENSE +1 -1
- package/README.md +238 -83
- package/bin/pagean.js +15 -10
- package/bin/pageanrc-lint.js +2 -2
- package/docs/upgrade-guide.md +14 -5
- package/index.js +12 -7
- package/lib/config.js +54 -15
- package/lib/default-config.json +3 -0
- package/lib/external-file-utils.js +8 -2
- package/lib/link-utils.js +9 -9
- package/lib/logger.js +14 -14
- package/lib/report-template.handlebars +63 -39
- package/lib/reporter.js +8 -6
- package/lib/schema-errors.js +2 -2
- package/lib/sitemap.js +120 -0
- package/lib/test-utils.js +4 -4
- package/lib/tests.js +14 -15
- package/package.json +28 -27
- package/schemas/pageanrc.schema.json +52 -1
package/lib/config.js
CHANGED
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
* @module config
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const Ajv = require('ajv')
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
const path = require('node:path');
|
|
11
|
+
const Ajv = require('ajv/dist/2019');
|
|
12
|
+
const draft7MetaSchema = require('ajv/dist/refs/json-schema-draft-07.json');
|
|
12
13
|
const ajvErrors = require('ajv-errors');
|
|
13
14
|
const protocolify = require('protocolify');
|
|
14
15
|
|
|
15
16
|
const { normalizeLink } = require('./link-utils');
|
|
17
|
+
const { getUrlsFromSitemap } = require('./sitemap');
|
|
16
18
|
|
|
17
19
|
const defaultConfig = require('./default-config.json');
|
|
18
20
|
const defaultHtmlHintConfigFilename = './.htmlhintrc';
|
|
@@ -43,6 +45,8 @@ const processAllTestSettings = (settings) => {
|
|
|
43
45
|
for (const key of Object.keys(settings)) {
|
|
44
46
|
processedSettings[key] = processTestSetting(settings[key]);
|
|
45
47
|
}
|
|
48
|
+
/* eslint-disable-next-line consistent-return -- return undefined
|
|
49
|
+
if no settings */
|
|
46
50
|
return processedSettings;
|
|
47
51
|
};
|
|
48
52
|
|
|
@@ -70,45 +74,75 @@ const consolidateTestSettings = (
|
|
|
70
74
|
return processedSettings;
|
|
71
75
|
};
|
|
72
76
|
|
|
73
|
-
const
|
|
77
|
+
const addSitemapUrls = (config, sitemapUrls) => {
|
|
78
|
+
if (config.urls) {
|
|
79
|
+
for (const url of config.urls) {
|
|
80
|
+
const { rawUrl } = getUrl(url);
|
|
81
|
+
const index = sitemapUrls.indexOf(rawUrl);
|
|
82
|
+
index > -1 && sitemapUrls.splice(index, 1);
|
|
83
|
+
}
|
|
84
|
+
config.urls.push(...sitemapUrls);
|
|
85
|
+
} else {
|
|
86
|
+
config.urls = sitemapUrls;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const processUrls = async (config) => {
|
|
74
91
|
const processedConfigSettings = processAllTestSettings(config.settings);
|
|
92
|
+
if (config.sitemap) {
|
|
93
|
+
const sitemapUrls = await getUrlsFromSitemap(config.sitemap);
|
|
94
|
+
addSitemapUrls(config, sitemapUrls);
|
|
95
|
+
}
|
|
75
96
|
return config.urls.map((testUrl) => {
|
|
76
97
|
const { rawUrl, urlSettings } = getUrl(testUrl);
|
|
77
98
|
return {
|
|
78
|
-
url: protocolify(rawUrl),
|
|
79
99
|
rawUrl,
|
|
80
100
|
settings: consolidateTestSettings(
|
|
81
101
|
defaultConfig.settings,
|
|
82
102
|
processedConfigSettings,
|
|
83
103
|
processAllTestSettings(urlSettings)
|
|
84
|
-
)
|
|
104
|
+
),
|
|
105
|
+
url: protocolify(rawUrl)
|
|
85
106
|
};
|
|
86
107
|
});
|
|
87
108
|
};
|
|
88
109
|
|
|
89
110
|
const getHtmlHintConfig = (htmlHintConfigFilename) => {
|
|
111
|
+
// Allow users to specify a custom htmlhintrc file.
|
|
112
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
90
113
|
if (!fs.existsSync(htmlHintConfigFilename)) {
|
|
91
114
|
return;
|
|
92
115
|
}
|
|
116
|
+
/* eslint-disable consistent-return -- return undefined if no settings */
|
|
117
|
+
// Allow users to specify a custom htmlhintrc file.
|
|
118
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
93
119
|
return JSON.parse(fs.readFileSync(htmlHintConfigFilename, 'utf8'));
|
|
120
|
+
/* eslint-enable consistent-return -- return undefined if no settings */
|
|
94
121
|
};
|
|
95
122
|
|
|
96
123
|
const validateConfigSchema = (config) => {
|
|
97
124
|
const schema = JSON.parse(
|
|
125
|
+
// All values hardcoded.
|
|
126
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
98
127
|
fs.readFileSync(
|
|
99
128
|
path.join(__dirname, '../', 'schemas', 'pageanrc.schema.json')
|
|
100
129
|
)
|
|
101
130
|
);
|
|
131
|
+
// Allow allErrors to lint the entire config file, although users could
|
|
132
|
+
// ReDoS themselves.
|
|
133
|
+
// nosemgrep: ajv-allerrors-true
|
|
102
134
|
const ajv = new Ajv({ allErrors: true });
|
|
135
|
+
ajv.addMetaSchema(draft7MetaSchema);
|
|
103
136
|
ajvErrors(ajv);
|
|
104
137
|
const validate = ajv.compile(schema);
|
|
105
138
|
const isValid = validate(config);
|
|
106
|
-
return {
|
|
139
|
+
return { errors: validate.errors || [], isValid };
|
|
107
140
|
};
|
|
108
141
|
|
|
109
|
-
const getConfigFromFile = (configFileName) =>
|
|
110
|
-
|
|
111
|
-
|
|
142
|
+
const getConfigFromFile = (configFileName) =>
|
|
143
|
+
// Allow users to specify config filename.
|
|
144
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
145
|
+
JSON.parse(fs.readFileSync(configFileName, 'utf8'));
|
|
112
146
|
|
|
113
147
|
/**
|
|
114
148
|
* Loads config from file and returns consolidated config with
|
|
@@ -116,10 +150,12 @@ const getConfigFromFile = (configFileName) => {
|
|
|
116
150
|
*
|
|
117
151
|
* @param {string} configFileName Pagean configuration file name.
|
|
118
152
|
* @returns {object} Consolidated Pagean configuration.
|
|
153
|
+
* @async
|
|
119
154
|
* @throws {TypeError} Throws if config file has an invalid schema.
|
|
120
155
|
* @static
|
|
156
|
+
* @public
|
|
121
157
|
*/
|
|
122
|
-
const processConfig = (configFileName) => {
|
|
158
|
+
const processConfig = async (configFileName) => {
|
|
123
159
|
const config = getConfigFromFile(configFileName);
|
|
124
160
|
const { isValid } = validateConfigSchema(config);
|
|
125
161
|
if (!isValid) {
|
|
@@ -128,13 +164,16 @@ const processConfig = (configFileName) => {
|
|
|
128
164
|
);
|
|
129
165
|
}
|
|
130
166
|
return {
|
|
131
|
-
project: config.project || '',
|
|
132
|
-
puppeteerLaunchOptions: config.puppeteerLaunchOptions,
|
|
133
|
-
urls: processUrls(config),
|
|
134
167
|
htmlHintConfig: getHtmlHintConfig(
|
|
135
168
|
config.htmlhintrc || defaultHtmlHintConfigFilename
|
|
136
169
|
),
|
|
137
|
-
|
|
170
|
+
project: config.project || '',
|
|
171
|
+
puppeteerLaunchOptions: {
|
|
172
|
+
...defaultConfig.puppeteerLaunchOptions,
|
|
173
|
+
...config.puppeteerLaunchOptions
|
|
174
|
+
},
|
|
175
|
+
reporters: config.reporters || defaultConfig.reporters,
|
|
176
|
+
urls: await processUrls(config)
|
|
138
177
|
};
|
|
139
178
|
};
|
|
140
179
|
|
package/lib/default-config.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module external-file-utils
|
|
7
7
|
*/
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
10
|
|
|
11
11
|
const axios = require('axios');
|
|
12
12
|
|
|
@@ -48,10 +48,16 @@ const saveExternalScript = async (script) => {
|
|
|
48
48
|
scriptUrl.hostname,
|
|
49
49
|
scriptUrl.pathname
|
|
50
50
|
);
|
|
51
|
+
// Path generated above from URL, not from user input.
|
|
52
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
51
53
|
if (!fs.existsSync(pathName)) {
|
|
52
54
|
// Axios will throw for any error response
|
|
53
55
|
const response = await axios.get(script);
|
|
56
|
+
// Path generated above from URL, not from user input.
|
|
57
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
54
58
|
fs.mkdirSync(path.dirname(pathName), { recursive: true });
|
|
59
|
+
// Path generated above from URL, not from user input.
|
|
60
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
55
61
|
fs.writeFileSync(pathName, response.data);
|
|
56
62
|
}
|
|
57
63
|
result.localFile = pathName;
|
package/lib/link-utils.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* @module link-utils
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const https = require('https');
|
|
9
|
+
const https = require('node:https');
|
|
10
10
|
const axios = require('axios');
|
|
11
11
|
const normalizeUrl = require('normalize-url');
|
|
12
12
|
const cssesc = require('cssesc');
|
|
@@ -24,6 +24,7 @@ const timeoutSeconds = 120;
|
|
|
24
24
|
const httpResponse = Object.freeze({
|
|
25
25
|
continue: 100,
|
|
26
26
|
ok: 200,
|
|
27
|
+
// eslint-disable-next-line sort-keys -- order by response code
|
|
27
28
|
badRequest: 400,
|
|
28
29
|
notFound: 404,
|
|
29
30
|
tooManyRequests: 429,
|
|
@@ -52,7 +53,6 @@ const normalizeLink = (url) =>
|
|
|
52
53
|
stripHash: true,
|
|
53
54
|
stripWWW: false
|
|
54
55
|
});
|
|
55
|
-
/* eslint-enable jsdoc/require-description-complete-sentence */
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Checks settings to determine if the provided link should be ignored.
|
|
@@ -99,7 +99,6 @@ const checkSamePageLink = async (page, link) => {
|
|
|
99
99
|
isIdentifier: true
|
|
100
100
|
})}`;
|
|
101
101
|
const element = await page.$(escapedSelector);
|
|
102
|
-
// const element = await page.$(selector);
|
|
103
102
|
return element ? httpResponse.ok : `${selector} Not Found`;
|
|
104
103
|
};
|
|
105
104
|
|
|
@@ -122,7 +121,7 @@ const checkExternalPageLinkBrowser = async (page, link) => {
|
|
|
122
121
|
} catch (error) {
|
|
123
122
|
// Errors are returned in the format: "ENOTFOUND at https://this.url.does.not.exist/",
|
|
124
123
|
// so extract error only and remove URL
|
|
125
|
-
status = error.message.replace(/^(
|
|
124
|
+
status = error.message.replace(/^(?<message>.*) at .*$/, '$1');
|
|
126
125
|
}
|
|
127
126
|
return status;
|
|
128
127
|
};
|
|
@@ -149,15 +148,16 @@ const checkExternalPageLink = async (page, link, useGet = false) => {
|
|
|
149
148
|
|
|
150
149
|
try {
|
|
151
150
|
const options = {
|
|
151
|
+
decompress: false,
|
|
152
152
|
headers: { 'User-Agent': userAgent },
|
|
153
|
-
timeout: timeoutSeconds * msPerSec
|
|
153
|
+
timeout: timeoutSeconds * msPerSec
|
|
154
154
|
// Axios can generate an error trying to decompress an empty compressed
|
|
155
155
|
// HEAD response, see https://github.com/axios/axios/issues/5102.
|
|
156
156
|
// The response body is also not used, so more efficient to skip.
|
|
157
|
-
decompress: false
|
|
158
157
|
};
|
|
159
158
|
// Using internal browser property since not exposed
|
|
160
|
-
|
|
159
|
+
/* eslint-disable-next-line no-underscore-dangle -- require to match
|
|
160
|
+
Puppeteer API */
|
|
161
161
|
if (page.browser()._ignoreHTTPSErrors) {
|
|
162
162
|
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
163
163
|
options.httpsAgent = agent;
|
|
@@ -184,7 +184,7 @@ const checkExternalPageLink = async (page, link, useGet = false) => {
|
|
|
184
184
|
};
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
|
-
* Factory function returning a linkChecker object with a
|
|
187
|
+
* Factory function returning a linkChecker object with a checkLink
|
|
188
188
|
* function that caches checked link results.
|
|
189
189
|
*
|
|
190
190
|
* @returns {object} Link checker object.
|
|
@@ -205,7 +205,7 @@ const createLinkChecker = () => {
|
|
|
205
205
|
* @returns {(string|number)} The link status (HTTP response code or error).
|
|
206
206
|
* @public
|
|
207
207
|
*/
|
|
208
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
208
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- allow < 10
|
|
209
209
|
checkLink: async (context, link) => {
|
|
210
210
|
let status = httpResponse.unknownError;
|
|
211
211
|
try {
|
package/lib/logger.js
CHANGED
|
@@ -10,8 +10,8 @@ const consoleLogger = require('ci-logger');
|
|
|
10
10
|
const { reporterTypes } = require('../lib/reporter');
|
|
11
11
|
|
|
12
12
|
const testResultSymbols = Object.freeze({
|
|
13
|
-
passed: ' √',
|
|
14
13
|
failed: ' ×',
|
|
14
|
+
passed: ' √',
|
|
15
15
|
warning: ' ‼'
|
|
16
16
|
});
|
|
17
17
|
|
|
@@ -25,7 +25,7 @@ const nullFunction = () => {};
|
|
|
25
25
|
* @returns {object} A logger object.
|
|
26
26
|
* @static
|
|
27
27
|
*/
|
|
28
|
-
// eslint-disable-next-line max-lines-per-function
|
|
28
|
+
// eslint-disable-next-line max-lines-per-function -- factory function with state
|
|
29
29
|
module.exports = (config) => {
|
|
30
30
|
const cliReporter = {
|
|
31
31
|
// If cli reporter is enabled, set console log function, otherwise null function
|
|
@@ -38,15 +38,15 @@ module.exports = (config) => {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const testResults = {
|
|
41
|
-
project: config.project,
|
|
42
41
|
executionStart: new Date(),
|
|
42
|
+
project: config.project,
|
|
43
|
+
results: [],
|
|
43
44
|
summary: {
|
|
44
|
-
|
|
45
|
+
failed: 0,
|
|
45
46
|
passed: 0,
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
results: []
|
|
47
|
+
tests: 0,
|
|
48
|
+
warning: 0
|
|
49
|
+
}
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
let currentUrlTests;
|
|
@@ -59,8 +59,8 @@ module.exports = (config) => {
|
|
|
59
59
|
*/
|
|
60
60
|
const startUrlTests = (url) => {
|
|
61
61
|
const urlTestResults = {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
tests: [],
|
|
63
|
+
url
|
|
64
64
|
};
|
|
65
65
|
testResults.results.push(urlTestResults);
|
|
66
66
|
currentUrlTests = urlTestResults;
|
|
@@ -72,7 +72,7 @@ module.exports = (config) => {
|
|
|
72
72
|
* by the last call to StartUrlTests).
|
|
73
73
|
*
|
|
74
74
|
* @instance
|
|
75
|
-
* @param {object} testResult The test results.
|
|
75
|
+
* @param {object} testResult The test results object.
|
|
76
76
|
*/
|
|
77
77
|
const logTestResults = (testResult) => {
|
|
78
78
|
if (testResult) {
|
|
@@ -81,8 +81,8 @@ module.exports = (config) => {
|
|
|
81
81
|
testResults.summary[testResult.result]++;
|
|
82
82
|
|
|
83
83
|
cliReporter.log({
|
|
84
|
-
message: `${testResult.name} (${testResult.result})`,
|
|
85
84
|
isResult: true,
|
|
85
|
+
message: `${testResult.name} (${testResult.result})`,
|
|
86
86
|
resultPrefix: testResultSymbols[testResult.result]
|
|
87
87
|
});
|
|
88
88
|
}
|
|
@@ -107,8 +107,8 @@ module.exports = (config) => {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
return {
|
|
110
|
-
|
|
110
|
+
getTestResults,
|
|
111
111
|
logTestResults,
|
|
112
|
-
|
|
112
|
+
startUrlTests
|
|
113
113
|
};
|
|
114
114
|
};
|
|
@@ -4,25 +4,40 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<title>Pagean Results</title>
|
|
6
6
|
<style>
|
|
7
|
+
:root {
|
|
8
|
+
/* Default colors */
|
|
9
|
+
--color-text-default: #333333;
|
|
10
|
+
--color-background-data-hover: rgba(255 255 255 / 10%);
|
|
11
|
+
--color-background-summary: #f4f4f4;
|
|
12
|
+
--color-border-summary: #333333;
|
|
13
|
+
|
|
14
|
+
/* Test result colors */
|
|
15
|
+
--color-background-passed: #dff2bf;
|
|
16
|
+
--color-text-passed: #44760f;
|
|
17
|
+
--color-background-failed: #ffbaba;
|
|
18
|
+
--color-text-failed: #ad000c;
|
|
19
|
+
--color-background-warning: #fdec96;
|
|
20
|
+
--color-text-warning: #7e6902;
|
|
21
|
+
}
|
|
22
|
+
|
|
7
23
|
html {
|
|
8
24
|
margin: 0;
|
|
9
25
|
padding: 0;
|
|
10
26
|
}
|
|
11
27
|
|
|
12
28
|
body {
|
|
13
|
-
color:
|
|
14
|
-
font-family:
|
|
29
|
+
color: var(--color-text-default);
|
|
30
|
+
font-family: system-ui, sans-serif;
|
|
15
31
|
font-size: 0.85rem;
|
|
16
32
|
margin: auto;
|
|
17
|
-
max-
|
|
33
|
+
max-inline-size: 1000px;
|
|
18
34
|
padding: 1rem;
|
|
19
35
|
}
|
|
20
36
|
|
|
21
37
|
ul {
|
|
22
|
-
margin: 0;
|
|
23
38
|
margin-block: 0;
|
|
24
39
|
margin-inline: 0;
|
|
25
|
-
padding: 0;
|
|
40
|
+
padding-block: 0;
|
|
26
41
|
padding-inline: 0;
|
|
27
42
|
}
|
|
28
43
|
|
|
@@ -31,7 +46,6 @@
|
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
h1 {
|
|
34
|
-
margin: 1rem 0 0.75rem;
|
|
35
49
|
margin-block: 0;
|
|
36
50
|
margin-inline: 0;
|
|
37
51
|
}
|
|
@@ -48,62 +62,68 @@
|
|
|
48
62
|
.project,
|
|
49
63
|
.started {
|
|
50
64
|
margin: 0;
|
|
51
|
-
padding-
|
|
65
|
+
padding-block-end: 0.5rem;
|
|
52
66
|
}
|
|
53
67
|
|
|
54
68
|
.test-summary {
|
|
55
69
|
display: flex;
|
|
56
70
|
flex-direction: row;
|
|
57
|
-
margin-
|
|
58
|
-
max-
|
|
71
|
+
margin-block-end: 2rem;
|
|
72
|
+
max-inline-size: 50%;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
.summary {
|
|
62
|
-
background-color:
|
|
63
|
-
border: 1px solid
|
|
76
|
+
background-color: var(--color-background-summary);
|
|
77
|
+
border: 1px solid var(--color-border-summary);
|
|
64
78
|
display: inline-block;
|
|
65
79
|
flex: 1 1 10%;
|
|
66
|
-
margin: 0
|
|
67
|
-
|
|
80
|
+
margin-block: 0;
|
|
81
|
+
margin-inline: 0.25rem;
|
|
82
|
+
padding-block: 0.25rem;
|
|
83
|
+
padding-inline: 0.5rem;
|
|
68
84
|
}
|
|
69
85
|
|
|
70
86
|
.summary:first-of-type {
|
|
71
|
-
margin-
|
|
87
|
+
margin-inline-start: 0;
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
.summary:last-of-type {
|
|
75
|
-
margin-
|
|
91
|
+
margin-inline-end: 0;
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
.passed {
|
|
79
|
-
background-color:
|
|
80
|
-
border: 1px solid
|
|
81
|
-
color:
|
|
95
|
+
background-color: var(--color-background-passed);
|
|
96
|
+
border: 1px solid var(--color-text-passed);
|
|
97
|
+
color: var(--color-text-passed);
|
|
82
98
|
}
|
|
83
99
|
|
|
84
100
|
.failed {
|
|
85
|
-
background-color:
|
|
86
|
-
border: 1px solid
|
|
87
|
-
color:
|
|
101
|
+
background-color: var(--color-background-failed);
|
|
102
|
+
border: 1px solid var(--color-text-failed);
|
|
103
|
+
color: var(--color-text-failed);
|
|
88
104
|
}
|
|
89
105
|
|
|
90
106
|
.warning {
|
|
91
|
-
background-color:
|
|
92
|
-
border: 1px solid
|
|
93
|
-
color:
|
|
107
|
+
background-color: var(--color-background-warning);
|
|
108
|
+
border: 1px solid var(--color-text-warning);
|
|
109
|
+
color: var(--color-text-warning);
|
|
94
110
|
}
|
|
95
111
|
|
|
96
112
|
.test-results h2 {
|
|
97
|
-
margin-block
|
|
98
|
-
margin-block-start: 1rem;
|
|
113
|
+
margin-block: 1rem 0;
|
|
99
114
|
}
|
|
100
115
|
|
|
101
|
-
details summary .name::after
|
|
102
|
-
|
|
116
|
+
details summary .name::after,
|
|
117
|
+
details[open] summary .name::after {
|
|
118
|
+
content: "+";
|
|
119
|
+
font-family: monospace;
|
|
120
|
+
font-weight: bold;
|
|
121
|
+
opacity: 0.8;
|
|
122
|
+
padding-inline-start: 0.5rem;
|
|
103
123
|
}
|
|
104
124
|
|
|
105
125
|
details[open] summary .name::after {
|
|
106
|
-
content: "
|
|
126
|
+
content: "–";
|
|
107
127
|
}
|
|
108
128
|
|
|
109
129
|
summary {
|
|
@@ -116,8 +136,10 @@
|
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
li.test {
|
|
119
|
-
margin: 0.25rem
|
|
120
|
-
|
|
139
|
+
margin-block: 0.25rem;
|
|
140
|
+
margin-inline: 0;
|
|
141
|
+
padding-block: 0.5rem;
|
|
142
|
+
padding-inline: 1rem;
|
|
121
143
|
}
|
|
122
144
|
|
|
123
145
|
.test .header {
|
|
@@ -131,20 +153,20 @@
|
|
|
131
153
|
|
|
132
154
|
.test .header .result {
|
|
133
155
|
flex: 1 1 25%;
|
|
134
|
-
text-align:
|
|
156
|
+
text-align: end;
|
|
135
157
|
}
|
|
136
158
|
|
|
137
159
|
.test .data,
|
|
138
160
|
.test .time,
|
|
139
161
|
.test .error {
|
|
140
|
-
padding-
|
|
162
|
+
padding-inline-start: 0.75rem;
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
.test .data h3 {
|
|
144
166
|
font-size: inherit;
|
|
145
167
|
font-weight: normal;
|
|
146
|
-
margin: 0.25rem 0 0;
|
|
147
168
|
margin-block: 0;
|
|
169
|
+
margin-inline: 0;
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
.test .data li {
|
|
@@ -154,18 +176,20 @@
|
|
|
154
176
|
}
|
|
155
177
|
|
|
156
178
|
.test .data li:hover {
|
|
157
|
-
background-color:
|
|
179
|
+
background-color: var(--color-background-data-hover);
|
|
158
180
|
border: 1px dotted;
|
|
159
|
-
border-
|
|
181
|
+
border-inline-end: 4px solid;
|
|
160
182
|
padding: calc(0.25rem - 1px);
|
|
161
183
|
}
|
|
162
184
|
|
|
163
185
|
.test .data .pre {
|
|
164
|
-
margin: 0 0
|
|
186
|
+
margin-block: 0 0.25rem;
|
|
187
|
+
margin-inline: 0;
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
.test .time {
|
|
168
|
-
margin: 0.25rem 0
|
|
191
|
+
margin-block: 0.25rem 0;
|
|
192
|
+
margin-inline: 0;
|
|
169
193
|
}
|
|
170
194
|
</style>
|
|
171
195
|
</head>
|
|
@@ -192,7 +216,7 @@
|
|
|
192
216
|
<li class="test {{result}}">
|
|
193
217
|
{{#if data}}
|
|
194
218
|
<details>
|
|
195
|
-
<summary class="header">
|
|
219
|
+
<summary class="header" title="Show/hide details">
|
|
196
220
|
<div class="name">{{name}}</div>
|
|
197
221
|
<div class="result">{{result}}</div>
|
|
198
222
|
</summary>
|
package/lib/reporter.js
CHANGED
|
@@ -5,23 +5,25 @@
|
|
|
5
5
|
*
|
|
6
6
|
* @module reporter
|
|
7
7
|
*/
|
|
8
|
-
const fs = require('fs');
|
|
9
|
-
const path = require('path');
|
|
8
|
+
const fs = require('node:fs');
|
|
9
|
+
const path = require('node:path');
|
|
10
10
|
const handlebars = require('handlebars');
|
|
11
11
|
|
|
12
12
|
const htmlReportTemplateName = 'report-template.handlebars';
|
|
13
13
|
const htmlReportFileName = './pagean-results.html';
|
|
14
14
|
const jsonReportFileName = './pagean-results.json';
|
|
15
15
|
|
|
16
|
-
handlebars.registerHelper('json', (context) =>
|
|
17
|
-
// eslint-disable-next-line no-magic-numbers -- count
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
handlebars.registerHelper('json', (context) =>
|
|
17
|
+
// eslint-disable-next-line no-magic-numbers -- no-magic-numbers - count
|
|
18
|
+
JSON.stringify(context, undefined, 2)
|
|
19
|
+
);
|
|
20
20
|
|
|
21
21
|
const saveHtmlReport = (results) => {
|
|
22
22
|
const templateFile = path.resolve(
|
|
23
23
|
path.join(__dirname, htmlReportTemplateName)
|
|
24
24
|
);
|
|
25
|
+
// Path hardcoded above, not from user input.
|
|
26
|
+
// nosemgrep: eslint.detect-non-literal-fs-filename
|
|
25
27
|
const htmlReportTemplate = fs.readFileSync(templateFile, 'utf8');
|
|
26
28
|
const template = handlebars.compile(htmlReportTemplate);
|
|
27
29
|
const htmlReport = template(results);
|
package/lib/schema-errors.js
CHANGED
|
@@ -24,8 +24,8 @@ const getDataKey = (instancePath) => {
|
|
|
24
24
|
// eslint-disable-next-line unicorn/no-array-reduce -- reduce for string concatenation
|
|
25
25
|
.reduce((accumulator, currentValue) => {
|
|
26
26
|
const unencodedValue = currentValue
|
|
27
|
-
.
|
|
28
|
-
.
|
|
27
|
+
.replaceAll('~0', '~')
|
|
28
|
+
.replaceAll('~1', '/');
|
|
29
29
|
const encodedValue = Number.isNaN(Number(unencodedValue))
|
|
30
30
|
? `.${unencodedValue}`
|
|
31
31
|
: `[${unencodedValue}]`;
|