turva-llms-txt-validator 0.3.8 → 0.3.9
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 +1 -1
- package/THIRD-PARTY-NOTICES.md +40 -0
- package/bin/cli.mjs +31 -7
- package/package.json +3 -2
- package/src/index.mjs +10 -3
package/README.md
CHANGED
|
@@ -54,7 +54,7 @@ Use JSON for automation and strict mode when warnings should fail a CI step:
|
|
|
54
54
|
npx --yes turva-llms-txt-validator example.com --json --strict
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Completed validation returns `{ target, summary, checks }`. Each check contains `{ id, status, label, detail }`. With `--json`, input and fetch errors return `{ "error": "..." }` and exit with code `2`. An unknown option, a second domain and a missing domain are input errors too
|
|
57
|
+
Completed validation returns `{ target, summary, checks }`. Each check contains `{ id, status, label, detail }`. With `--json`, input and fetch errors return `{ "error": "...", "target": "..." }` and exit with code `2`. The target is the address given on the command line, with any user name, password and query value masked, and a network error adds `code`, such as `EAI_AGAIN`. An unknown option, a second domain and a missing domain are input errors too. `--help` exits with code `0`, and `--version` or `-V` prints the installed version and exits with code `0`.
|
|
58
58
|
|
|
59
59
|
## What it checks
|
|
60
60
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
This package's own license is MIT, in `LICENSE`. The named character reference
|
|
4
|
+
table in `src/index.mjs` (`NAMED_REFERENCE_DATA`) is generated from data
|
|
5
|
+
published by a third-party package and carries that package's own license
|
|
6
|
+
below. This is a build-time data source, not an installed runtime dependency:
|
|
7
|
+
the table is generated once and copied into `src/index.mjs`, so no reference
|
|
8
|
+
to it appears in `package.json` or in the dependency tree an installer sees.
|
|
9
|
+
|
|
10
|
+
## character-entities
|
|
11
|
+
|
|
12
|
+
- Package: `character-entities`
|
|
13
|
+
- Version used to generate the table: 2.0.2
|
|
14
|
+
- URL: <https://github.com/wooorm/character-entities>
|
|
15
|
+
- License: MIT
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
(The MIT License)
|
|
19
|
+
|
|
20
|
+
Copyright (c) 2015 Titus Wormer <tituswormer@gmail.com>
|
|
21
|
+
|
|
22
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
23
|
+
a copy of this software and associated documentation files (the
|
|
24
|
+
'Software'), to deal in the Software without restriction, including
|
|
25
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
26
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
27
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
28
|
+
the following conditions:
|
|
29
|
+
|
|
30
|
+
The above copyright notice and this permission notice shall be
|
|
31
|
+
included in all copies or substantial portions of the Software.
|
|
32
|
+
|
|
33
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
34
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
35
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
36
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
37
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
38
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
39
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
40
|
+
```
|
package/bin/cli.mjs
CHANGED
|
@@ -4,15 +4,18 @@
|
|
|
4
4
|
// or bad input. The two v2 discovery checks and the input-path note carry status
|
|
5
5
|
// "info" and move no exit code, by design. Same checks and JSON shape as the hosted validator:
|
|
6
6
|
// curl -H "Accept: application/json" "https://turva.dev/llms-txt-validator?url=example.com"
|
|
7
|
-
import {
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { validateHost, stripControls, maskLocation } from "../src/index.mjs";
|
|
8
10
|
|
|
9
11
|
const USAGE = [
|
|
10
12
|
"usage: llms-txt-validate <domain-or-url> [--json] [--strict]",
|
|
11
|
-
" --json
|
|
12
|
-
" --strict
|
|
13
|
-
" --help
|
|
13
|
+
" --json print the result as JSON (same shape as the hosted validator)",
|
|
14
|
+
" --strict exit 1 on warnings too, for CI gates",
|
|
15
|
+
" --help print this help",
|
|
16
|
+
" --version print the installed version and exit"
|
|
14
17
|
];
|
|
15
|
-
const KNOWN = new Set(["--json", "--strict", "--help", "-h"]);
|
|
18
|
+
const KNOWN = new Set(["--json", "--strict", "--help", "-h", "--version", "-V"]);
|
|
16
19
|
|
|
17
20
|
// The arguments are read strictly since 0.3.8 (Tek-496). An unknown flag such as a misspelled
|
|
18
21
|
// --strcit used to be ignored, so a CI step ran without the gate it asked for, and a second
|
|
@@ -36,6 +39,15 @@ if (flags.includes("--help") || flags.includes("-h")) {
|
|
|
36
39
|
for (const line of USAGE) console.log(line);
|
|
37
40
|
process.exit(0);
|
|
38
41
|
}
|
|
42
|
+
if (flags.includes("--version") || flags.includes("-V")) {
|
|
43
|
+
// Read package.json relative to this file, not the caller's cwd, so this works the same
|
|
44
|
+
// whether the CLI runs installed, linked or through npx (V-04, outside audit 2026-09-26).
|
|
45
|
+
// No network read: the version is the one actually installed, not the npm registry's latest.
|
|
46
|
+
const pkgUrl = new URL("../package.json", import.meta.url);
|
|
47
|
+
const pkg = JSON.parse(readFileSync(fileURLToPath(pkgUrl), "utf8"));
|
|
48
|
+
console.log(pkg.version);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
39
51
|
const unknown = flags.find((f) => !KNOWN.has(f));
|
|
40
52
|
if (unknown) fail("unknown option " + JSON.stringify(stripControls(unknown).slice(0, 40)));
|
|
41
53
|
if (targets.length === 0 || targets[0].trim() === "") fail("no domain or URL given");
|
|
@@ -61,7 +73,19 @@ try {
|
|
|
61
73
|
// The hosted validator answers every failure as JSON when JSON was asked for, so a CI
|
|
62
74
|
// step that parses --json output gets {"error": ...} here too instead of an empty stdout
|
|
63
75
|
// and a plain-text stderr (round 16 S3-3). The exit code is unchanged.
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
//
|
|
77
|
+
// target and code are added as separate fields (V-05, outside audit 2026-09-26): a bare
|
|
78
|
+
// {"error": message} left a CI step unable to tell which target failed or why without
|
|
79
|
+
// parsing free text. code reads a network error's cause first (undici wraps the real
|
|
80
|
+
// errno code, such as EAI_AGAIN or ECONNREFUSED, in err.cause.code) and falls back to
|
|
81
|
+
// err.code for an error that sets it directly; it is omitted, not null, when neither is set.
|
|
82
|
+
// target goes through maskLocation like every other address the tool prints, so a user name,
|
|
83
|
+
// a password or a query value typed into the command line is never echoed back.
|
|
84
|
+
const code = (err && err.cause && err.cause.code) || (err && err.code);
|
|
85
|
+
if (json) {
|
|
86
|
+
const report = { error: message, target: stripControls(maskLocation(targets[0])) };
|
|
87
|
+
if (code) report.code = stripControls(String(code));
|
|
88
|
+
console.log(JSON.stringify(report, null, 2));
|
|
89
|
+
} else console.error("error: " + message);
|
|
66
90
|
process.exit(2);
|
|
67
91
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turva-llms-txt-validator",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "Validate a site's llms.txt structure: eight checks as pass/warn/fail plus two llms.txt v2 discovery checks that read pass or info and never move the summary, JSON output. Same checks as the hosted validator at turva.dev/llms-txt-validator.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.mjs",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"src",
|
|
13
13
|
"bin",
|
|
14
14
|
"README.md",
|
|
15
|
-
"LICENSE"
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"THIRD-PARTY-NOTICES.md"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
19
|
"test": "node --test"
|
package/src/index.mjs
CHANGED
|
@@ -454,7 +454,11 @@ export function validateLlmsTxt(f) {
|
|
|
454
454
|
add("h1-title", "fail", "Starts with an H1 title", "the first non-empty line should be a markdown H1 (# Site name)");
|
|
455
455
|
}
|
|
456
456
|
const afterH1 = lines.slice(firstIdx + 1).find((l) => l.trim() !== "") || "";
|
|
457
|
-
|
|
457
|
+
// Read the same way as the H1 check and the "late" blockquote search below: at most three
|
|
458
|
+
// leading spaces, per CommonMark. A trim-first prefix test used to erase the difference, so
|
|
459
|
+
// a four-space or tab-indented code block right after the title passed as the summary until
|
|
460
|
+
// 2026-09-27 (Tek-496, mirrored from worker.js).
|
|
461
|
+
if (/^ {0,3}> /.test(afterH1)) {
|
|
458
462
|
add("summary", "pass", "Blockquote summary after the title", JSON.stringify(cut(afterH1.trim(), 80)));
|
|
459
463
|
} else {
|
|
460
464
|
// A blockquote further down, before the first H2, is a summary in the wrong place and not a
|
|
@@ -679,8 +683,11 @@ var C1_REPLACEMENTS = {
|
|
|
679
683
|
|
|
680
684
|
// The full WHATWG list of named character references, 2 231 names: "name!hex" is a name that
|
|
681
685
|
// also decodes without its ";", "name=hex" one that needs it, and several code points are
|
|
682
|
-
// separated by ",". Generated 2026-09-12 from
|
|
683
|
-
//
|
|
686
|
+
// separated by ",". Generated 2026-09-12 from character-entities 2.0.2
|
|
687
|
+
// (https://github.com/wooorm/character-entities), (c) 2015 Titus Wormer, MIT, with every value
|
|
688
|
+
// and every legacy name checked against the decoder of the entities package. The full license
|
|
689
|
+
// text is in THIRD-PARTY-NOTICES.md (E01, outside audit 2026-09-26); this is a build-time data
|
|
690
|
+
// source copied in at generation time, not an installed runtime dependency.
|
|
684
691
|
var NAMED_REFERENCE_DATA =
|
|
685
692
|
"AElig!c6 AMP!26 Aacute!c1 Abreve=102 Acirc!c2 Acy=410 Afr=1d504 Agrave!c0 Alpha=391 Amacr=100 " +
|
|
686
693
|
"And=2a53 Aogon=104 Aopf=1d538 ApplyFunction=2061 Aring!c5 Ascr=1d49c Assign=2254 Atilde!c3 " +
|