@chrissgon/light-site-auditor 0.1.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 +21 -0
- package/README.md +126 -0
- package/dist/a11y.js +44 -0
- package/dist/cli.js +130 -0
- package/dist/consent.js +94 -0
- package/dist/explanations.js +42 -0
- package/dist/explanations.pt.json +619 -0
- package/dist/lighthouse.js +47 -0
- package/dist/profile.js +19 -0
- package/dist/report.js +241 -0
- package/dist/weight.js +32 -0
- package/docs/field/consentimento.md +45 -0
- package/package.json +66 -0
package/dist/profile.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The 3G profile every audit uses: Lighthouse's `mobileRegular3G` preset, copied from its source so
|
|
3
|
+
* the numbers are visible here. Source and access date: docs/spikes/3g.md.
|
|
4
|
+
* tests/profile.test.ts fails if the installed Lighthouse ever defines the preset differently.
|
|
5
|
+
*/
|
|
6
|
+
export const REGULAR_3G = {
|
|
7
|
+
/** Round-trip time between the phone and the server, in milliseconds. */
|
|
8
|
+
rttMs: 300,
|
|
9
|
+
/** Download and upload bandwidth, in kilobits per second. */
|
|
10
|
+
throughputKbps: 700,
|
|
11
|
+
/** The DevTools-throttling equivalents (not used by simulated throttling; kept for completeness). */
|
|
12
|
+
requestLatencyMs: 300 * 3.75,
|
|
13
|
+
downloadThroughputKbps: 700 * 0.9,
|
|
14
|
+
uploadThroughputKbps: 700 * 0.9,
|
|
15
|
+
/** The phone's processor is emulated as this many times slower than the machine running the audit. */
|
|
16
|
+
cpuSlowdownMultiplier: 4,
|
|
17
|
+
};
|
|
18
|
+
/** Lighthouse's own throttling method by default: a simulation from one unthrottled load. */
|
|
19
|
+
export const THROTTLING_METHOD = "simulate";
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { CATALOG, explainAxe, explainLighthouse, fill, glossaryClosure } from "./explanations.js";
|
|
2
|
+
import { FILE_TYPES, weightByType } from "./weight.js";
|
|
3
|
+
const T = CATALOG.report;
|
|
4
|
+
const METRICS = ["first-contentful-paint", "largest-contentful-paint"];
|
|
5
|
+
/** Lighthouse's own threshold: an audit scoring below 0.9 is not passed. */
|
|
6
|
+
const PASS = 0.9;
|
|
7
|
+
/** One decimal, Portuguese decimal comma: 4202.484 ms → "4,2" seconds; 200211 bytes → "200,2" KB. */
|
|
8
|
+
export const oneDecimal = (n) => (Math.round(n * 10) / 10).toFixed(1).replace(".", ",");
|
|
9
|
+
const seconds = (ms) => oneDecimal(ms / 1000);
|
|
10
|
+
const kb = (bytes) => oneDecimal(bytes / 1000);
|
|
11
|
+
/** The Lighthouse insights and diagnostics that did not pass, largest possible gain first. */
|
|
12
|
+
export function lighthouseProblems(lhr) {
|
|
13
|
+
const refs = lhr.categories.performance?.auditRefs ?? [];
|
|
14
|
+
return refs
|
|
15
|
+
.filter((ref) => ref.group === "insights" || ref.group === "diagnostics")
|
|
16
|
+
.map((ref) => lhr.audits[ref.id])
|
|
17
|
+
.filter((a) => a && a.score !== null && a.score < PASS && ["binary", "numeric", "metricSavings"].includes(a.scoreDisplayMode))
|
|
18
|
+
.map((audit) => ({ audit, lcp: audit.metricSavings?.LCP ?? 0, fcp: audit.metricSavings?.FCP ?? 0 }))
|
|
19
|
+
.sort((a, b) => b.lcp - a.lcp || b.fcp - a.fcp);
|
|
20
|
+
}
|
|
21
|
+
function explanationBlocks(e, heading, extra = []) {
|
|
22
|
+
if (!e.explained) {
|
|
23
|
+
return [
|
|
24
|
+
{ h: 3, text: [...heading, ` (${e.mark})`] },
|
|
25
|
+
{ p: [T.noExplanationNote] },
|
|
26
|
+
{ p: [{ strong: `${T.original}:` }, " ", { code: e.original }] },
|
|
27
|
+
...extra.map((p) => ({ p })),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
return [
|
|
31
|
+
{ h: 3, text: heading },
|
|
32
|
+
{ p: [{ strong: `${T.whatItIs}:` }, ` ${e.what}`] },
|
|
33
|
+
{ p: [{ strong: `${T.whatToDo}:` }, ` ${e.fix}`] },
|
|
34
|
+
...extra.map((p) => ({ p })),
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
export function buildBlocks(lhr, axe, files) {
|
|
38
|
+
const weight = weightByType(lhr);
|
|
39
|
+
const throttling = lhr.configSettings.throttling;
|
|
40
|
+
const blocks = [
|
|
41
|
+
{ h: 1, text: [T.title] },
|
|
42
|
+
{ p: [`${T.intro} ${T.introSources} ${T.introTerms}`] },
|
|
43
|
+
{
|
|
44
|
+
ul: [
|
|
45
|
+
[{ strong: `${T.page}:` }, " ", { code: lhr.finalDisplayedUrl }],
|
|
46
|
+
[{ strong: `${T.measuredAt}:` }, " ", { code: lhr.fetchTime }],
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
// Weight by file type, heaviest first; types with no file are left out.
|
|
51
|
+
const types = FILE_TYPES.filter((t) => weight.byType[t].files > 0).sort((a, b) => weight.byType[b].transferBytes - weight.byType[a].transferBytes);
|
|
52
|
+
blocks.push({ h: 2, text: [T.weightHeading] }, { p: [T.weightIntro] }, { p: [{ strong: fill(T.weightTotal, { kb: kb(weight.total.transferBytes), files: weight.total.files }) }] }, {
|
|
53
|
+
table: {
|
|
54
|
+
head: [T.weightType, T.weightFiles, T.weightSize],
|
|
55
|
+
rows: types.map((t) => [[CATALOG.fileTypes[t]], [String(weight.byType[t].files)], [kb(weight.byType[t].transferBytes)]]),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
// Load times on the 3G profile.
|
|
59
|
+
blocks.push({ h: 2, text: [T.loadHeading] }, { p: [T.loadIntro] }, {
|
|
60
|
+
ul: METRICS.map((id) => [
|
|
61
|
+
{ strong: fill(T.loadMetric, { title: CATALOG.metrics[id].title, seconds: seconds(lhr.audits[id].numericValue) }) },
|
|
62
|
+
` ${CATALOG.metrics[id].what}`,
|
|
63
|
+
]),
|
|
64
|
+
}, {
|
|
65
|
+
p: [
|
|
66
|
+
fill(T.loadProfile, {
|
|
67
|
+
rtt: throttling.rttMs ?? "",
|
|
68
|
+
kbps: throttling.throughputKbps ?? "",
|
|
69
|
+
cpu: throttling.cpuSlowdownMultiplier ?? "",
|
|
70
|
+
}),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
// What makes the page heavy or slow.
|
|
74
|
+
const problems = lighthouseProblems(lhr);
|
|
75
|
+
blocks.push({ h: 2, text: [T.speedHeading] });
|
|
76
|
+
if (problems.length === 0)
|
|
77
|
+
blocks.push({ p: [T.speedNone] });
|
|
78
|
+
for (const { audit, lcp, fcp } of problems) {
|
|
79
|
+
const e = explainLighthouse(audit.id, audit.title);
|
|
80
|
+
const gain = lcp > 0 ? [[fill(T.speedGainLcp, { seconds: seconds(lcp) })]] : fcp > 0 ? [[fill(T.speedGainFcp, { seconds: seconds(fcp) })]] : [];
|
|
81
|
+
blocks.push(...explanationBlocks(e, [e.title], gain));
|
|
82
|
+
}
|
|
83
|
+
// Accessibility barriers.
|
|
84
|
+
const places = axe.violations.reduce((n, v) => n + v.nodes.length, 0);
|
|
85
|
+
blocks.push({ h: 2, text: [T.a11yHeading] }, { p: [T.a11yIntro] });
|
|
86
|
+
if (axe.violations.length === 0)
|
|
87
|
+
blocks.push({ p: [T.a11yNone] });
|
|
88
|
+
else
|
|
89
|
+
blocks.push({ p: [{ strong: fill(T.a11yCount, { rules: axe.violations.length, places }) }] });
|
|
90
|
+
for (const v of axe.violations) {
|
|
91
|
+
const e = explainAxe(v.id, v.help);
|
|
92
|
+
const impact = v.impact ? CATALOG.impact[v.impact] : undefined;
|
|
93
|
+
const shown = v.nodes.slice(0, 5);
|
|
94
|
+
blocks.push(...explanationBlocks(e, [e.title], [
|
|
95
|
+
...(impact ? [[{ strong: `${T.severity}:` }, ` ${impact}`]] : []),
|
|
96
|
+
[v.nodes.length === 1 ? T.a11yPlacesOne : fill(T.a11yPlaces, { places: v.nodes.length })],
|
|
97
|
+
[{ strong: `${T.a11yWhere}:` }],
|
|
98
|
+
]), { ul: shown.map((n) => [{ code: n.html }]) });
|
|
99
|
+
if (v.nodes.length > shown.length)
|
|
100
|
+
blocks.push({ p: [T.a11yMore] });
|
|
101
|
+
}
|
|
102
|
+
blocks.push({ p: [T.a11yNote] });
|
|
103
|
+
// Glossary: every term the prose above uses, and the terms its definitions use.
|
|
104
|
+
const prose = blocks.flatMap(textOf);
|
|
105
|
+
const terms = glossaryClosure([...prose, T.termsHeading, T.sourcesHeading, T.sourcesIntro]);
|
|
106
|
+
blocks.push({ h: 2, text: [T.termsHeading] }, { ul: terms.map((t) => [{ strong: `${t}:` }, ` ${CATALOG.glossary[t]}`]) });
|
|
107
|
+
// Where every number comes from.
|
|
108
|
+
const lh = { code: files.lighthouse };
|
|
109
|
+
blocks.push({ h: 2, text: [T.sourcesHeading] }, { p: [T.sourcesIntro] }, {
|
|
110
|
+
ul: [
|
|
111
|
+
[
|
|
112
|
+
`${T.sourcesWeight} `,
|
|
113
|
+
lh,
|
|
114
|
+
", ",
|
|
115
|
+
{ code: 'audits["network-requests"].details.items[].transferSize' },
|
|
116
|
+
", ",
|
|
117
|
+
{ code: "resourceType" },
|
|
118
|
+
": ",
|
|
119
|
+
types.map((t) => `${CATALOG.fileTypes[t]} ${weight.byType[t].transferBytes}`).join("; "),
|
|
120
|
+
".",
|
|
121
|
+
],
|
|
122
|
+
[`${T.sourcesTotal}, `, { code: 'audits["total-byte-weight"].numericValue' }, `: ${lhr.audits["total-byte-weight"].numericValue}.`],
|
|
123
|
+
[
|
|
124
|
+
`${T.sourcesTime}, `,
|
|
125
|
+
...METRICS.flatMap((id, i) => [
|
|
126
|
+
i ? "; " : "",
|
|
127
|
+
{ code: `audits["${id}"].numericValue` },
|
|
128
|
+
`: ${lhr.audits[id].numericValue}`,
|
|
129
|
+
]),
|
|
130
|
+
".",
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
`${T.sourcesProfile}, `,
|
|
134
|
+
{ code: "configSettings.throttling" },
|
|
135
|
+
": ",
|
|
136
|
+
{ code: "rttMs" },
|
|
137
|
+
` ${throttling.rttMs}, `,
|
|
138
|
+
{ code: "throughputKbps" },
|
|
139
|
+
` ${throttling.throughputKbps}, `,
|
|
140
|
+
{ code: "cpuSlowdownMultiplier" },
|
|
141
|
+
` ${throttling.cpuSlowdownMultiplier}.`,
|
|
142
|
+
],
|
|
143
|
+
...(problems.some((p) => p.lcp > 0 || p.fcp > 0)
|
|
144
|
+
? [
|
|
145
|
+
[
|
|
146
|
+
`${T.sourcesGain}, `,
|
|
147
|
+
...problems
|
|
148
|
+
.filter((p) => p.lcp > 0 || p.fcp > 0)
|
|
149
|
+
.flatMap((p, i) => {
|
|
150
|
+
const metric = p.lcp > 0 ? "LCP" : "FCP";
|
|
151
|
+
return [i ? "; " : "", { code: `audits["${p.audit.id}"].metricSavings.${metric}` }, `: ${p.lcp > 0 ? p.lcp : p.fcp}`];
|
|
152
|
+
}),
|
|
153
|
+
".",
|
|
154
|
+
],
|
|
155
|
+
]
|
|
156
|
+
: []),
|
|
157
|
+
[`${T.sourcesA11y}, `, { code: files.axe }, ", ", { code: "violations[].nodes" }, "."],
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
return blocks;
|
|
161
|
+
}
|
|
162
|
+
/** The plain text of a block, without code spans (what a reader reads as prose). */
|
|
163
|
+
function textOf(block) {
|
|
164
|
+
const inline = (parts) => parts.map((p) => (typeof p === "string" ? p : "strong" in p ? p.strong : "")).join("");
|
|
165
|
+
if ("h" in block)
|
|
166
|
+
return [inline(block.text)];
|
|
167
|
+
if ("p" in block)
|
|
168
|
+
return [inline(block.p)];
|
|
169
|
+
if ("ul" in block)
|
|
170
|
+
return block.ul.map(inline);
|
|
171
|
+
return [...block.table.head, ...block.table.rows.flatMap((row) => row.map(inline))];
|
|
172
|
+
}
|
|
173
|
+
// ---- Markdown -------------------------------------------------------------------------------------
|
|
174
|
+
const mdCode = (s) => {
|
|
175
|
+
const flat = s.replace(/\s*\n\s*/g, " ");
|
|
176
|
+
return flat.includes("`") ? `\`\` ${flat} \`\`` : `\`${flat}\``;
|
|
177
|
+
};
|
|
178
|
+
const mdInline = (parts) => parts.map((p) => (typeof p === "string" ? p : "code" in p ? mdCode(p.code) : `**${p.strong}**`)).join("");
|
|
179
|
+
export function toMarkdown(blocks) {
|
|
180
|
+
const out = [];
|
|
181
|
+
for (const b of blocks) {
|
|
182
|
+
if ("h" in b)
|
|
183
|
+
out.push(`${"#".repeat(b.h)} ${mdInline(b.text)}`);
|
|
184
|
+
else if ("p" in b)
|
|
185
|
+
out.push(mdInline(b.p));
|
|
186
|
+
else if ("ul" in b)
|
|
187
|
+
out.push(b.ul.map((item) => `- ${mdInline(item)}`).join("\n"));
|
|
188
|
+
else {
|
|
189
|
+
const row = (cells) => `| ${cells.join(" | ")} |`;
|
|
190
|
+
out.push([row(b.table.head), row(b.table.head.map(() => "---")), ...b.table.rows.map((r) => row(r.map(mdInline)))].join("\n"));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return `${out.join("\n\n")}\n`;
|
|
194
|
+
}
|
|
195
|
+
// ---- HTML -----------------------------------------------------------------------------------------
|
|
196
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
197
|
+
const htmlInline = (parts) => parts
|
|
198
|
+
.map((p) => (typeof p === "string" ? esc(p) : "code" in p ? `<code>${esc(p.code)}</code>` : `<strong>${esc(p.strong)}</strong>`))
|
|
199
|
+
.join("");
|
|
200
|
+
const STYLE = `
|
|
201
|
+
body { font: 1.0625rem/1.6 system-ui, sans-serif; color: #1a1a1a; background: #ffffff; max-width: 46rem; margin: 0 auto; padding: 1rem; }
|
|
202
|
+
h1, h2, h3 { line-height: 1.25; }
|
|
203
|
+
h2 { margin-top: 2.5rem; border-bottom: 1px solid #d0d0d0; padding-bottom: 0.25rem; }
|
|
204
|
+
code { font-size: 0.9em; background: #f2f2f2; padding: 0.1em 0.3em; border-radius: 0.2em; overflow-wrap: anywhere; }
|
|
205
|
+
table { border-collapse: collapse; width: 100%; }
|
|
206
|
+
th, td { text-align: left; padding: 0.4rem 0.6rem; border-bottom: 1px solid #d0d0d0; }
|
|
207
|
+
`;
|
|
208
|
+
export function toHtml(blocks) {
|
|
209
|
+
const body = blocks.map((b) => {
|
|
210
|
+
if ("h" in b)
|
|
211
|
+
return `<h${b.h}>${htmlInline(b.text)}</h${b.h}>`;
|
|
212
|
+
if ("p" in b)
|
|
213
|
+
return `<p>${htmlInline(b.p)}</p>`;
|
|
214
|
+
if ("ul" in b)
|
|
215
|
+
return `<ul>\n${b.ul.map((item) => `<li>${htmlInline(item)}</li>`).join("\n")}\n</ul>`;
|
|
216
|
+
const head = `<tr>${b.table.head.map((h) => `<th scope="col">${esc(h)}</th>`).join("")}</tr>`;
|
|
217
|
+
const rows = b.table.rows.map((r) => `<tr>${r.map((c) => `<td>${htmlInline(c)}</td>`).join("")}</tr>`).join("\n");
|
|
218
|
+
return `<table>\n<thead>${head}</thead>\n<tbody>\n${rows}\n</tbody>\n</table>`;
|
|
219
|
+
});
|
|
220
|
+
return [
|
|
221
|
+
"<!doctype html>",
|
|
222
|
+
'<html lang="pt-BR">',
|
|
223
|
+
"<head>",
|
|
224
|
+
'<meta charset="utf-8">',
|
|
225
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
226
|
+
`<title>${esc(T.title)}</title>`,
|
|
227
|
+
`<style>${STYLE}</style>`,
|
|
228
|
+
"</head>",
|
|
229
|
+
"<body>",
|
|
230
|
+
"<main>",
|
|
231
|
+
...body,
|
|
232
|
+
"</main>",
|
|
233
|
+
"</body>",
|
|
234
|
+
"</html>",
|
|
235
|
+
"",
|
|
236
|
+
].join("\n");
|
|
237
|
+
}
|
|
238
|
+
export function buildReport(lhr, axe, files) {
|
|
239
|
+
const blocks = buildBlocks(lhr, axe, files);
|
|
240
|
+
return { markdown: toMarkdown(blocks), html: toHtml(blocks) };
|
|
241
|
+
}
|
package/dist/weight.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** The file types the report groups bytes by. */
|
|
2
|
+
export const FILE_TYPES = ["html", "css", "js", "image", "font", "other"];
|
|
3
|
+
/** Lighthouse's `resourceType` (Chrome's DevTools resource types) to the report's file types. */
|
|
4
|
+
const TYPE_OF = {
|
|
5
|
+
Document: "html",
|
|
6
|
+
Stylesheet: "css",
|
|
7
|
+
Script: "js",
|
|
8
|
+
Image: "image",
|
|
9
|
+
Font: "font",
|
|
10
|
+
};
|
|
11
|
+
/** The items of the `network-requests` audit: one per request the page made. */
|
|
12
|
+
export function networkItems(lhr) {
|
|
13
|
+
const details = lhr.audits["network-requests"]?.details;
|
|
14
|
+
if (!details?.items)
|
|
15
|
+
throw new Error("the Lighthouse result has no network-requests table");
|
|
16
|
+
return details.items;
|
|
17
|
+
}
|
|
18
|
+
/** Sums the bytes of every request by file type, from Lighthouse's `network-requests` audit. */
|
|
19
|
+
export function weightByType(lhr) {
|
|
20
|
+
const empty = () => ({ files: 0, transferBytes: 0, resourceBytes: 0 });
|
|
21
|
+
const byType = Object.fromEntries(FILE_TYPES.map((type) => [type, empty()]));
|
|
22
|
+
const total = empty();
|
|
23
|
+
for (const item of networkItems(lhr)) {
|
|
24
|
+
const type = TYPE_OF[item.resourceType ?? ""] ?? "other";
|
|
25
|
+
for (const bytes of [byType[type], total]) {
|
|
26
|
+
bytes.files += 1;
|
|
27
|
+
bytes.transferBytes += item.transferSize ?? 0;
|
|
28
|
+
bytes.resourceBytes += item.resourceSize ?? 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { byType, total };
|
|
32
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Consentimento para auditar um site
|
|
2
|
+
|
|
3
|
+
O auditor só verifica um site real depois que o dono aceita. Este arquivo tem o texto do pedido, que pode ser enviado como está, e o modelo do registro que a ferramenta lê antes de auditar (`auditor <url> --consent <arquivo>`).
|
|
4
|
+
|
|
5
|
+
## Texto do pedido
|
|
6
|
+
|
|
7
|
+
> Olá, [nome]. Eu uso uma ferramenta gratuita, o light-site-auditor, que verifica se um site é leve, se abre rápido num celular com internet fraca (3G) e se tem barreiras para pessoas com deficiência. Posso verificar a página inicial de [endereço]?
|
|
8
|
+
>
|
|
9
|
+
> **O que a verificação faz:** abre a página inicial pública do seu site como um visitante comum, duas vezes seguidas, e mede com duas ferramentas abertas, o Lighthouse e o axe, que rodam no meu computador.
|
|
10
|
+
>
|
|
11
|
+
> **O que ela não faz:** não entra em área com login, não preenche nem envia formulários, não abre outras páginas e não gera mais tráfego do que essas duas visitas.
|
|
12
|
+
>
|
|
13
|
+
> Com o resultado, eu escrevo um relatório em português simples, com o que corrigir. O relatório fica só com você, a menos que você concorde com outro uso.
|
|
14
|
+
>
|
|
15
|
+
> Você concorda? Basta responder "sim" ou "não", e você pode mudar de ideia quando quiser.
|
|
16
|
+
|
|
17
|
+
## Registro do consentimento
|
|
18
|
+
|
|
19
|
+
Crie um arquivo por site, por exemplo `docs/field/<site>/consentimento.md`, que comece com este bloco:
|
|
20
|
+
|
|
21
|
+
```markdown
|
|
22
|
+
---
|
|
23
|
+
endereco: https://www.exemplo.com.br/
|
|
24
|
+
dono: Nome de quem aceitou
|
|
25
|
+
status: pendente: aguardando o aceite do dono
|
|
26
|
+
data: -
|
|
27
|
+
como: -
|
|
28
|
+
reacao: pendente
|
|
29
|
+
---
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Quando o dono aceitar, troque `status` por `aceito`, `data` pela data do aceite (AAAA-MM-DD) e `como` pela forma do aceite (por exemplo, "por e-mail, respondendo ao pedido"). Depois de entregar o relatório, anote em `reacao` o que o dono achou.
|
|
33
|
+
|
|
34
|
+
A ferramenta recusa a auditoria quando:
|
|
35
|
+
|
|
36
|
+
- não há registro (`--consent`) e o endereço não é deste computador (`localhost`, `127.0.0.1`, `[::1]`);
|
|
37
|
+
- `status` é qualquer coisa diferente de `aceito`, inclusive `pendente`;
|
|
38
|
+
- o endereço auditado não é exatamente o `endereco` do registro;
|
|
39
|
+
- falta `endereco`, `dono`, `status`, `data` ou `como`, ou a data não está no formato AAAA-MM-DD ou é futura.
|
|
40
|
+
|
|
41
|
+
Não existe opção para pular essa verificação.
|
|
42
|
+
|
|
43
|
+
## O que fica guardado
|
|
44
|
+
|
|
45
|
+
Na pasta do relatório ficam `relatorio.md`, `relatorio.html`, `lighthouse.json` e `axe.json`. Neste repositório, as pastas `docs/field/<site>/` ficam fora do git (`.gitignore`): o relatório é só do dono do site.
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chrissgon/light-site-auditor",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Checks a small business's home page for weight, load time on 3G and accessibility (WCAG 2.1 AA), and explains the fixes in plain Portuguese. Real sites only with the owner's consent.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"accessibility",
|
|
7
|
+
"acessibilidade",
|
|
8
|
+
"wcag",
|
|
9
|
+
"axe",
|
|
10
|
+
"lighthouse",
|
|
11
|
+
"performance",
|
|
12
|
+
"3g",
|
|
13
|
+
"web-performance",
|
|
14
|
+
"audit",
|
|
15
|
+
"auditoria",
|
|
16
|
+
"cli",
|
|
17
|
+
"portuguese"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/chrissgon/light-site-auditor#readme",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/chrissgon/light-site-auditor/issues"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/chrissgon/light-site-auditor.git"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"author": "Christopher Gonçalves",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"bin": {
|
|
31
|
+
"auditor": "dist/cli.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist/",
|
|
35
|
+
"docs/field/consentimento.md"
|
|
36
|
+
],
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22.19"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"provenance": true
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.json",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"typecheck": "tsc -p tsconfig.scripts.json",
|
|
48
|
+
"auditor": "tsx src/cli.ts",
|
|
49
|
+
"fixture": "tsx scripts/serve-fixture.ts",
|
|
50
|
+
"check-report": "tsx scripts/check-report.ts",
|
|
51
|
+
"prepack": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && npm run build"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@axe-core/playwright": "4.13.0",
|
|
55
|
+
"chrome-launcher": "1.2.2",
|
|
56
|
+
"lighthouse": "13.5.0",
|
|
57
|
+
"playwright": "1.63.0"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "24.19.0",
|
|
61
|
+
"axe-core": "4.13.0",
|
|
62
|
+
"tsx": "4.23.15",
|
|
63
|
+
"typescript": "7.0.2",
|
|
64
|
+
"vitest": "5.0.2"
|
|
65
|
+
}
|
|
66
|
+
}
|