@contentrain/verify 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/README.md +149 -0
- package/dist/bin.d.mts +1 -0
- package/dist/bin.mjs +13 -0
- package/dist/bin.mjs.map +1 -0
- package/dist/cli.d.mts +24 -0
- package/dist/cli.d.mts.map +1 -0
- package/dist/cli.mjs +68 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +29 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/load-KUG-8Ug4.mjs +742 -0
- package/dist/load-KUG-8Ug4.mjs.map +1 -0
- package/dist/types-C_hmH1n3.d.mts +87 -0
- package/dist/types-C_hmH1n3.d.mts.map +1 -0
- package/package.json +55 -0
package/README.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# @contentrain/verify
|
|
2
|
+
|
|
3
|
+
Portable gates for a migrated site: identity, indexing, status, hreflang,
|
|
4
|
+
structured data, navigation and assets — checked over documents you already
|
|
5
|
+
have, with no network and no dependencies.
|
|
6
|
+
|
|
7
|
+
MIT. Part of the [Contentrain](https://contentrain.io) migration pipeline, and
|
|
8
|
+
usable on its own against any static build.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx @contentrain/verify dist --site https://example.com
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Why it does not fetch
|
|
15
|
+
|
|
16
|
+
A hardened crawler is a security surface of its own — SSRF, DNS rebinding,
|
|
17
|
+
redirect limits, byte and time caps, robots handling — and it belongs where that
|
|
18
|
+
hardening is maintained, not inside the MIT library every consumer embeds.
|
|
19
|
+
|
|
20
|
+
So this package takes documents. That constraint turns out to be the feature:
|
|
21
|
+
the same gate runs over a `dist/` directory, over pages a migration engine
|
|
22
|
+
already captured, and inside CI, with no network calls to mock and no flake.
|
|
23
|
+
Whoever holds a safe fetcher supplies the pages.
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { verify, formatReport } from '@contentrain/verify'
|
|
29
|
+
|
|
30
|
+
const report = verify({
|
|
31
|
+
site: 'https://example.com',
|
|
32
|
+
documents: [{ url: '/about', html, status: 200, headers }],
|
|
33
|
+
assets: ['/img/hero.png'],
|
|
34
|
+
sitemap: sitemapXml,
|
|
35
|
+
redirects: [{ from: '/old', to: '/about', status: 301 }],
|
|
36
|
+
baseline: { documents: oldPages }, // optional: enables the parity checks
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
report.passed // no error findings — this is the gate
|
|
40
|
+
report.findings // every finding, with a stable `check` id
|
|
41
|
+
report.skipped // what could not run, and why
|
|
42
|
+
console.log(formatReport(report))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or over a built directory:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { loadSiteDirectory, verify } from '@contentrain/verify'
|
|
49
|
+
|
|
50
|
+
const report = verify(await loadSiteDirectory('dist', 'https://example.com'))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## The checks
|
|
54
|
+
|
|
55
|
+
Severity is decided by one rule: **error** means the built site is wrong — a
|
|
56
|
+
visitor or a crawler gets something it should not. **warning** means it is worse
|
|
57
|
+
than the site it replaced, or worse than it should be. A migration that improves
|
|
58
|
+
a page must not fail its own gate.
|
|
59
|
+
|
|
60
|
+
| Group | Check | Severity | What it catches |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| identity | `identity.title-missing` | error | No non-empty `<title>` |
|
|
63
|
+
| | `identity.title-duplicate` | error | Two titles in the head — a crawler picks one, undefined which. A `<title>` inside an inline `<svg>` is an accessibility label and is not counted |
|
|
64
|
+
| | `identity.canonical-missing` | warning | No canonical link |
|
|
65
|
+
| | `identity.canonical-duplicate` | error | Two canonicals: the page says two things about itself |
|
|
66
|
+
| | `identity.canonical-mismatch` | error | **The clone failure.** A page carrying the template's canonical points the whole site at one URL and removes it from the index. Invisible in a browser, worse than having no canonical |
|
|
67
|
+
| | `identity.description-missing` | warning | No meta description |
|
|
68
|
+
| | `identity.open-graph-incomplete` | warning | Missing `og:title` / `og:type` / `og:url` — shares render without a card |
|
|
69
|
+
| | `identity.twitter-card-missing` | info | No `twitter:card` |
|
|
70
|
+
| indexing | `indexing.noindex` | error | **The staging leak.** A `noindex` in the meta or `X-Robots-Tag` survives deploy and the site disappears from search over the following weeks |
|
|
71
|
+
| | `indexing.sitemap-missing-entry` | warning | An indexable page is not in the sitemap (a `noindex` page is not asked to be) |
|
|
72
|
+
| | `indexing.sitemap-stale-entry` | warning | The sitemap lists a page the build does not serve |
|
|
73
|
+
| status | `status.mismatch` | error | The status changed against the baseline |
|
|
74
|
+
| | `status.soft-404` | error | A "not found" page answering 200 — invisible to every status monitor, indexed as content |
|
|
75
|
+
| | `status.redirect-chain` | warning | More hops than `maxRedirectHops` (default 1) |
|
|
76
|
+
| | `status.redirect-loop` | error | A chain that returns to itself |
|
|
77
|
+
| | `status.redirect-target-missing` | error | An internal redirect pointing at a page the build does not serve |
|
|
78
|
+
| international | `international.hreflang-not-reciprocal` | error | A names B but B does not name A. Search engines ignore one-sided alternates entirely, so half the work is worth nothing |
|
|
79
|
+
| | `international.hreflang-no-self` | warning | A page with alternates that does not list itself |
|
|
80
|
+
| | `international.hreflang-unknown-target` | warning | An alternate pointing at a page that does not exist |
|
|
81
|
+
| structured | `structured.jsonld-invalid` | error | A JSON-LD block that does not parse — consumers discard the whole block |
|
|
82
|
+
| | `structured.type-lost` | warning | A `@type` the old page had is gone. Types inside `@graph` are counted, which is how themes actually ship them |
|
|
83
|
+
| navigation | `navigation.broken-internal-link` | error | An internal link that goes nowhere. A path a redirect rule covers is fine; external, `mailto:`, `tel:` and fragment links are not checked |
|
|
84
|
+
| | `navigation.pagination-broken` | warning | `rel="next"` / `rel="prev"` pointing at a page the build does not serve |
|
|
85
|
+
| | `navigation.feed-lost` | warning | The old page advertised a feed and this one does not |
|
|
86
|
+
| assets | `assets.broken-image` | error | An internal image that resolves to nothing the build serves. `data:` URIs and third-party hosts are skipped |
|
|
87
|
+
| | `assets.missing-alt` | warning | An `<img>` with no `alt` attribute. `alt=""` is a decision (decorative) and is not reported |
|
|
88
|
+
| | `assets.alt-lost` | warning | Alt text the old page had for the same image is gone |
|
|
89
|
+
|
|
90
|
+
## Address identity
|
|
91
|
+
|
|
92
|
+
Every "does this go somewhere that exists" question is a normalisation question,
|
|
93
|
+
and getting it wrong in either direction is worse than not asking: too strict and
|
|
94
|
+
a correct site fails, too loose and a broken one passes.
|
|
95
|
+
|
|
96
|
+
- `/about`, `/about/` and `/about/index.html` are one address, because static
|
|
97
|
+
hosts serve them that way.
|
|
98
|
+
- Query strings and fragments are not part of a document's identity.
|
|
99
|
+
- Case is preserved, because static hosts are case-sensitive even when the
|
|
100
|
+
author assumed otherwise.
|
|
101
|
+
|
|
102
|
+
Without `site`, relative links still resolve (against a reserved origin) and
|
|
103
|
+
absolute links to real hosts are treated as external. Supplying `site` is better:
|
|
104
|
+
it is what makes `canonical-mismatch` and cross-origin checks exact.
|
|
105
|
+
|
|
106
|
+
## What a missing input does
|
|
107
|
+
|
|
108
|
+
Nothing is silently skipped. A run without a baseline, a sitemap or redirect
|
|
109
|
+
rules reports those absences in `report.skipped` — a green report over a run that
|
|
110
|
+
compared nothing is the most dangerous output this package could produce.
|
|
111
|
+
|
|
112
|
+
## CLI
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
contentrain-verify <dist-dir> [options]
|
|
116
|
+
|
|
117
|
+
--site <url> Canonical origin
|
|
118
|
+
--baseline <dir> The old site, as a directory of captured pages
|
|
119
|
+
--redirects <file> JSON: [{ "from": "/a", "to": "/b", "status": 301 }]
|
|
120
|
+
--groups <list> identity,indexing,status,international,structured,navigation,assets
|
|
121
|
+
--max-redirect-hops <n>
|
|
122
|
+
--json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Exits 1 when any finding is an error, 2 on a usage error. Unknown options and
|
|
126
|
+
unknown group names are rejected rather than ignored.
|
|
127
|
+
|
|
128
|
+
## Reading HTML
|
|
129
|
+
|
|
130
|
+
Targeted regexes, not a parser — the same choice `@contentrain/emitter-astro`
|
|
131
|
+
makes, for the same reason: these are machine-generated documents whose head the
|
|
132
|
+
pipeline controls, the extracted set is small and fixed, and a parser would put a
|
|
133
|
+
dependency into the one package a migration should be able to run anywhere.
|
|
134
|
+
|
|
135
|
+
The limits are honoured rather than papered over. Attribute reads require
|
|
136
|
+
whitespace before the name, so `data-src` never answers a read of `src` — which
|
|
137
|
+
matters, because lazy-loading WordPress themes put the real URL in `data-src` on
|
|
138
|
+
almost every image. Where a check would be wrong under these limits, it is not
|
|
139
|
+
written.
|
|
140
|
+
|
|
141
|
+
## Related
|
|
142
|
+
|
|
143
|
+
- [`@contentrain/emitter-astro`](../emitter-astro) — produces the site this verifies
|
|
144
|
+
- [`@contentrain/wp-import`](../wp-import) — produces the content
|
|
145
|
+
- [`@contentrain/types`](../types) — the shared contracts
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
package/dist/bin.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/bin.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { main } from "./cli.mjs";
|
|
3
|
+
//#region src/bin.ts
|
|
4
|
+
main(process.argv.slice(2)).then((code) => {
|
|
5
|
+
process.exitCode = code;
|
|
6
|
+
}, (error) => {
|
|
7
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
8
|
+
process.exitCode = 2;
|
|
9
|
+
});
|
|
10
|
+
//#endregion
|
|
11
|
+
export {};
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=bin.mjs.map
|
package/dist/bin.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.mjs","names":[],"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n// The executable. Kept separate from `cli.ts` so the shell can be imported and\n// tested without a module side effect that runs it.\n\nimport { main } from './cli.js'\n\nmain(process.argv.slice(2)).then(\n (code) => { process.exitCode = code },\n (error: unknown) => {\n console.error(error instanceof Error ? error.message : String(error))\n process.exitCode = 2\n },\n)\n"],"mappings":";;;AAMA,KAAK,QAAQ,KAAK,MAAM,EAAE,CAAC,CAAC,MACzB,SAAS;AAAE,SAAQ,WAAW;IAC9B,UAAmB;AAClB,SAAQ,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAAC;AACrE,SAAQ,WAAW;EAEtB"}
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { n as CheckGroup } from "./types-C_hmH1n3.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/cli.d.ts
|
|
4
|
+
declare const USAGE: string;
|
|
5
|
+
interface Args {
|
|
6
|
+
dir?: string;
|
|
7
|
+
site?: string;
|
|
8
|
+
baseline?: string;
|
|
9
|
+
redirects?: string;
|
|
10
|
+
groups?: CheckGroup[];
|
|
11
|
+
maxRedirectHops?: number;
|
|
12
|
+
json: boolean;
|
|
13
|
+
help: boolean;
|
|
14
|
+
}
|
|
15
|
+
declare function parseArgs(argv: readonly string[]): Args;
|
|
16
|
+
interface Console2 {
|
|
17
|
+
log: (line: string) => void;
|
|
18
|
+
error: (line: string) => void;
|
|
19
|
+
}
|
|
20
|
+
/** Returns the process exit code. `io` is injectable so tests read the output. */
|
|
21
|
+
declare function main(argv: readonly string[], io?: Console2): Promise<number>;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Args, Console2, USAGE, main, parseArgs };
|
|
24
|
+
//# sourceMappingURL=cli.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.mts","names":[],"sources":["../src/cli.ts"],"mappings":";;;cAca,KAAA;AAAA,UAWI,IAAA;EACf,GAAA;EACA,IAAA;EACA,QAAA;EACA,SAAA;EACA,MAAA,GAAS,UAAA;EACT,eAAA;EACA,IAAA;EACA,IAAA;AAAA;AAAA,iBAGc,SAAA,CAAU,IAAA,sBAA0B,IAAA;AAAA,UAyBnC,QAAA;EACf,GAAA,GAAM,IAAA;EACN,KAAA,GAAQ,IAAA;AAAA;;iBAIY,IAAA,CAAK,IAAA,qBAAyB,EAAA,GAAI,QAAA,GAAqB,OAAA"}
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { i as CHECK_GROUPS, n as formatReport, r as verify, t as loadSiteDirectory } from "./load-KUG-8Ug4.mjs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
//#region src/cli.ts
|
|
4
|
+
const USAGE = `contentrain-verify <dist-dir> [options]
|
|
5
|
+
|
|
6
|
+
--site <url> Canonical origin, e.g. https://example.com
|
|
7
|
+
--baseline <dir> The old site, as a directory of captured pages
|
|
8
|
+
--redirects <file> JSON: [{ "from": "/a", "to": "/b", "status": 301 }]
|
|
9
|
+
--groups <list> Comma-separated: ${CHECK_GROUPS.join(",")}
|
|
10
|
+
--max-redirect-hops <n>
|
|
11
|
+
--json Print the report as JSON instead of text
|
|
12
|
+
|
|
13
|
+
Exits 1 when any finding is an error, 2 on a usage error.`;
|
|
14
|
+
function parseArgs(argv) {
|
|
15
|
+
const args = {
|
|
16
|
+
json: false,
|
|
17
|
+
help: false
|
|
18
|
+
};
|
|
19
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
20
|
+
const arg = argv[i];
|
|
21
|
+
const next = () => argv[++i];
|
|
22
|
+
if (arg === "--help" || arg === "-h") args.help = true;
|
|
23
|
+
else if (arg === "--json") args.json = true;
|
|
24
|
+
else if (arg === "--site") args.site = next();
|
|
25
|
+
else if (arg === "--baseline") args.baseline = next();
|
|
26
|
+
else if (arg === "--redirects") args.redirects = next();
|
|
27
|
+
else if (arg === "--max-redirect-hops") {
|
|
28
|
+
const value = Number(next());
|
|
29
|
+
if (!Number.isInteger(value) || value < 0) throw new Error("--max-redirect-hops expects a non-negative integer");
|
|
30
|
+
args.maxRedirectHops = value;
|
|
31
|
+
} else if (arg === "--groups") {
|
|
32
|
+
const list = (next() ?? "").split(",").map((g) => g.trim()).filter(Boolean);
|
|
33
|
+
const unknown = list.filter((g) => !CHECK_GROUPS.includes(g));
|
|
34
|
+
if (unknown.length) throw new Error(`unknown group(s): ${unknown.join(", ")}`);
|
|
35
|
+
args.groups = list;
|
|
36
|
+
} else if (arg.startsWith("-")) throw new Error(`unknown option: ${arg}`);
|
|
37
|
+
else args.dir ??= arg;
|
|
38
|
+
}
|
|
39
|
+
return args;
|
|
40
|
+
}
|
|
41
|
+
/** Returns the process exit code. `io` is injectable so tests read the output. */
|
|
42
|
+
async function main(argv, io = console) {
|
|
43
|
+
let args;
|
|
44
|
+
try {
|
|
45
|
+
args = parseArgs(argv);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
io.error(`${error.message}\n\n${USAGE}`);
|
|
48
|
+
return 2;
|
|
49
|
+
}
|
|
50
|
+
if (args.help || !args.dir) {
|
|
51
|
+
io.log(USAGE);
|
|
52
|
+
return args.help ? 0 : 2;
|
|
53
|
+
}
|
|
54
|
+
const input = {
|
|
55
|
+
...await loadSiteDirectory(args.dir, args.site),
|
|
56
|
+
groups: args.groups,
|
|
57
|
+
options: args.maxRedirectHops === void 0 ? void 0 : { maxRedirectHops: args.maxRedirectHops }
|
|
58
|
+
};
|
|
59
|
+
if (args.baseline) input.baseline = { documents: (await loadSiteDirectory(args.baseline, args.site)).documents };
|
|
60
|
+
if (args.redirects) input.redirects = JSON.parse(await readFile(args.redirects, "utf8"));
|
|
61
|
+
const report = verify(input);
|
|
62
|
+
io.log(args.json ? JSON.stringify(report, null, 2) : formatReport(report));
|
|
63
|
+
return report.passed ? 0 : 1;
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { USAGE, main, parseArgs };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=cli.mjs.map
|
package/dist/cli.mjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["// ─── contentrain-verify ───\n//\n// A thin shell over `verify()`: read a built directory, run the gates, print\n// the report, return an exit code. Everything decisional lives in the library —\n// this adds argument parsing and a number, nothing else. `bin.ts` is the\n// executable; keeping the shell importable is what makes it testable without\n// spawning a process.\n\nimport { readFile } from 'node:fs/promises'\nimport { verify, formatReport } from './verify.js'\nimport { loadSiteDirectory } from './load.js'\nimport { CHECK_GROUPS } from './types.js'\nimport type { CheckGroup, VerifyInput } from './types.js'\n\nexport const USAGE = `contentrain-verify <dist-dir> [options]\n\n --site <url> Canonical origin, e.g. https://example.com\n --baseline <dir> The old site, as a directory of captured pages\n --redirects <file> JSON: [{ \"from\": \"/a\", \"to\": \"/b\", \"status\": 301 }]\n --groups <list> Comma-separated: ${CHECK_GROUPS.join(',')}\n --max-redirect-hops <n>\n --json Print the report as JSON instead of text\n\nExits 1 when any finding is an error, 2 on a usage error.`\n\nexport interface Args {\n dir?: string\n site?: string\n baseline?: string\n redirects?: string\n groups?: CheckGroup[]\n maxRedirectHops?: number\n json: boolean\n help: boolean\n}\n\nexport function parseArgs(argv: readonly string[]): Args {\n const args: Args = { json: false, help: false }\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i]!\n const next = (): string | undefined => argv[++i]\n if (arg === '--help' || arg === '-h') args.help = true\n else if (arg === '--json') args.json = true\n else if (arg === '--site') args.site = next()\n else if (arg === '--baseline') args.baseline = next()\n else if (arg === '--redirects') args.redirects = next()\n else if (arg === '--max-redirect-hops') {\n const value = Number(next())\n if (!Number.isInteger(value) || value < 0) throw new Error('--max-redirect-hops expects a non-negative integer')\n args.maxRedirectHops = value\n } else if (arg === '--groups') {\n const list = (next() ?? '').split(',').map(g => g.trim()).filter(Boolean)\n const unknown = list.filter(g => !(CHECK_GROUPS as readonly string[]).includes(g))\n if (unknown.length) throw new Error(`unknown group(s): ${unknown.join(', ')}`)\n args.groups = list as CheckGroup[]\n } else if (arg.startsWith('-')) throw new Error(`unknown option: ${arg}`)\n else args.dir ??= arg\n }\n return args\n}\n\nexport interface Console2 {\n log: (line: string) => void\n error: (line: string) => void\n}\n\n/** Returns the process exit code. `io` is injectable so tests read the output. */\nexport async function main(argv: readonly string[], io: Console2 = console): Promise<number> {\n let args: Args\n try {\n args = parseArgs(argv)\n } catch (error) {\n io.error(`${(error as Error).message}\\n\\n${USAGE}`)\n return 2\n }\n\n if (args.help || !args.dir) {\n io.log(USAGE)\n return args.help ? 0 : 2\n }\n\n const loaded = await loadSiteDirectory(args.dir, args.site)\n const input: VerifyInput = {\n ...loaded,\n groups: args.groups,\n options: args.maxRedirectHops === undefined ? undefined : { maxRedirectHops: args.maxRedirectHops },\n }\n\n if (args.baseline) {\n const before = await loadSiteDirectory(args.baseline, args.site)\n input.baseline = { documents: before.documents }\n }\n if (args.redirects) {\n input.redirects = JSON.parse(await readFile(args.redirects, 'utf8')) as VerifyInput['redirects']\n }\n\n const report = verify(input)\n io.log(args.json ? JSON.stringify(report, null, 2) : formatReport(report))\n return report.passed ? 0 : 1\n}\n"],"mappings":";;;AAcA,MAAa,QAAQ;;;;;2CAKsB,aAAa,KAAK,IAAI,CAAC;;;;;AAiBlE,SAAgB,UAAU,MAA+B;CACvD,MAAM,OAAa;EAAE,MAAM;EAAO,MAAM;EAAO;AAC/C,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;EACvC,MAAM,MAAM,KAAK;EACjB,MAAM,aAAiC,KAAK,EAAE;AAC9C,MAAI,QAAQ,YAAY,QAAQ,KAAM,MAAK,OAAO;WACzC,QAAQ,SAAU,MAAK,OAAO;WAC9B,QAAQ,SAAU,MAAK,OAAO,MAAM;WACpC,QAAQ,aAAc,MAAK,WAAW,MAAM;WAC5C,QAAQ,cAAe,MAAK,YAAY,MAAM;WAC9C,QAAQ,uBAAuB;GACtC,MAAM,QAAQ,OAAO,MAAM,CAAC;AAC5B,OAAI,CAAC,OAAO,UAAU,MAAM,IAAI,QAAQ,EAAG,OAAM,IAAI,MAAM,qDAAqD;AAChH,QAAK,kBAAkB;aACd,QAAQ,YAAY;GAC7B,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,KAAI,MAAK,EAAE,MAAM,CAAC,CAAC,OAAO,QAAQ;GACzE,MAAM,UAAU,KAAK,QAAO,MAAK,CAAE,aAAmC,SAAS,EAAE,CAAC;AAClF,OAAI,QAAQ,OAAQ,OAAM,IAAI,MAAM,qBAAqB,QAAQ,KAAK,KAAK,GAAG;AAC9E,QAAK,SAAS;aACL,IAAI,WAAW,IAAI,CAAE,OAAM,IAAI,MAAM,mBAAmB,MAAM;MACpE,MAAK,QAAQ;;AAEpB,QAAO;;;AAST,eAAsB,KAAK,MAAyB,KAAe,SAA0B;CAC3F,IAAI;AACJ,KAAI;AACF,SAAO,UAAU,KAAK;UACf,OAAO;AACd,KAAG,MAAM,GAAI,MAAgB,QAAQ,MAAM,QAAQ;AACnD,SAAO;;AAGT,KAAI,KAAK,QAAQ,CAAC,KAAK,KAAK;AAC1B,KAAG,IAAI,MAAM;AACb,SAAO,KAAK,OAAO,IAAI;;CAIzB,MAAM,QAAqB;EACzB,GAFa,MAAM,kBAAkB,KAAK,KAAK,KAAK,KAAK;EAGzD,QAAQ,KAAK;EACb,SAAS,KAAK,oBAAoB,KAAA,IAAY,KAAA,IAAY,EAAE,iBAAiB,KAAK,iBAAiB;EACpG;AAED,KAAI,KAAK,SAEP,OAAM,WAAW,EAAE,YADJ,MAAM,kBAAkB,KAAK,UAAU,KAAK,KAAK,EAC3B,WAAW;AAElD,KAAI,KAAK,UACP,OAAM,YAAY,KAAK,MAAM,MAAM,SAAS,KAAK,WAAW,OAAO,CAAC;CAGtE,MAAM,SAAS,OAAO,MAAM;AAC5B,IAAG,IAAI,KAAK,OAAO,KAAK,UAAU,QAAQ,MAAM,EAAE,GAAG,aAAa,OAAO,CAAC;AAC1E,QAAO,OAAO,SAAS,IAAI"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { a as VerifyDocument, c as VerifyRedirect, i as Severity, l as VerifyReport, n as CheckGroup, o as VerifyInput, r as Finding, s as VerifyOptions, t as CHECK_GROUPS } from "./types-C_hmH1n3.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/verify.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Run the gates over a built site.
|
|
6
|
+
*
|
|
7
|
+
* Nothing is fetched. `input.documents` is the built site as served, and
|
|
8
|
+
* `input.baseline` — when the caller has one — is the site it replaced. Without
|
|
9
|
+
* a baseline every self-check still runs; only the parity checks (status
|
|
10
|
+
* change, lost structured data, lost alt text, lost feed) go quiet, because
|
|
11
|
+
* there is nothing to compare against.
|
|
12
|
+
*/
|
|
13
|
+
declare function verify(input: VerifyInput): VerifyReport;
|
|
14
|
+
/** A report as lines, most severe first. Used by the CLI and by consumers. */
|
|
15
|
+
declare function formatReport(report: VerifyReport): string;
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/load.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Load a static build as a verification input: every `.html` becomes a
|
|
20
|
+
* document served at its path, everything else becomes an asset.
|
|
21
|
+
*
|
|
22
|
+
* `dist/about/index.html` is served at `/about/`, which `identity()` then
|
|
23
|
+
* reduces to `/about` — the same address a link to `/about/` or
|
|
24
|
+
* `/about/index.html` resolves to.
|
|
25
|
+
*/
|
|
26
|
+
declare function loadSiteDirectory(dir: string, site?: string): Promise<Pick<VerifyInput, 'documents' | 'assets' | 'sitemap' | 'site'>>;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { CHECK_GROUPS, type CheckGroup, type Finding, type Severity, type VerifyDocument, type VerifyInput, type VerifyOptions, type VerifyRedirect, type VerifyReport, formatReport, loadSiteDirectory, verify };
|
|
29
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/verify.ts","../src/load.ts"],"mappings":";;;;;AAsDA;;;;;;;iBAAgB,MAAA,CAAO,KAAA,EAAO,WAAA,GAAc,YAAA;;iBAuC5B,YAAA,CAAa,MAAA,EAAQ,YAAA;;;;;AAvCrC;;;;;;iBCpBsB,iBAAA,CAAkB,GAAA,UAAa,IAAA,YAAgB,OAAA,CAAQ,IAAA,CAAK,WAAA"}
|
package/dist/index.mjs
ADDED