pagetrace 0.5.0 → 0.6.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 +24 -1
- package/dist/cli.cjs +32 -33
- package/dist/cli.js +32 -33
- package/dist/index.cjs +31 -32
- package/dist/index.js +31 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ 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.6.0] - 2026-09-06
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Breaking.** `duplicate.title`, `duplicate.description` and
|
|
14
|
+
`duplicate.canonical` now emit one finding per affected route instead of a
|
|
15
|
+
single finding with `route: null`. Integrations reading `route` on these three
|
|
16
|
+
codes will see a path where they saw null; the codes themselves are unchanged.
|
|
17
|
+
|
|
18
|
+
Found by auditing a real site. The report said "2 pages share the same meta
|
|
19
|
+
description" and could not say which two, because the routes lived in `after`,
|
|
20
|
+
which the rollup drops — leaving the only actionable part of the finding
|
|
21
|
+
invisible. Per-route findings also make the counts truthful and point
|
|
22
|
+
`--format github` annotations at the pages rather than at "site".
|
|
23
|
+
|
|
9
24
|
## [0.5.0] - 2026-09-06
|
|
10
25
|
|
|
11
26
|
### Added
|
|
@@ -20,8 +35,15 @@ While the version is below 1.0.0, breaking changes ship in a minor release.
|
|
|
20
35
|
- A composite GitHub Action (`action.yml`). Three lines in a workflow run the
|
|
21
36
|
check and post the findings as a pull request comment, editing the previous
|
|
22
37
|
comment on each push rather than stacking new ones. It fetches the baseline ref
|
|
23
|
-
first, since a shallow CI checkout usually has only the PR head.
|
|
38
|
+
first, since a shallow CI checkout usually has only the PR head. The comment is
|
|
39
|
+
skipped for pull requests from a fork, which run with a read-only token and
|
|
40
|
+
would otherwise fail with a 403 through no fault of the contributor; the
|
|
41
|
+
findings still reach the step summary and still set the exit code.
|
|
24
42
|
- `snapshotFromGitRef(ref, path)` is exported.
|
|
43
|
+
- `examples/site`, a small deliberately correct site with a committed baseline.
|
|
44
|
+
The action runs against it on every pull request to this repo, so a change
|
|
45
|
+
that breaks the action's own wiring fails here rather than in someone else's
|
|
46
|
+
CI. It doubles as a worked example of what a clean surface looks like.
|
|
25
47
|
|
|
26
48
|
### Fixed
|
|
27
49
|
|
|
@@ -194,6 +216,7 @@ Initial release. `snapshot`, `check` and `audit` commands; filesystem and HTTP
|
|
|
194
216
|
crawling; diff classified by transition; absolute, cross-page and hreflang audit
|
|
195
217
|
rules; pretty, JSON, markdown, GitHub and HTML reporters.
|
|
196
218
|
|
|
219
|
+
[0.6.0]: https://github.com/shyamexe/pagetrace/compare/v0.5.0...v0.6.0
|
|
197
220
|
[0.5.0]: https://github.com/shyamexe/pagetrace/compare/v0.4.0...v0.5.0
|
|
198
221
|
[0.4.0]: https://github.com/shyamexe/pagetrace/compare/v0.3.0...v0.4.0
|
|
199
222
|
[0.3.0]: https://github.com/shyamexe/pagetrace/compare/v0.2.0...v0.3.0
|
package/dist/cli.cjs
CHANGED
|
@@ -266,39 +266,38 @@ function auditCrossPage(snapshot) {
|
|
|
266
266
|
}
|
|
267
267
|
return map;
|
|
268
268
|
};
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (routes.length > 1) {
|
|
282
|
-
findings.push({
|
|
283
|
-
code: "duplicate.description",
|
|
284
|
-
severity: "warn",
|
|
285
|
-
route: null,
|
|
286
|
-
message: `${routes.length} pages share the same meta description.`,
|
|
287
|
-
after: routes
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
for (const [canonical, routes] of group((p) => p.canonical)) {
|
|
292
|
-
if (routes.length > 1) {
|
|
293
|
-
findings.push({
|
|
294
|
-
code: "duplicate.canonical",
|
|
295
|
-
severity: "error",
|
|
296
|
-
route: null,
|
|
297
|
-
message: `${routes.length} pages canonicalise to ${canonical}.`,
|
|
298
|
-
after: routes
|
|
299
|
-
});
|
|
269
|
+
const duplicates = (code, severity, keyed, message) => {
|
|
270
|
+
for (const [value, routes] of keyed) {
|
|
271
|
+
if (routes.length < 2) continue;
|
|
272
|
+
for (const route of routes) {
|
|
273
|
+
findings.push({
|
|
274
|
+
code,
|
|
275
|
+
severity,
|
|
276
|
+
route,
|
|
277
|
+
message: message(value, routes.length),
|
|
278
|
+
after: routes
|
|
279
|
+
});
|
|
280
|
+
}
|
|
300
281
|
}
|
|
301
|
-
}
|
|
282
|
+
};
|
|
283
|
+
duplicates(
|
|
284
|
+
"duplicate.title",
|
|
285
|
+
"warn",
|
|
286
|
+
group((p) => p.title),
|
|
287
|
+
(title, n) => `${n} pages share the title "${title}".`
|
|
288
|
+
);
|
|
289
|
+
duplicates(
|
|
290
|
+
"duplicate.description",
|
|
291
|
+
"warn",
|
|
292
|
+
group((p) => p.description),
|
|
293
|
+
(_, n) => `${n} pages share the same meta description.`
|
|
294
|
+
);
|
|
295
|
+
duplicates(
|
|
296
|
+
"duplicate.canonical",
|
|
297
|
+
"error",
|
|
298
|
+
group((p) => p.canonical),
|
|
299
|
+
(canonical, n) => `${n} pages canonicalise to ${canonical}.`
|
|
300
|
+
);
|
|
302
301
|
const expectedOrigin = snapshot.site.origin ?? null;
|
|
303
302
|
for (const page of pages) {
|
|
304
303
|
if (!page.canonical) continue;
|
|
@@ -1573,7 +1572,7 @@ cli.command("audit", "Audit a site as it stands, with explanations and fixes").o
|
|
|
1573
1572
|
}
|
|
1574
1573
|
});
|
|
1575
1574
|
cli.help();
|
|
1576
|
-
cli.version("0.
|
|
1575
|
+
cli.version("0.6.0");
|
|
1577
1576
|
async function main() {
|
|
1578
1577
|
try {
|
|
1579
1578
|
cli.parse(process.argv, { run: false });
|
package/dist/cli.js
CHANGED
|
@@ -243,39 +243,38 @@ function auditCrossPage(snapshot) {
|
|
|
243
243
|
}
|
|
244
244
|
return map;
|
|
245
245
|
};
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
if (routes.length > 1) {
|
|
259
|
-
findings.push({
|
|
260
|
-
code: "duplicate.description",
|
|
261
|
-
severity: "warn",
|
|
262
|
-
route: null,
|
|
263
|
-
message: `${routes.length} pages share the same meta description.`,
|
|
264
|
-
after: routes
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
for (const [canonical, routes] of group((p) => p.canonical)) {
|
|
269
|
-
if (routes.length > 1) {
|
|
270
|
-
findings.push({
|
|
271
|
-
code: "duplicate.canonical",
|
|
272
|
-
severity: "error",
|
|
273
|
-
route: null,
|
|
274
|
-
message: `${routes.length} pages canonicalise to ${canonical}.`,
|
|
275
|
-
after: routes
|
|
276
|
-
});
|
|
246
|
+
const duplicates = (code, severity, keyed, message) => {
|
|
247
|
+
for (const [value, routes] of keyed) {
|
|
248
|
+
if (routes.length < 2) continue;
|
|
249
|
+
for (const route of routes) {
|
|
250
|
+
findings.push({
|
|
251
|
+
code,
|
|
252
|
+
severity,
|
|
253
|
+
route,
|
|
254
|
+
message: message(value, routes.length),
|
|
255
|
+
after: routes
|
|
256
|
+
});
|
|
257
|
+
}
|
|
277
258
|
}
|
|
278
|
-
}
|
|
259
|
+
};
|
|
260
|
+
duplicates(
|
|
261
|
+
"duplicate.title",
|
|
262
|
+
"warn",
|
|
263
|
+
group((p) => p.title),
|
|
264
|
+
(title, n) => `${n} pages share the title "${title}".`
|
|
265
|
+
);
|
|
266
|
+
duplicates(
|
|
267
|
+
"duplicate.description",
|
|
268
|
+
"warn",
|
|
269
|
+
group((p) => p.description),
|
|
270
|
+
(_, n) => `${n} pages share the same meta description.`
|
|
271
|
+
);
|
|
272
|
+
duplicates(
|
|
273
|
+
"duplicate.canonical",
|
|
274
|
+
"error",
|
|
275
|
+
group((p) => p.canonical),
|
|
276
|
+
(canonical, n) => `${n} pages canonicalise to ${canonical}.`
|
|
277
|
+
);
|
|
279
278
|
const expectedOrigin = snapshot.site.origin ?? null;
|
|
280
279
|
for (const page of pages) {
|
|
281
280
|
if (!page.canonical) continue;
|
|
@@ -1550,7 +1549,7 @@ cli.command("audit", "Audit a site as it stands, with explanations and fixes").o
|
|
|
1550
1549
|
}
|
|
1551
1550
|
});
|
|
1552
1551
|
cli.help();
|
|
1553
|
-
cli.version("0.
|
|
1552
|
+
cli.version("0.6.0");
|
|
1554
1553
|
async function main() {
|
|
1555
1554
|
try {
|
|
1556
1555
|
cli.parse(process.argv, { run: false });
|
package/dist/index.cjs
CHANGED
|
@@ -307,39 +307,38 @@ function auditCrossPage(snapshot) {
|
|
|
307
307
|
}
|
|
308
308
|
return map;
|
|
309
309
|
};
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (routes.length > 1) {
|
|
323
|
-
findings.push({
|
|
324
|
-
code: "duplicate.description",
|
|
325
|
-
severity: "warn",
|
|
326
|
-
route: null,
|
|
327
|
-
message: `${routes.length} pages share the same meta description.`,
|
|
328
|
-
after: routes
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
for (const [canonical, routes] of group((p) => p.canonical)) {
|
|
333
|
-
if (routes.length > 1) {
|
|
334
|
-
findings.push({
|
|
335
|
-
code: "duplicate.canonical",
|
|
336
|
-
severity: "error",
|
|
337
|
-
route: null,
|
|
338
|
-
message: `${routes.length} pages canonicalise to ${canonical}.`,
|
|
339
|
-
after: routes
|
|
340
|
-
});
|
|
310
|
+
const duplicates = (code, severity, keyed, message) => {
|
|
311
|
+
for (const [value, routes] of keyed) {
|
|
312
|
+
if (routes.length < 2) continue;
|
|
313
|
+
for (const route of routes) {
|
|
314
|
+
findings.push({
|
|
315
|
+
code,
|
|
316
|
+
severity,
|
|
317
|
+
route,
|
|
318
|
+
message: message(value, routes.length),
|
|
319
|
+
after: routes
|
|
320
|
+
});
|
|
321
|
+
}
|
|
341
322
|
}
|
|
342
|
-
}
|
|
323
|
+
};
|
|
324
|
+
duplicates(
|
|
325
|
+
"duplicate.title",
|
|
326
|
+
"warn",
|
|
327
|
+
group((p) => p.title),
|
|
328
|
+
(title, n) => `${n} pages share the title "${title}".`
|
|
329
|
+
);
|
|
330
|
+
duplicates(
|
|
331
|
+
"duplicate.description",
|
|
332
|
+
"warn",
|
|
333
|
+
group((p) => p.description),
|
|
334
|
+
(_, n) => `${n} pages share the same meta description.`
|
|
335
|
+
);
|
|
336
|
+
duplicates(
|
|
337
|
+
"duplicate.canonical",
|
|
338
|
+
"error",
|
|
339
|
+
group((p) => p.canonical),
|
|
340
|
+
(canonical, n) => `${n} pages canonicalise to ${canonical}.`
|
|
341
|
+
);
|
|
343
342
|
const expectedOrigin = snapshot.site.origin ?? null;
|
|
344
343
|
for (const page of pages) {
|
|
345
344
|
if (!page.canonical) continue;
|
package/dist/index.js
CHANGED
|
@@ -236,39 +236,38 @@ function auditCrossPage(snapshot) {
|
|
|
236
236
|
}
|
|
237
237
|
return map;
|
|
238
238
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
if (routes.length > 1) {
|
|
252
|
-
findings.push({
|
|
253
|
-
code: "duplicate.description",
|
|
254
|
-
severity: "warn",
|
|
255
|
-
route: null,
|
|
256
|
-
message: `${routes.length} pages share the same meta description.`,
|
|
257
|
-
after: routes
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
for (const [canonical, routes] of group((p) => p.canonical)) {
|
|
262
|
-
if (routes.length > 1) {
|
|
263
|
-
findings.push({
|
|
264
|
-
code: "duplicate.canonical",
|
|
265
|
-
severity: "error",
|
|
266
|
-
route: null,
|
|
267
|
-
message: `${routes.length} pages canonicalise to ${canonical}.`,
|
|
268
|
-
after: routes
|
|
269
|
-
});
|
|
239
|
+
const duplicates = (code, severity, keyed, message) => {
|
|
240
|
+
for (const [value, routes] of keyed) {
|
|
241
|
+
if (routes.length < 2) continue;
|
|
242
|
+
for (const route of routes) {
|
|
243
|
+
findings.push({
|
|
244
|
+
code,
|
|
245
|
+
severity,
|
|
246
|
+
route,
|
|
247
|
+
message: message(value, routes.length),
|
|
248
|
+
after: routes
|
|
249
|
+
});
|
|
250
|
+
}
|
|
270
251
|
}
|
|
271
|
-
}
|
|
252
|
+
};
|
|
253
|
+
duplicates(
|
|
254
|
+
"duplicate.title",
|
|
255
|
+
"warn",
|
|
256
|
+
group((p) => p.title),
|
|
257
|
+
(title, n) => `${n} pages share the title "${title}".`
|
|
258
|
+
);
|
|
259
|
+
duplicates(
|
|
260
|
+
"duplicate.description",
|
|
261
|
+
"warn",
|
|
262
|
+
group((p) => p.description),
|
|
263
|
+
(_, n) => `${n} pages share the same meta description.`
|
|
264
|
+
);
|
|
265
|
+
duplicates(
|
|
266
|
+
"duplicate.canonical",
|
|
267
|
+
"error",
|
|
268
|
+
group((p) => p.canonical),
|
|
269
|
+
(canonical, n) => `${n} pages canonicalise to ${canonical}.`
|
|
270
|
+
);
|
|
272
271
|
const expectedOrigin = snapshot.site.origin ?? null;
|
|
273
272
|
for (const page of pages) {
|
|
274
273
|
if (!page.canonical) continue;
|