pagetrace 0.6.0 → 0.7.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/CHANGELOG.md +22 -0
- package/README.md +16 -5
- package/dist/cli.cjs +86 -17
- package/dist/cli.js +86 -17
- package/dist/index.cjs +84 -15
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +84 -15
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
While the version is below 1.0.0, breaking changes ship in a minor release.
|
|
8
8
|
|
|
9
|
+
## [0.7.0] - 2026-09-06
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- The terminal audit report is laid out rather than printed. Findings sit under a
|
|
14
|
+
rule per severity, prose wraps to the terminal instead of running off the right
|
|
15
|
+
edge, the affected routes get their own line, and the page count is right
|
|
16
|
+
aligned against the headline. The old output put explanation, fix and routes at
|
|
17
|
+
the same indent with no wrapping, so on a real site the useful part was the
|
|
18
|
+
hardest part to find.
|
|
19
|
+
- `formatAuditPretty(groups, meta, columns?)` takes an optional terminal width,
|
|
20
|
+
defaulting to 80. The CLI passes `process.stdout.columns`, which keeps
|
|
21
|
+
`report.ts` free of environment reads. Piped output falls back to 80 and drops
|
|
22
|
+
colour, so it stays readable in a CI log.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- A word longer than the terminal is hard-broken instead of overflowing. A
|
|
27
|
+
canonical URL is a single word and is routinely longer than 80 characters, so
|
|
28
|
+
`duplicate.canonical` could push a line well past the edge.
|
|
29
|
+
|
|
9
30
|
## [0.6.0] - 2026-09-06
|
|
10
31
|
|
|
11
32
|
### Changed
|
|
@@ -216,6 +237,7 @@ Initial release. `snapshot`, `check` and `audit` commands; filesystem and HTTP
|
|
|
216
237
|
crawling; diff classified by transition; absolute, cross-page and hreflang audit
|
|
217
238
|
rules; pretty, JSON, markdown, GitHub and HTML reporters.
|
|
218
239
|
|
|
240
|
+
[0.7.0]: https://github.com/shyamexe/pagetrace/compare/v0.6.0...v0.7.0
|
|
219
241
|
[0.6.0]: https://github.com/shyamexe/pagetrace/compare/v0.5.0...v0.6.0
|
|
220
242
|
[0.5.0]: https://github.com/shyamexe/pagetrace/compare/v0.4.0...v0.5.0
|
|
221
243
|
[0.4.0]: https://github.com/shyamexe/pagetrace/compare/v0.3.0...v0.4.0
|
package/README.md
CHANGED
|
@@ -72,16 +72,27 @@ npx pagetrace audit --url https://example.com --format html --out audit.html
|
|
|
72
72
|
Findings are rolled up by issue rather than by page, so one template defect reads as a single row affecting 43 pages instead of 43 separate lines. Each row carries why it matters and how to fix it, and the fix is platform-aware — `pagetrace` reads the generator tag and asset paths, so a WordPress site gets Yoast and Rank Math instructions rather than generic advice.
|
|
73
73
|
|
|
74
74
|
```
|
|
75
|
-
|
|
75
|
+
pagetrace · https://acme.test
|
|
76
|
+
43 pages · WordPress · 2026-09-06
|
|
77
|
+
|
|
78
|
+
ERRORS ─────────────────────────────────────────────────────────────────────── 2
|
|
79
|
+
|
|
80
|
+
✗ 2 pages canonicalise to https://acme.test/shop. 2 pages
|
|
76
81
|
Several pages pointing at one canonical means those pages are declaring
|
|
77
82
|
themselves duplicates and will not rank independently.
|
|
78
|
-
|
|
83
|
+
→ A common symptom of a plugin canonicalising every archive page to the
|
|
84
|
+
parent.
|
|
85
|
+
/shop/page/2, /shop/page/3
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
✗ Page has no <h1>. 1 page
|
|
81
88
|
The h1 anchors the document outline used for passage extraction.
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
→ Many themes render the post title as h2 inside archive templates. Check
|
|
90
|
+
single.php or the block template for this post type.
|
|
84
91
|
/tag/widgets
|
|
92
|
+
|
|
93
|
+
────────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
2 issues 2 errors
|
|
95
|
+
3 findings across 43 pages
|
|
85
96
|
```
|
|
86
97
|
|
|
87
98
|
Auditing runs cross-page rules the per-page checks cannot see: duplicate titles and descriptions, several pages canonicalising to one URL, canonicals pointing away from their own path or at another host entirely, and a full hreflang check. Paginated archives and AMP variants are left alone, since canonicalising those to their parent is correct.
|
package/dist/cli.cjs
CHANGED
|
@@ -940,33 +940,102 @@ function sampleRoutes(routes, limit = 5) {
|
|
|
940
940
|
const shown = routes.slice(0, limit).join(", ");
|
|
941
941
|
return routes.length > limit ? `${shown} +${routes.length - limit} more` : shown;
|
|
942
942
|
}
|
|
943
|
-
|
|
943
|
+
var GLYPH = { error: "\u2717", warn: "\u25B2", info: "\xB7" };
|
|
944
|
+
var PLURAL = { error: "errors", warn: "warnings", info: "notes" };
|
|
945
|
+
var SINGULAR = { error: "error", warn: "warning", info: "note" };
|
|
946
|
+
function wrapText(text2, width) {
|
|
947
|
+
const lines = [];
|
|
948
|
+
let line = "";
|
|
949
|
+
const flush = () => {
|
|
950
|
+
if (line) lines.push(line);
|
|
951
|
+
line = "";
|
|
952
|
+
};
|
|
953
|
+
for (const word of text2.split(/\s+/)) {
|
|
954
|
+
if (word.length > width) {
|
|
955
|
+
flush();
|
|
956
|
+
for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
|
|
957
|
+
continue;
|
|
958
|
+
}
|
|
959
|
+
if (line && line.length + 1 + word.length > width) {
|
|
960
|
+
flush();
|
|
961
|
+
line = word;
|
|
962
|
+
} else {
|
|
963
|
+
line = line ? `${line} ${word}` : word;
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
flush();
|
|
967
|
+
return lines;
|
|
968
|
+
}
|
|
969
|
+
function sectionRule(severity, count, width) {
|
|
970
|
+
const label = PLURAL[severity].toUpperCase();
|
|
971
|
+
const tail = String(count);
|
|
972
|
+
const dashes = Math.max(3, width - label.length - tail.length - 2);
|
|
973
|
+
return `${BADGE[severity](import_picocolors.default.bold(label))} ${import_picocolors.default.dim("\u2500".repeat(dashes))} ${import_picocolors.default.dim(tail)}`;
|
|
974
|
+
}
|
|
975
|
+
function formatAuditPretty(groups, meta, columns = 80) {
|
|
976
|
+
const width = Math.min(Math.max(columns, 48), 96);
|
|
977
|
+
const indent = " ";
|
|
978
|
+
const body = width - indent.length;
|
|
944
979
|
const lines = [
|
|
945
|
-
import_picocolors.default.bold(meta.target
|
|
946
|
-
import_picocolors.default.dim(
|
|
980
|
+
`${import_picocolors.default.bold("pagetrace")} ${import_picocolors.default.dim("\xB7")} ${meta.target}`,
|
|
981
|
+
import_picocolors.default.dim(
|
|
982
|
+
[
|
|
983
|
+
`${meta.pageCount} page${meta.pageCount === 1 ? "" : "s"}`,
|
|
984
|
+
meta.platform === "unknown" ? null : PLATFORM_LABEL[meta.platform],
|
|
985
|
+
meta.generatedAt
|
|
986
|
+
].filter(Boolean).join(" \xB7 ")
|
|
987
|
+
),
|
|
947
988
|
""
|
|
948
989
|
];
|
|
949
990
|
if (groups.length === 0) {
|
|
950
|
-
lines.push(import_picocolors.default.green(
|
|
991
|
+
lines.push(import_picocolors.default.green(`\u2713 No issues found across ${meta.pageCount} pages.`), "");
|
|
951
992
|
return lines.join("\n");
|
|
952
993
|
}
|
|
953
|
-
for (const
|
|
954
|
-
const
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
994
|
+
for (const severity of ["error", "warn", "info"]) {
|
|
995
|
+
const inSeverity = groups.filter((g) => g.severity === severity);
|
|
996
|
+
if (inSeverity.length === 0) continue;
|
|
997
|
+
lines.push(sectionRule(severity, inSeverity.length, width), "");
|
|
998
|
+
for (const group of inSeverity) {
|
|
999
|
+
const scope = isTemplateWide(group, meta.pageCount) ? `${group.count} pages \xB7 one template fix` : `${group.count} page${group.count === 1 ? "" : "s"}`;
|
|
1000
|
+
const headline = group.message;
|
|
1001
|
+
const room = width - 2 - scope.length - 1;
|
|
1002
|
+
if (headline.length <= room) {
|
|
1003
|
+
const pad = " ".repeat(Math.max(1, room - headline.length + 1));
|
|
1004
|
+
lines.push(
|
|
1005
|
+
`${BADGE[severity](GLYPH[severity])} ${import_picocolors.default.bold(headline)}${pad}${import_picocolors.default.dim(scope)}`
|
|
1006
|
+
);
|
|
1007
|
+
} else {
|
|
1008
|
+
for (const [i, line] of wrapText(headline, width - 2).entries()) {
|
|
1009
|
+
lines.push(i === 0 ? `${BADGE[severity](GLYPH[severity])} ${import_picocolors.default.bold(line)}` : `${indent}${import_picocolors.default.bold(line)}`);
|
|
1010
|
+
}
|
|
1011
|
+
lines.push(`${indent}${import_picocolors.default.dim(scope)}`);
|
|
1012
|
+
}
|
|
1013
|
+
if (group.detail) {
|
|
1014
|
+
for (const line of wrapText(group.detail, body)) lines.push(`${indent}${import_picocolors.default.dim(line)}`);
|
|
1015
|
+
}
|
|
1016
|
+
if (group.fix) {
|
|
1017
|
+
const [first, ...rest] = wrapText(group.fix, body - 2);
|
|
1018
|
+
lines.push(`${indent}${import_picocolors.default.cyan("\u2192")} ${first}`);
|
|
1019
|
+
for (const line of rest) lines.push(`${indent} ${line}`);
|
|
1020
|
+
}
|
|
1021
|
+
if (group.routes.length > 0) {
|
|
1022
|
+
for (const line of wrapText(sampleRoutes(group.routes), body)) {
|
|
1023
|
+
lines.push(`${indent}${import_picocolors.default.dim(line)}`);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
lines.push("");
|
|
1027
|
+
}
|
|
960
1028
|
}
|
|
961
1029
|
const { issues, instances, total } = countIssues(groups);
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
);
|
|
1030
|
+
const counted = ["error", "warn", "info"].filter((s) => issues[s] > 0).map((s) => BADGE[s](`${issues[s]} ${issues[s] === 1 ? SINGULAR[s] : PLURAL[s]}`)).join(import_picocolors.default.dim(" \xB7 "));
|
|
1031
|
+
lines.push(import_picocolors.default.dim("\u2500".repeat(width)));
|
|
1032
|
+
lines.push(`${import_picocolors.default.bold(`${total} issue${total === 1 ? "" : "s"}`)} ${counted}`);
|
|
965
1033
|
lines.push(
|
|
966
1034
|
import_picocolors.default.dim(
|
|
967
|
-
|
|
1035
|
+
`${instances.error + instances.warn + instances.info} findings across ${meta.pageCount} pages`
|
|
968
1036
|
)
|
|
969
1037
|
);
|
|
1038
|
+
lines.push("");
|
|
970
1039
|
return lines.join("\n");
|
|
971
1040
|
}
|
|
972
1041
|
function formatAuditMarkdown(groups, meta) {
|
|
@@ -1559,7 +1628,7 @@ cli.command("audit", "Audit a site as it stands, with explanations and fixes").o
|
|
|
1559
1628
|
pageCount: pages.length,
|
|
1560
1629
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
1561
1630
|
};
|
|
1562
|
-
const output = flags.format === "json" ? JSON.stringify({ schemaVersion: 1, meta, summary: summarize(findings), groups }, null, 2) : flags.format === "markdown" ? formatAuditMarkdown(groups, meta) : flags.format === "html" ? formatAuditHtml(groups, meta) : formatAuditPretty(groups, meta);
|
|
1631
|
+
const output = flags.format === "json" ? JSON.stringify({ schemaVersion: 1, meta, summary: summarize(findings), groups }, null, 2) : flags.format === "markdown" ? formatAuditMarkdown(groups, meta) : flags.format === "html" ? formatAuditHtml(groups, meta) : formatAuditPretty(groups, meta, process.stdout.columns);
|
|
1563
1632
|
if (flags.out) {
|
|
1564
1633
|
await (0, import_promises2.writeFile)(flags.out, `${output}
|
|
1565
1634
|
`, "utf8");
|
|
@@ -1572,7 +1641,7 @@ cli.command("audit", "Audit a site as it stands, with explanations and fixes").o
|
|
|
1572
1641
|
}
|
|
1573
1642
|
});
|
|
1574
1643
|
cli.help();
|
|
1575
|
-
cli.version("0.
|
|
1644
|
+
cli.version("0.7.0");
|
|
1576
1645
|
async function main() {
|
|
1577
1646
|
try {
|
|
1578
1647
|
cli.parse(process.argv, { run: false });
|
package/dist/cli.js
CHANGED
|
@@ -917,33 +917,102 @@ function sampleRoutes(routes, limit = 5) {
|
|
|
917
917
|
const shown = routes.slice(0, limit).join(", ");
|
|
918
918
|
return routes.length > limit ? `${shown} +${routes.length - limit} more` : shown;
|
|
919
919
|
}
|
|
920
|
-
|
|
920
|
+
var GLYPH = { error: "\u2717", warn: "\u25B2", info: "\xB7" };
|
|
921
|
+
var PLURAL = { error: "errors", warn: "warnings", info: "notes" };
|
|
922
|
+
var SINGULAR = { error: "error", warn: "warning", info: "note" };
|
|
923
|
+
function wrapText(text2, width) {
|
|
924
|
+
const lines = [];
|
|
925
|
+
let line = "";
|
|
926
|
+
const flush = () => {
|
|
927
|
+
if (line) lines.push(line);
|
|
928
|
+
line = "";
|
|
929
|
+
};
|
|
930
|
+
for (const word of text2.split(/\s+/)) {
|
|
931
|
+
if (word.length > width) {
|
|
932
|
+
flush();
|
|
933
|
+
for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
if (line && line.length + 1 + word.length > width) {
|
|
937
|
+
flush();
|
|
938
|
+
line = word;
|
|
939
|
+
} else {
|
|
940
|
+
line = line ? `${line} ${word}` : word;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
flush();
|
|
944
|
+
return lines;
|
|
945
|
+
}
|
|
946
|
+
function sectionRule(severity, count, width) {
|
|
947
|
+
const label = PLURAL[severity].toUpperCase();
|
|
948
|
+
const tail = String(count);
|
|
949
|
+
const dashes = Math.max(3, width - label.length - tail.length - 2);
|
|
950
|
+
return `${BADGE[severity](pc.bold(label))} ${pc.dim("\u2500".repeat(dashes))} ${pc.dim(tail)}`;
|
|
951
|
+
}
|
|
952
|
+
function formatAuditPretty(groups, meta, columns = 80) {
|
|
953
|
+
const width = Math.min(Math.max(columns, 48), 96);
|
|
954
|
+
const indent = " ";
|
|
955
|
+
const body = width - indent.length;
|
|
921
956
|
const lines = [
|
|
922
|
-
pc.bold(meta.target
|
|
923
|
-
pc.dim(
|
|
957
|
+
`${pc.bold("pagetrace")} ${pc.dim("\xB7")} ${meta.target}`,
|
|
958
|
+
pc.dim(
|
|
959
|
+
[
|
|
960
|
+
`${meta.pageCount} page${meta.pageCount === 1 ? "" : "s"}`,
|
|
961
|
+
meta.platform === "unknown" ? null : PLATFORM_LABEL[meta.platform],
|
|
962
|
+
meta.generatedAt
|
|
963
|
+
].filter(Boolean).join(" \xB7 ")
|
|
964
|
+
),
|
|
924
965
|
""
|
|
925
966
|
];
|
|
926
967
|
if (groups.length === 0) {
|
|
927
|
-
lines.push(pc.green(
|
|
968
|
+
lines.push(pc.green(`\u2713 No issues found across ${meta.pageCount} pages.`), "");
|
|
928
969
|
return lines.join("\n");
|
|
929
970
|
}
|
|
930
|
-
for (const
|
|
931
|
-
const
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
971
|
+
for (const severity of ["error", "warn", "info"]) {
|
|
972
|
+
const inSeverity = groups.filter((g) => g.severity === severity);
|
|
973
|
+
if (inSeverity.length === 0) continue;
|
|
974
|
+
lines.push(sectionRule(severity, inSeverity.length, width), "");
|
|
975
|
+
for (const group of inSeverity) {
|
|
976
|
+
const scope = isTemplateWide(group, meta.pageCount) ? `${group.count} pages \xB7 one template fix` : `${group.count} page${group.count === 1 ? "" : "s"}`;
|
|
977
|
+
const headline = group.message;
|
|
978
|
+
const room = width - 2 - scope.length - 1;
|
|
979
|
+
if (headline.length <= room) {
|
|
980
|
+
const pad = " ".repeat(Math.max(1, room - headline.length + 1));
|
|
981
|
+
lines.push(
|
|
982
|
+
`${BADGE[severity](GLYPH[severity])} ${pc.bold(headline)}${pad}${pc.dim(scope)}`
|
|
983
|
+
);
|
|
984
|
+
} else {
|
|
985
|
+
for (const [i, line] of wrapText(headline, width - 2).entries()) {
|
|
986
|
+
lines.push(i === 0 ? `${BADGE[severity](GLYPH[severity])} ${pc.bold(line)}` : `${indent}${pc.bold(line)}`);
|
|
987
|
+
}
|
|
988
|
+
lines.push(`${indent}${pc.dim(scope)}`);
|
|
989
|
+
}
|
|
990
|
+
if (group.detail) {
|
|
991
|
+
for (const line of wrapText(group.detail, body)) lines.push(`${indent}${pc.dim(line)}`);
|
|
992
|
+
}
|
|
993
|
+
if (group.fix) {
|
|
994
|
+
const [first, ...rest] = wrapText(group.fix, body - 2);
|
|
995
|
+
lines.push(`${indent}${pc.cyan("\u2192")} ${first}`);
|
|
996
|
+
for (const line of rest) lines.push(`${indent} ${line}`);
|
|
997
|
+
}
|
|
998
|
+
if (group.routes.length > 0) {
|
|
999
|
+
for (const line of wrapText(sampleRoutes(group.routes), body)) {
|
|
1000
|
+
lines.push(`${indent}${pc.dim(line)}`);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
lines.push("");
|
|
1004
|
+
}
|
|
937
1005
|
}
|
|
938
1006
|
const { issues, instances, total } = countIssues(groups);
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
);
|
|
1007
|
+
const counted = ["error", "warn", "info"].filter((s) => issues[s] > 0).map((s) => BADGE[s](`${issues[s]} ${issues[s] === 1 ? SINGULAR[s] : PLURAL[s]}`)).join(pc.dim(" \xB7 "));
|
|
1008
|
+
lines.push(pc.dim("\u2500".repeat(width)));
|
|
1009
|
+
lines.push(`${pc.bold(`${total} issue${total === 1 ? "" : "s"}`)} ${counted}`);
|
|
942
1010
|
lines.push(
|
|
943
1011
|
pc.dim(
|
|
944
|
-
|
|
1012
|
+
`${instances.error + instances.warn + instances.info} findings across ${meta.pageCount} pages`
|
|
945
1013
|
)
|
|
946
1014
|
);
|
|
1015
|
+
lines.push("");
|
|
947
1016
|
return lines.join("\n");
|
|
948
1017
|
}
|
|
949
1018
|
function formatAuditMarkdown(groups, meta) {
|
|
@@ -1536,7 +1605,7 @@ cli.command("audit", "Audit a site as it stands, with explanations and fixes").o
|
|
|
1536
1605
|
pageCount: pages.length,
|
|
1537
1606
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
1538
1607
|
};
|
|
1539
|
-
const output = flags.format === "json" ? JSON.stringify({ schemaVersion: 1, meta, summary: summarize(findings), groups }, null, 2) : flags.format === "markdown" ? formatAuditMarkdown(groups, meta) : flags.format === "html" ? formatAuditHtml(groups, meta) : formatAuditPretty(groups, meta);
|
|
1608
|
+
const output = flags.format === "json" ? JSON.stringify({ schemaVersion: 1, meta, summary: summarize(findings), groups }, null, 2) : flags.format === "markdown" ? formatAuditMarkdown(groups, meta) : flags.format === "html" ? formatAuditHtml(groups, meta) : formatAuditPretty(groups, meta, process.stdout.columns);
|
|
1540
1609
|
if (flags.out) {
|
|
1541
1610
|
await writeFile(flags.out, `${output}
|
|
1542
1611
|
`, "utf8");
|
|
@@ -1549,7 +1618,7 @@ cli.command("audit", "Audit a site as it stands, with explanations and fixes").o
|
|
|
1549
1618
|
}
|
|
1550
1619
|
});
|
|
1551
1620
|
cli.help();
|
|
1552
|
-
cli.version("0.
|
|
1621
|
+
cli.version("0.7.0");
|
|
1553
1622
|
async function main() {
|
|
1554
1623
|
try {
|
|
1555
1624
|
cli.parse(process.argv, { run: false });
|
package/dist/index.cjs
CHANGED
|
@@ -1185,33 +1185,102 @@ function sampleRoutes(routes, limit = 5) {
|
|
|
1185
1185
|
const shown = routes.slice(0, limit).join(", ");
|
|
1186
1186
|
return routes.length > limit ? `${shown} +${routes.length - limit} more` : shown;
|
|
1187
1187
|
}
|
|
1188
|
-
|
|
1188
|
+
var GLYPH = { error: "\u2717", warn: "\u25B2", info: "\xB7" };
|
|
1189
|
+
var PLURAL = { error: "errors", warn: "warnings", info: "notes" };
|
|
1190
|
+
var SINGULAR = { error: "error", warn: "warning", info: "note" };
|
|
1191
|
+
function wrapText(text2, width) {
|
|
1192
|
+
const lines = [];
|
|
1193
|
+
let line = "";
|
|
1194
|
+
const flush = () => {
|
|
1195
|
+
if (line) lines.push(line);
|
|
1196
|
+
line = "";
|
|
1197
|
+
};
|
|
1198
|
+
for (const word of text2.split(/\s+/)) {
|
|
1199
|
+
if (word.length > width) {
|
|
1200
|
+
flush();
|
|
1201
|
+
for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
|
|
1202
|
+
continue;
|
|
1203
|
+
}
|
|
1204
|
+
if (line && line.length + 1 + word.length > width) {
|
|
1205
|
+
flush();
|
|
1206
|
+
line = word;
|
|
1207
|
+
} else {
|
|
1208
|
+
line = line ? `${line} ${word}` : word;
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
flush();
|
|
1212
|
+
return lines;
|
|
1213
|
+
}
|
|
1214
|
+
function sectionRule(severity, count, width) {
|
|
1215
|
+
const label = PLURAL[severity].toUpperCase();
|
|
1216
|
+
const tail = String(count);
|
|
1217
|
+
const dashes = Math.max(3, width - label.length - tail.length - 2);
|
|
1218
|
+
return `${BADGE[severity](import_picocolors.default.bold(label))} ${import_picocolors.default.dim("\u2500".repeat(dashes))} ${import_picocolors.default.dim(tail)}`;
|
|
1219
|
+
}
|
|
1220
|
+
function formatAuditPretty(groups, meta, columns = 80) {
|
|
1221
|
+
const width = Math.min(Math.max(columns, 48), 96);
|
|
1222
|
+
const indent = " ";
|
|
1223
|
+
const body = width - indent.length;
|
|
1189
1224
|
const lines = [
|
|
1190
|
-
import_picocolors.default.bold(meta.target
|
|
1191
|
-
import_picocolors.default.dim(
|
|
1225
|
+
`${import_picocolors.default.bold("pagetrace")} ${import_picocolors.default.dim("\xB7")} ${meta.target}`,
|
|
1226
|
+
import_picocolors.default.dim(
|
|
1227
|
+
[
|
|
1228
|
+
`${meta.pageCount} page${meta.pageCount === 1 ? "" : "s"}`,
|
|
1229
|
+
meta.platform === "unknown" ? null : PLATFORM_LABEL[meta.platform],
|
|
1230
|
+
meta.generatedAt
|
|
1231
|
+
].filter(Boolean).join(" \xB7 ")
|
|
1232
|
+
),
|
|
1192
1233
|
""
|
|
1193
1234
|
];
|
|
1194
1235
|
if (groups.length === 0) {
|
|
1195
|
-
lines.push(import_picocolors.default.green(
|
|
1236
|
+
lines.push(import_picocolors.default.green(`\u2713 No issues found across ${meta.pageCount} pages.`), "");
|
|
1196
1237
|
return lines.join("\n");
|
|
1197
1238
|
}
|
|
1198
|
-
for (const
|
|
1199
|
-
const
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1239
|
+
for (const severity of ["error", "warn", "info"]) {
|
|
1240
|
+
const inSeverity = groups.filter((g) => g.severity === severity);
|
|
1241
|
+
if (inSeverity.length === 0) continue;
|
|
1242
|
+
lines.push(sectionRule(severity, inSeverity.length, width), "");
|
|
1243
|
+
for (const group of inSeverity) {
|
|
1244
|
+
const scope = isTemplateWide(group, meta.pageCount) ? `${group.count} pages \xB7 one template fix` : `${group.count} page${group.count === 1 ? "" : "s"}`;
|
|
1245
|
+
const headline = group.message;
|
|
1246
|
+
const room = width - 2 - scope.length - 1;
|
|
1247
|
+
if (headline.length <= room) {
|
|
1248
|
+
const pad = " ".repeat(Math.max(1, room - headline.length + 1));
|
|
1249
|
+
lines.push(
|
|
1250
|
+
`${BADGE[severity](GLYPH[severity])} ${import_picocolors.default.bold(headline)}${pad}${import_picocolors.default.dim(scope)}`
|
|
1251
|
+
);
|
|
1252
|
+
} else {
|
|
1253
|
+
for (const [i, line] of wrapText(headline, width - 2).entries()) {
|
|
1254
|
+
lines.push(i === 0 ? `${BADGE[severity](GLYPH[severity])} ${import_picocolors.default.bold(line)}` : `${indent}${import_picocolors.default.bold(line)}`);
|
|
1255
|
+
}
|
|
1256
|
+
lines.push(`${indent}${import_picocolors.default.dim(scope)}`);
|
|
1257
|
+
}
|
|
1258
|
+
if (group.detail) {
|
|
1259
|
+
for (const line of wrapText(group.detail, body)) lines.push(`${indent}${import_picocolors.default.dim(line)}`);
|
|
1260
|
+
}
|
|
1261
|
+
if (group.fix) {
|
|
1262
|
+
const [first, ...rest] = wrapText(group.fix, body - 2);
|
|
1263
|
+
lines.push(`${indent}${import_picocolors.default.cyan("\u2192")} ${first}`);
|
|
1264
|
+
for (const line of rest) lines.push(`${indent} ${line}`);
|
|
1265
|
+
}
|
|
1266
|
+
if (group.routes.length > 0) {
|
|
1267
|
+
for (const line of wrapText(sampleRoutes(group.routes), body)) {
|
|
1268
|
+
lines.push(`${indent}${import_picocolors.default.dim(line)}`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
lines.push("");
|
|
1272
|
+
}
|
|
1205
1273
|
}
|
|
1206
1274
|
const { issues, instances, total } = countIssues(groups);
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
);
|
|
1275
|
+
const counted = ["error", "warn", "info"].filter((s) => issues[s] > 0).map((s) => BADGE[s](`${issues[s]} ${issues[s] === 1 ? SINGULAR[s] : PLURAL[s]}`)).join(import_picocolors.default.dim(" \xB7 "));
|
|
1276
|
+
lines.push(import_picocolors.default.dim("\u2500".repeat(width)));
|
|
1277
|
+
lines.push(`${import_picocolors.default.bold(`${total} issue${total === 1 ? "" : "s"}`)} ${counted}`);
|
|
1210
1278
|
lines.push(
|
|
1211
1279
|
import_picocolors.default.dim(
|
|
1212
|
-
|
|
1280
|
+
`${instances.error + instances.warn + instances.info} findings across ${meta.pageCount} pages`
|
|
1213
1281
|
)
|
|
1214
1282
|
);
|
|
1283
|
+
lines.push("");
|
|
1215
1284
|
return lines.join("\n");
|
|
1216
1285
|
}
|
|
1217
1286
|
function formatAuditMarkdown(groups, meta) {
|
package/dist/index.d.cts
CHANGED
|
@@ -192,7 +192,13 @@ interface AuditMeta {
|
|
|
192
192
|
pageCount: number;
|
|
193
193
|
generatedAt: string;
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
/**
|
|
196
|
+
* The terminal report. Findings are grouped under a rule per severity, prose is
|
|
197
|
+
* wrapped to the terminal rather than running off it, and the affected routes
|
|
198
|
+
* sit on their own line — they are the part you act on, and they used to blend
|
|
199
|
+
* into the surrounding text.
|
|
200
|
+
*/
|
|
201
|
+
declare function formatAuditPretty(groups: Aggregate[], meta: AuditMeta, columns?: number): string;
|
|
196
202
|
declare function formatAuditMarkdown(groups: Aggregate[], meta: AuditMeta): string;
|
|
197
203
|
/** Self-contained HTML report, suitable for handing to a client. */
|
|
198
204
|
declare function formatAuditHtml(groups: Aggregate[], meta: AuditMeta): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -192,7 +192,13 @@ interface AuditMeta {
|
|
|
192
192
|
pageCount: number;
|
|
193
193
|
generatedAt: string;
|
|
194
194
|
}
|
|
195
|
-
|
|
195
|
+
/**
|
|
196
|
+
* The terminal report. Findings are grouped under a rule per severity, prose is
|
|
197
|
+
* wrapped to the terminal rather than running off it, and the affected routes
|
|
198
|
+
* sit on their own line — they are the part you act on, and they used to blend
|
|
199
|
+
* into the surrounding text.
|
|
200
|
+
*/
|
|
201
|
+
declare function formatAuditPretty(groups: Aggregate[], meta: AuditMeta, columns?: number): string;
|
|
196
202
|
declare function formatAuditMarkdown(groups: Aggregate[], meta: AuditMeta): string;
|
|
197
203
|
/** Self-contained HTML report, suitable for handing to a client. */
|
|
198
204
|
declare function formatAuditHtml(groups: Aggregate[], meta: AuditMeta): string;
|
package/dist/index.js
CHANGED
|
@@ -1114,33 +1114,102 @@ function sampleRoutes(routes, limit = 5) {
|
|
|
1114
1114
|
const shown = routes.slice(0, limit).join(", ");
|
|
1115
1115
|
return routes.length > limit ? `${shown} +${routes.length - limit} more` : shown;
|
|
1116
1116
|
}
|
|
1117
|
-
|
|
1117
|
+
var GLYPH = { error: "\u2717", warn: "\u25B2", info: "\xB7" };
|
|
1118
|
+
var PLURAL = { error: "errors", warn: "warnings", info: "notes" };
|
|
1119
|
+
var SINGULAR = { error: "error", warn: "warning", info: "note" };
|
|
1120
|
+
function wrapText(text2, width) {
|
|
1121
|
+
const lines = [];
|
|
1122
|
+
let line = "";
|
|
1123
|
+
const flush = () => {
|
|
1124
|
+
if (line) lines.push(line);
|
|
1125
|
+
line = "";
|
|
1126
|
+
};
|
|
1127
|
+
for (const word of text2.split(/\s+/)) {
|
|
1128
|
+
if (word.length > width) {
|
|
1129
|
+
flush();
|
|
1130
|
+
for (let i = 0; i < word.length; i += width) lines.push(word.slice(i, i + width));
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
if (line && line.length + 1 + word.length > width) {
|
|
1134
|
+
flush();
|
|
1135
|
+
line = word;
|
|
1136
|
+
} else {
|
|
1137
|
+
line = line ? `${line} ${word}` : word;
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
flush();
|
|
1141
|
+
return lines;
|
|
1142
|
+
}
|
|
1143
|
+
function sectionRule(severity, count, width) {
|
|
1144
|
+
const label = PLURAL[severity].toUpperCase();
|
|
1145
|
+
const tail = String(count);
|
|
1146
|
+
const dashes = Math.max(3, width - label.length - tail.length - 2);
|
|
1147
|
+
return `${BADGE[severity](pc.bold(label))} ${pc.dim("\u2500".repeat(dashes))} ${pc.dim(tail)}`;
|
|
1148
|
+
}
|
|
1149
|
+
function formatAuditPretty(groups, meta, columns = 80) {
|
|
1150
|
+
const width = Math.min(Math.max(columns, 48), 96);
|
|
1151
|
+
const indent = " ";
|
|
1152
|
+
const body = width - indent.length;
|
|
1118
1153
|
const lines = [
|
|
1119
|
-
pc.bold(meta.target
|
|
1120
|
-
pc.dim(
|
|
1154
|
+
`${pc.bold("pagetrace")} ${pc.dim("\xB7")} ${meta.target}`,
|
|
1155
|
+
pc.dim(
|
|
1156
|
+
[
|
|
1157
|
+
`${meta.pageCount} page${meta.pageCount === 1 ? "" : "s"}`,
|
|
1158
|
+
meta.platform === "unknown" ? null : PLATFORM_LABEL[meta.platform],
|
|
1159
|
+
meta.generatedAt
|
|
1160
|
+
].filter(Boolean).join(" \xB7 ")
|
|
1161
|
+
),
|
|
1121
1162
|
""
|
|
1122
1163
|
];
|
|
1123
1164
|
if (groups.length === 0) {
|
|
1124
|
-
lines.push(pc.green(
|
|
1165
|
+
lines.push(pc.green(`\u2713 No issues found across ${meta.pageCount} pages.`), "");
|
|
1125
1166
|
return lines.join("\n");
|
|
1126
1167
|
}
|
|
1127
|
-
for (const
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1168
|
+
for (const severity of ["error", "warn", "info"]) {
|
|
1169
|
+
const inSeverity = groups.filter((g) => g.severity === severity);
|
|
1170
|
+
if (inSeverity.length === 0) continue;
|
|
1171
|
+
lines.push(sectionRule(severity, inSeverity.length, width), "");
|
|
1172
|
+
for (const group of inSeverity) {
|
|
1173
|
+
const scope = isTemplateWide(group, meta.pageCount) ? `${group.count} pages \xB7 one template fix` : `${group.count} page${group.count === 1 ? "" : "s"}`;
|
|
1174
|
+
const headline = group.message;
|
|
1175
|
+
const room = width - 2 - scope.length - 1;
|
|
1176
|
+
if (headline.length <= room) {
|
|
1177
|
+
const pad = " ".repeat(Math.max(1, room - headline.length + 1));
|
|
1178
|
+
lines.push(
|
|
1179
|
+
`${BADGE[severity](GLYPH[severity])} ${pc.bold(headline)}${pad}${pc.dim(scope)}`
|
|
1180
|
+
);
|
|
1181
|
+
} else {
|
|
1182
|
+
for (const [i, line] of wrapText(headline, width - 2).entries()) {
|
|
1183
|
+
lines.push(i === 0 ? `${BADGE[severity](GLYPH[severity])} ${pc.bold(line)}` : `${indent}${pc.bold(line)}`);
|
|
1184
|
+
}
|
|
1185
|
+
lines.push(`${indent}${pc.dim(scope)}`);
|
|
1186
|
+
}
|
|
1187
|
+
if (group.detail) {
|
|
1188
|
+
for (const line of wrapText(group.detail, body)) lines.push(`${indent}${pc.dim(line)}`);
|
|
1189
|
+
}
|
|
1190
|
+
if (group.fix) {
|
|
1191
|
+
const [first, ...rest] = wrapText(group.fix, body - 2);
|
|
1192
|
+
lines.push(`${indent}${pc.cyan("\u2192")} ${first}`);
|
|
1193
|
+
for (const line of rest) lines.push(`${indent} ${line}`);
|
|
1194
|
+
}
|
|
1195
|
+
if (group.routes.length > 0) {
|
|
1196
|
+
for (const line of wrapText(sampleRoutes(group.routes), body)) {
|
|
1197
|
+
lines.push(`${indent}${pc.dim(line)}`);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
lines.push("");
|
|
1201
|
+
}
|
|
1134
1202
|
}
|
|
1135
1203
|
const { issues, instances, total } = countIssues(groups);
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
);
|
|
1204
|
+
const counted = ["error", "warn", "info"].filter((s) => issues[s] > 0).map((s) => BADGE[s](`${issues[s]} ${issues[s] === 1 ? SINGULAR[s] : PLURAL[s]}`)).join(pc.dim(" \xB7 "));
|
|
1205
|
+
lines.push(pc.dim("\u2500".repeat(width)));
|
|
1206
|
+
lines.push(`${pc.bold(`${total} issue${total === 1 ? "" : "s"}`)} ${counted}`);
|
|
1139
1207
|
lines.push(
|
|
1140
1208
|
pc.dim(
|
|
1141
|
-
|
|
1209
|
+
`${instances.error + instances.warn + instances.info} findings across ${meta.pageCount} pages`
|
|
1142
1210
|
)
|
|
1143
1211
|
);
|
|
1212
|
+
lines.push("");
|
|
1144
1213
|
return lines.join("\n");
|
|
1145
1214
|
}
|
|
1146
1215
|
function formatAuditMarkdown(groups, meta) {
|