@forumulate/cli 0.9.0 → 0.10.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 +3 -3
- package/dist/api/args.d.ts.map +1 -1
- package/dist/api/args.js +1 -0
- package/dist/api/args.js.map +1 -1
- package/dist/api/command.d.ts +1 -1
- package/dist/api/command.d.ts.map +1 -1
- package/dist/api/command.js +86 -11
- package/dist/api/command.js.map +1 -1
- package/dist/api/http.d.ts +8 -0
- package/dist/api/http.d.ts.map +1 -1
- package/dist/api/http.js +3 -2
- package/dist/api/http.js.map +1 -1
- package/dist/api/live-spec.d.ts +48 -0
- package/dist/api/live-spec.d.ts.map +1 -0
- package/dist/api/live-spec.js +72 -0
- package/dist/api/live-spec.js.map +1 -0
- package/dist/api/operations.json +1 -1
- package/dist/api/request.d.ts.map +1 -1
- package/dist/api/request.js +25 -1
- package/dist/api/request.js.map +1 -1
- package/dist/api/send.d.ts +10 -1
- package/dist/api/send.d.ts.map +1 -1
- package/dist/api/send.js +14 -3
- package/dist/api/send.js.map +1 -1
- package/dist/api/slim.d.ts +14 -0
- package/dist/api/slim.d.ts.map +1 -0
- package/dist/api/slim.js +174 -0
- package/dist/api/slim.js.map +1 -0
- package/dist/cache-command.d.ts +12 -0
- package/dist/cache-command.d.ts.map +1 -0
- package/dist/cache-command.js +93 -0
- package/dist/cache-command.js.map +1 -0
- package/dist/check/command.d.ts +48 -0
- package/dist/check/command.d.ts.map +1 -0
- package/dist/check/command.js +256 -0
- package/dist/check/command.js.map +1 -0
- package/dist/check/external.d.ts +66 -0
- package/dist/check/external.d.ts.map +1 -0
- package/dist/check/external.js +177 -0
- package/dist/check/external.js.map +1 -0
- package/dist/check/markdown-links.d.ts +38 -0
- package/dist/check/markdown-links.d.ts.map +1 -0
- package/dist/check/markdown-links.js +166 -0
- package/dist/check/markdown-links.js.map +1 -0
- package/dist/cli.js +42 -7
- package/dist/cli.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +1 -0
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +6 -2
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/groups-api.d.ts.map +1 -1
- package/dist/commands/groups-api.js +20 -2
- package/dist/commands/groups-api.js.map +1 -1
- package/dist/commands/groups-docs.d.ts.map +1 -1
- package/dist/commands/groups-docs.js +8 -2
- package/dist/commands/groups-docs.js.map +1 -1
- package/dist/commands/groups-jobs.d.ts +10 -0
- package/dist/commands/groups-jobs.d.ts.map +1 -0
- package/dist/commands/groups-jobs.js +89 -0
- package/dist/commands/groups-jobs.js.map +1 -0
- package/dist/commands/groups-org.d.ts.map +1 -1
- package/dist/commands/groups-org.js +59 -2
- package/dist/commands/groups-org.js.map +1 -1
- package/dist/commands/groups-site.d.ts.map +1 -1
- package/dist/commands/groups-site.js +33 -3
- package/dist/commands/groups-site.js.map +1 -1
- package/dist/commands/groups-spaces.d.ts.map +1 -1
- package/dist/commands/groups-spaces.js +11 -5
- package/dist/commands/groups-spaces.js.map +1 -1
- package/dist/commands/groups-types.d.ts.map +1 -1
- package/dist/commands/groups-types.js +74 -0
- package/dist/commands/groups-types.js.map +1 -1
- package/dist/commands/groups.d.ts.map +1 -1
- package/dist/commands/groups.js +2 -0
- package/dist/commands/groups.js.map +1 -1
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +18 -3
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/job-wait.d.ts +64 -0
- package/dist/commands/job-wait.d.ts.map +1 -0
- package/dist/commands/job-wait.js +91 -0
- package/dist/commands/job-wait.js.map +1 -0
- package/dist/commands/resource.d.ts +19 -0
- package/dist/commands/resource.d.ts.map +1 -1
- package/dist/commands/resource.js +65 -3
- package/dist/commands/resource.js.map +1 -1
- package/dist/content/library.d.ts +8 -0
- package/dist/content/library.d.ts.map +1 -1
- package/dist/content/library.js +10 -2
- package/dist/content/library.js.map +1 -1
- package/dist/credentials.d.ts +43 -4
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +67 -4
- package/dist/credentials.js.map +1 -1
- package/dist/doctor/address-checks.d.ts +56 -0
- package/dist/doctor/address-checks.d.ts.map +1 -0
- package/dist/doctor/address-checks.js +183 -0
- package/dist/doctor/address-checks.js.map +1 -0
- package/dist/doctor/boilerplate.d.ts +33 -0
- package/dist/doctor/boilerplate.d.ts.map +1 -0
- package/dist/doctor/boilerplate.js +78 -0
- package/dist/doctor/boilerplate.js.map +1 -0
- package/dist/doctor/command.d.ts +18 -0
- package/dist/doctor/command.d.ts.map +1 -0
- package/dist/doctor/command.js +234 -0
- package/dist/doctor/command.js.map +1 -0
- package/dist/doctor/crawl.d.ts +49 -0
- package/dist/doctor/crawl.d.ts.map +1 -0
- package/dist/doctor/crawl.js +210 -0
- package/dist/doctor/crawl.js.map +1 -0
- package/dist/doctor/extended.d.ts +52 -0
- package/dist/doctor/extended.d.ts.map +1 -0
- package/dist/doctor/extended.js +150 -0
- package/dist/doctor/extended.js.map +1 -0
- package/dist/doctor/page-checks.d.ts +42 -0
- package/dist/doctor/page-checks.d.ts.map +1 -0
- package/dist/doctor/page-checks.js +200 -0
- package/dist/doctor/page-checks.js.map +1 -0
- package/dist/doctor/probe.d.ts +63 -0
- package/dist/doctor/probe.d.ts.map +1 -0
- package/dist/doctor/probe.js +170 -0
- package/dist/doctor/probe.js.map +1 -0
- package/dist/doctor/reader-auth-atlas.d.ts +18 -0
- package/dist/doctor/reader-auth-atlas.d.ts.map +1 -0
- package/dist/doctor/reader-auth-atlas.js +50 -0
- package/dist/doctor/reader-auth-atlas.js.map +1 -0
- package/dist/doctor/robots-params.d.ts +36 -0
- package/dist/doctor/robots-params.d.ts.map +1 -0
- package/dist/doctor/robots-params.js +97 -0
- package/dist/doctor/robots-params.js.map +1 -0
- package/dist/doctor/site.d.ts +42 -0
- package/dist/doctor/site.d.ts.map +1 -0
- package/dist/doctor/site.js +82 -0
- package/dist/doctor/site.js.map +1 -0
- package/dist/doctor.d.ts +21 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +94 -3
- package/dist/doctor.js.map +1 -1
- package/dist/exit-codes.d.ts +40 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +66 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/help-commands.d.ts +13 -0
- package/dist/help-commands.d.ts.map +1 -0
- package/dist/help-commands.js +215 -0
- package/dist/help-commands.js.map +1 -0
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +57 -11
- package/dist/help.js.map +1 -1
- package/dist/io.d.ts +33 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +81 -0
- package/dist/io.js.map +1 -0
- package/dist/preview-comment.d.ts +20 -0
- package/dist/preview-comment.d.ts.map +1 -0
- package/dist/preview-comment.js +88 -0
- package/dist/preview-comment.js.map +1 -0
- package/dist/publish/about.d.ts +23 -0
- package/dist/publish/about.d.ts.map +1 -0
- package/dist/publish/about.js +66 -0
- package/dist/publish/about.js.map +1 -0
- package/dist/publish/command.d.ts +20 -0
- package/dist/publish/command.d.ts.map +1 -1
- package/dist/publish/command.js +97 -4
- package/dist/publish/command.js.map +1 -1
- package/dist/publish/context.d.ts +8 -0
- package/dist/publish/context.d.ts.map +1 -1
- package/dist/publish/context.js.map +1 -1
- package/dist/publish/docs-apply.d.ts.map +1 -1
- package/dist/publish/docs-apply.js +13 -1
- package/dist/publish/docs-apply.js.map +1 -1
- package/dist/publish/extras.d.ts +10 -0
- package/dist/publish/extras.d.ts.map +1 -1
- package/dist/publish/extras.js +54 -2
- package/dist/publish/extras.js.map +1 -1
- package/dist/publish/manifest.d.ts.map +1 -1
- package/dist/publish/manifest.js +27 -2
- package/dist/publish/manifest.js.map +1 -1
- package/dist/publish/preview.d.ts +56 -0
- package/dist/publish/preview.d.ts.map +1 -0
- package/dist/publish/preview.js +133 -0
- package/dist/publish/preview.js.map +1 -0
- package/dist/publish/redirects.d.ts +13 -0
- package/dist/publish/redirects.d.ts.map +1 -0
- package/dist/publish/redirects.js +154 -0
- package/dist/publish/redirects.js.map +1 -0
- package/dist/publish/studio-edits.d.ts +25 -0
- package/dist/publish/studio-edits.d.ts.map +1 -0
- package/dist/publish/studio-edits.js +53 -0
- package/dist/publish/studio-edits.js.map +1 -0
- package/dist/publish/types.d.ts +22 -1
- package/dist/publish/types.d.ts.map +1 -1
- package/dist/publish-command.d.ts +55 -0
- package/dist/publish-command.d.ts.map +1 -0
- package/dist/publish-command.js +197 -0
- package/dist/publish-command.js.map +1 -0
- package/dist/run.d.ts +3 -10
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +122 -110
- package/dist/run.js.map +1 -1
- package/dist/translations/target.d.ts.map +1 -1
- package/dist/translations/target.js +2 -2
- package/dist/translations/target.js.map +1 -1
- package/dist/validate.d.ts +39 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +166 -0
- package/dist/validate.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/dist/whoami.d.ts +41 -0
- package/dist/whoami.d.ts.map +1 -0
- package/dist/whoami.js +161 -0
- package/dist/whoami.js.map +1 -0
- package/package.json +2 -2
- package/dist/publish-dir.d.ts +0 -54
- package/dist/publish-dir.d.ts.map +0 -1
- package/dist/publish-dir.js +0 -119
- package/dist/publish-dir.js.map +0 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DOCTOR_HELP = exports.DOCTOR_CHECK_IDS = void 0;
|
|
4
|
+
exports.runDoctorCommand = runDoctorCommand;
|
|
5
|
+
exports.printReport = printReport;
|
|
6
|
+
const args_1 = require("../api/args");
|
|
7
|
+
const http_1 = require("../api/http");
|
|
8
|
+
const api_base_1 = require("../api-base");
|
|
9
|
+
const config_1 = require("../commands/config");
|
|
10
|
+
const credentials_1 = require("../credentials");
|
|
11
|
+
const exit_codes_1 = require("../exit-codes");
|
|
12
|
+
const doctor_1 = require("../doctor");
|
|
13
|
+
const extended_1 = require("./extended");
|
|
14
|
+
const site_1 = require("./site");
|
|
15
|
+
const probe_1 = require("./probe");
|
|
16
|
+
const reader_auth_atlas_1 = require("./reader-auth-atlas");
|
|
17
|
+
exports.DOCTOR_CHECK_IDS = [
|
|
18
|
+
...doctor_1.BASE_CHECK_IDS,
|
|
19
|
+
...extended_1.EXTENDED_CHECK_IDS,
|
|
20
|
+
reader_auth_atlas_1.READER_AUTH_ATLAS_CHECK,
|
|
21
|
+
];
|
|
22
|
+
exports.DOCTOR_HELP = `forumulate doctor — check a live site from the outside
|
|
23
|
+
|
|
24
|
+
Usage:
|
|
25
|
+
forumulate doctor <readerUrl> [flags]
|
|
26
|
+
forumulate doctor --site <slug|id> [--org <slug|id>] [flags]
|
|
27
|
+
|
|
28
|
+
Plain HTTP, no token needed for a URL: the same report against our platform, a
|
|
29
|
+
staging mount or any other docs host. --site asks the API for every address of
|
|
30
|
+
every space on a site (needs a token), and checks each copy.
|
|
31
|
+
|
|
32
|
+
Flags:
|
|
33
|
+
--site <slug|id> every member space, at its custom domain AND its
|
|
34
|
+
platform copy (token: FORUMULATE_TOKEN or \`login\`)
|
|
35
|
+
--org <slug|id> the org --site is in (default: FORUMULATE_ORG / config)
|
|
36
|
+
--also <url> another address of the same page, e.g. the platform copy
|
|
37
|
+
of a custom-domain page (repeatable)
|
|
38
|
+
--only a,b run just these checks (ids below)
|
|
39
|
+
--json the full report as JSON on stdout (nothing else there)
|
|
40
|
+
--api-base <origin> where /api/health lives, when not on the page's host
|
|
41
|
+
--platform-host <h> the hosting platform's hostname, for white-label and
|
|
42
|
+
canonical checks (default: FORUMULATE_URL's host, else
|
|
43
|
+
${extended_1.DEFAULT_PLATFORM_HOST})
|
|
44
|
+
--limit <n> sitemap URLs to sample (default ${extended_1.DEFAULT_SAMPLE_LIMIT})
|
|
45
|
+
--timeout <ms> per-request timeout (default 15000)
|
|
46
|
+
|
|
47
|
+
Checks (id — what fails it):
|
|
48
|
+
health /api/health not 2xx JSON (also tried under the mount)
|
|
49
|
+
public-read a logged-out reader is sent to sign-in
|
|
50
|
+
canonical the page's canonical names another host
|
|
51
|
+
html-untouched a proxy rewrites the markup (email obfuscation)
|
|
52
|
+
cache-headers no Cache-Control, or no-store, on the page
|
|
53
|
+
robots / sitemap robots.txt missing, disallows the path, no Sitemap:
|
|
54
|
+
crawler-access a crawler gets a different status than a reader
|
|
55
|
+
feeds an advertised feed does not answer 200
|
|
56
|
+
images a picture on the page does not load
|
|
57
|
+
canonical-both-addresses a page's copies (custom domain, platform) name
|
|
58
|
+
different canonicals, or not the primary address
|
|
59
|
+
sitemap-crawl a sitemap URL redirects, 404s, is on another host or
|
|
60
|
+
names another canonical
|
|
61
|
+
cache-consistency pages of one space cached differently (public vs
|
|
62
|
+
private/no-store) with no x-forumulate-cache-reason
|
|
63
|
+
root-redirect a space root answers 302/307, or a redirect with a
|
|
64
|
+
body over 1 KB, or lands on a page canonical elsewhere
|
|
65
|
+
meta-description missing description/og:description, template text
|
|
66
|
+
("Guides and reference, in a nested tree."), or
|
|
67
|
+
headings run together
|
|
68
|
+
llms-txt /llms.txt missing, no H1, preset summary, links on
|
|
69
|
+
another host, slugs in titles, no sections/descriptions
|
|
70
|
+
favicon /favicon.ico or <link rel=icon> not an image; an
|
|
71
|
+
asset path answering a large HTML page
|
|
72
|
+
consent-tag a consent manager (any CMP: Cookie Munch, Cookiebot,
|
|
73
|
+
OneTrust, Osano, Termly, Usercentrics, Didomi,
|
|
74
|
+
iubenda, Complianz, TCF) not told the page language
|
|
75
|
+
white-label-leaks a custom-domain page or its .md twin links to the
|
|
76
|
+
platform host
|
|
77
|
+
robots-params (advice) prints the Disallow lines that keep crawlers
|
|
78
|
+
off each mount's ?sort= / ?period= / ?range= copies
|
|
79
|
+
reader-auth-atlas (advice, needs --org and a token) the organization
|
|
80
|
+
uses Atlas but signs readers in another way (an
|
|
81
|
+
external_jwt route): prints the relink and the switch
|
|
82
|
+
to the Atlas reader provider (readerAuth: atlas)
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
forumulate doctor https://docs.acme.com/docs
|
|
86
|
+
forumulate doctor https://acme.com/legal/privacy --also https://forumulate.com/p/acme/legal/privacy
|
|
87
|
+
forumulate doctor --site main --org acme --json > doctor.json
|
|
88
|
+
forumulate doctor https://acme.com/docs --only sitemap-crawl,root-redirect
|
|
89
|
+
|
|
90
|
+
In CI: packages/cli/examples/github-action-doctor.yml runs this on a schedule and
|
|
91
|
+
on every push, uploads the JSON report and fails the job on any FAIL.
|
|
92
|
+
|
|
93
|
+
${exit_codes_1.EXIT_CODES_HELP}
|
|
94
|
+
doctor: 1 when any check FAILs; a SKIP is not a failure.`;
|
|
95
|
+
const SWITCHES = new Set(['json', 'help', 'h']);
|
|
96
|
+
/** `forumulate doctor …` → an exit code. */
|
|
97
|
+
async function runDoctorCommand(argv, deps) {
|
|
98
|
+
const p = (0, args_1.parseArgs)(argv, SWITCHES);
|
|
99
|
+
if ((0, args_1.has)(p, 'help') || (0, args_1.has)(p, 'h')) {
|
|
100
|
+
deps.out(exports.DOCTOR_HELP);
|
|
101
|
+
return exit_codes_1.EXIT.OK;
|
|
102
|
+
}
|
|
103
|
+
const env = deps.env ?? process.env;
|
|
104
|
+
const json = (0, args_1.has)(p, 'json');
|
|
105
|
+
const usage = (why) => {
|
|
106
|
+
deps.err(why);
|
|
107
|
+
deps.err('Usage: forumulate doctor <readerUrl> | --site <slug|id> [--org …] (doctor --help)');
|
|
108
|
+
return exit_codes_1.EXIT.USAGE;
|
|
109
|
+
};
|
|
110
|
+
const only = (0, args_1.all)(p, 'only')
|
|
111
|
+
.flatMap((v) => v.split(','))
|
|
112
|
+
.map((v) => v.trim())
|
|
113
|
+
.filter(Boolean);
|
|
114
|
+
const unknown = only.filter((id) => !exports.DOCTOR_CHECK_IDS.includes(id));
|
|
115
|
+
if (unknown.length)
|
|
116
|
+
return usage(`Unknown check id(s): ${unknown.join(', ')}. Known: ${exports.DOCTOR_CHECK_IDS.join(', ')}.`);
|
|
117
|
+
const limit = (0, args_1.flag)(p, 'limit') === undefined ? undefined : Number((0, args_1.flag)(p, 'limit'));
|
|
118
|
+
if (limit !== undefined && !(Number.isInteger(limit) && limit > 0))
|
|
119
|
+
return usage('--limit takes a positive whole number.');
|
|
120
|
+
const timeout = (0, args_1.flag)(p, 'timeout') === undefined ? undefined : Number((0, args_1.flag)(p, 'timeout'));
|
|
121
|
+
if (timeout !== undefined && !(Number.isInteger(timeout) && timeout > 0))
|
|
122
|
+
return usage('--timeout takes a positive number of milliseconds.');
|
|
123
|
+
let url = p.positionals[0];
|
|
124
|
+
if (p.positionals.length > 1)
|
|
125
|
+
return usage(`One URL at a time; got ${p.positionals.join(' ')}.`);
|
|
126
|
+
if (url !== undefined) {
|
|
127
|
+
try {
|
|
128
|
+
const parsed = new URL(url);
|
|
129
|
+
if (!/^https?:$/.test(parsed.protocol))
|
|
130
|
+
throw new Error('not http');
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return usage(`"${url}" is not an http(s) URL.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const siteUrl = (0, api_base_1.siteFromEnv)(env);
|
|
137
|
+
const platformHost = ((0, args_1.flag)(p, 'platform-host') ??
|
|
138
|
+
(siteUrl ? new URL(siteUrl).host : undefined) ??
|
|
139
|
+
extended_1.DEFAULT_PLATFORM_HOST).toLowerCase();
|
|
140
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
141
|
+
const addressSets = [];
|
|
142
|
+
let roots;
|
|
143
|
+
const site = (0, args_1.flag)(p, 'site');
|
|
144
|
+
// The organization's own checks (reader-auth-atlas) need an org and a token;
|
|
145
|
+
// `--site` needs both anyway.
|
|
146
|
+
const orgFlag = (0, args_1.flag)(p, 'org');
|
|
147
|
+
const orgRef = site || orgFlag ? (0, config_1.setting)('org', orgFlag, env, (0, config_1.readConfig)(env)) : undefined;
|
|
148
|
+
const apiHttp = () => {
|
|
149
|
+
const baseUrl = (0, api_base_1.apiBaseFromEnv)(env);
|
|
150
|
+
return (deps.http ??
|
|
151
|
+
new http_1.Http({
|
|
152
|
+
baseUrl,
|
|
153
|
+
token: (0, credentials_1.resolveToken)(baseUrl, env, deps.flagToken),
|
|
154
|
+
userAgent: deps.userAgent ?? '@forumulate/cli',
|
|
155
|
+
fetch: fetchImpl,
|
|
156
|
+
}));
|
|
157
|
+
};
|
|
158
|
+
if (site) {
|
|
159
|
+
const org = orgRef;
|
|
160
|
+
if (!org)
|
|
161
|
+
return usage('--site needs --org <slug|id> (or FORUMULATE_ORG, or `config set org`).');
|
|
162
|
+
const http = apiHttp();
|
|
163
|
+
try {
|
|
164
|
+
const resolved = await (0, site_1.resolveSite)(http, org, site);
|
|
165
|
+
for (const problem of resolved.problems)
|
|
166
|
+
deps.err(`note: ${problem}`);
|
|
167
|
+
if (!url)
|
|
168
|
+
url = resolved.url;
|
|
169
|
+
if (!url) {
|
|
170
|
+
deps.err(`Site ${site} has no space with a public address to check.`);
|
|
171
|
+
return exit_codes_1.EXIT.NOT_FOUND;
|
|
172
|
+
}
|
|
173
|
+
roots = resolved.spaces.flatMap((s) => s.endpoints.map((e) => e.url.replace(/\/+$/, '')));
|
|
174
|
+
const get = (0, probe_1.makeGet)({ fetchImpl, timeoutMs: timeout });
|
|
175
|
+
addressSets.push(...(await (0, site_1.addressSetsFor)(get, resolved)));
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
const err = e;
|
|
179
|
+
deps.err(`Could not read site ${site}: ${err.message ?? String(e)}`);
|
|
180
|
+
return (0, exit_codes_1.exitCodeForError)(e);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!url)
|
|
184
|
+
return usage('Give a reader URL, or --site <slug|id>.');
|
|
185
|
+
const also = (0, args_1.all)(p, 'also');
|
|
186
|
+
if (also.length)
|
|
187
|
+
addressSets.push({ urls: [url, ...also] });
|
|
188
|
+
const report = await (0, doctor_1.runDoctor)({
|
|
189
|
+
url,
|
|
190
|
+
apiBase: (0, args_1.flag)(p, 'api-base') ?? siteUrl,
|
|
191
|
+
fetchImpl,
|
|
192
|
+
only: only.length ? only : undefined,
|
|
193
|
+
platformHost,
|
|
194
|
+
// The platform the copies are compared against: the configured API when
|
|
195
|
+
// one is, else the API of whichever host --platform-host named.
|
|
196
|
+
platformApi: env.FORUMULATE_API_URL?.trim() || env.FORUMULATE_URL?.trim()
|
|
197
|
+
? (0, api_base_1.apiBaseFromEnv)(env)
|
|
198
|
+
: `https://${platformHost}/api`,
|
|
199
|
+
addressSets: addressSets.length ? addressSets : undefined,
|
|
200
|
+
roots,
|
|
201
|
+
limit,
|
|
202
|
+
timeoutMs: timeout,
|
|
203
|
+
});
|
|
204
|
+
// FMU-X01: the organization's reader sign-in, from the API's own recommendation.
|
|
205
|
+
if (orgRef && (!only.length || only.includes(reader_auth_atlas_1.READER_AUTH_ATLAS_CHECK))) {
|
|
206
|
+
const c = await (0, reader_auth_atlas_1.readerAuthAtlasCheck)(apiHttp(), orgRef);
|
|
207
|
+
report.checks.push(c);
|
|
208
|
+
if (c.state === 'pass')
|
|
209
|
+
report.passed += 1;
|
|
210
|
+
else if (c.state === 'fail')
|
|
211
|
+
report.failed += 1;
|
|
212
|
+
else
|
|
213
|
+
report.warnings += 1;
|
|
214
|
+
}
|
|
215
|
+
if (json)
|
|
216
|
+
deps.out(JSON.stringify(report, null, 2));
|
|
217
|
+
else
|
|
218
|
+
printReport(report, deps.out);
|
|
219
|
+
return report.failed > 0 ? exit_codes_1.EXIT.FAILED : exit_codes_1.EXIT.OK;
|
|
220
|
+
}
|
|
221
|
+
function printReport(report, out) {
|
|
222
|
+
out(`Checking ${report.url}`);
|
|
223
|
+
for (const c of report.checks) {
|
|
224
|
+
const mark = c.state === 'pass' ? 'PASS' : c.state === 'fail' ? 'FAIL' : 'SKIP';
|
|
225
|
+
out(` ${mark} ${c.id} ${c.title}`);
|
|
226
|
+
// A detail may be several lines (robots-params prints a robots.txt block).
|
|
227
|
+
for (const line of c.detail.split('\n'))
|
|
228
|
+
out(` ${line}`);
|
|
229
|
+
if (c.state === 'fail' && c.matters)
|
|
230
|
+
out(` why it matters: ${c.matters}`);
|
|
231
|
+
}
|
|
232
|
+
out(`${report.passed} passed, ${report.failed} failed, ${report.warnings} skipped`);
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/doctor/command.ts"],"names":[],"mappings":";;;AA2GA,4CA4HC;AAED,kCAUC;AAnPD,sCAAwD;AACxD,sCAAmC;AACnC,0CAA0D;AAC1D,+CAAyD;AACzD,gDAA8C;AAC9C,8CAAwE;AACxE,sCAAyE;AACzE,yCAA6F;AAC7F,iCAAqD;AACrD,mCAAkC;AAElC,2DAAoF;AAEvE,QAAA,gBAAgB,GAAsB;IACjD,GAAG,uBAAc;IACjB,GAAG,6BAAkB;IACrB,2CAAuB;CACxB,CAAC;AAEW,QAAA,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;yBAqBF,gCAAqB;yDACW,+BAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiD3E,4BAAe;2DAC0C,CAAC;AAa5D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC;AAEhD,4CAA4C;AACrC,KAAK,UAAU,gBAAgB,CAAC,IAAc,EAAE,IAAuB;IAC5E,MAAM,CAAC,GAAG,IAAA,gBAAS,EAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACpC,IAAI,IAAA,UAAG,EAAC,CAAC,EAAE,MAAM,CAAC,IAAI,IAAA,UAAG,EAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;QAClC,IAAI,CAAC,GAAG,CAAC,mBAAW,CAAC,CAAC;QACtB,OAAO,iBAAI,CAAC,EAAE,CAAC;IACjB,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACpC,MAAM,IAAI,GAAG,IAAA,UAAG,EAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5B,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,EAAE;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,CAAC,GAAG,CAAC,oFAAoF,CAAC,CAAC;QAC/F,OAAO,iBAAI,CAAC,KAAK,CAAC;IACpB,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,IAAA,UAAG,EAAC,CAAC,EAAE,MAAM,CAAC;SACxB,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SAC5B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,wBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IACpE,IAAI,OAAO,CAAC,MAAM;QAChB,OAAO,KAAK,CACV,wBAAwB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,wBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACrF,CAAC;IAEJ,MAAM,KAAK,GAAG,IAAA,WAAI,EAAC,CAAC,EAAE,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAA,WAAI,EAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACpF,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;QAChE,OAAO,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,IAAA,WAAI,EAAC,CAAC,EAAE,SAAS,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAA,WAAI,EAAC,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IAC1F,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;QACtE,OAAO,KAAK,CAAC,oDAAoD,CAAC,CAAC;IAErE,IAAI,GAAG,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,0BAA0B,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACjG,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;YAC5B,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC,IAAI,GAAG,0BAA0B,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAA,sBAAW,EAAC,GAAG,CAAC,CAAC;IACjC,MAAM,YAAY,GAAG,CACnB,IAAA,WAAI,EAAC,CAAC,EAAE,eAAe,CAAC;QACxB,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC7C,gCAAqB,CACtB,CAAC,WAAW,EAAE,CAAC;IAChB,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC;IAC1C,MAAM,WAAW,GAAiB,EAAE,CAAC;IACrC,IAAI,KAA2B,CAAC;IAEhC,MAAM,IAAI,GAAG,IAAA,WAAI,EAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC7B,6EAA6E;IAC7E,8BAA8B;IAC9B,MAAM,OAAO,GAAG,IAAA,WAAI,EAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,IAAA,gBAAO,EAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,IAAA,mBAAU,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3F,MAAM,OAAO,GAAG,GAAG,EAAE;QACnB,MAAM,OAAO,GAAG,IAAA,yBAAc,EAAC,GAAG,CAAC,CAAC;QACpC,OAAO,CACL,IAAI,CAAC,IAAI;YACT,IAAI,WAAI,CAAC;gBACP,OAAO;gBACP,KAAK,EAAE,IAAA,0BAAY,EAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC;gBACjD,SAAS,EAAE,IAAI,CAAC,SAAS,IAAI,iBAAiB;gBAC9C,KAAK,EAAE,SAAS;aACjB,CAAC,CACH,CAAC;IACJ,CAAC,CAAC;IACF,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,GAAG,GAAG,MAAM,CAAC;QACnB,IAAI,CAAC,GAAG;YACN,OAAO,KAAK,CAAC,wEAAwE,CAAC,CAAC;QACzF,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAA,kBAAW,EAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;YACpD,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ;gBAAE,IAAI,CAAC,GAAG,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;YACtE,IAAI,CAAC,GAAG;gBAAE,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC;YAC7B,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,+CAA+C,CAAC,CAAC;gBACtE,OAAO,iBAAI,CAAC,SAAS,CAAC;YACxB,CAAC;YACD,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;YAC1F,MAAM,GAAG,GAAG,IAAA,eAAO,EAAC,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,CAAC;YACvD,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAA,qBAAc,EAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,GAAG,GAAG,CAA0C,CAAC;YACvD,IAAI,CAAC,GAAG,CAAC,uBAAuB,IAAI,KAAK,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACrE,OAAO,IAAA,6BAAgB,EAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAElE,MAAM,IAAI,GAAG,IAAA,UAAG,EAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5B,IAAI,IAAI,CAAC,MAAM;QAAE,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC;IAE5D,MAAM,MAAM,GAAG,MAAM,IAAA,kBAAS,EAAC;QAC7B,GAAG;QACH,OAAO,EAAE,IAAA,WAAI,EAAC,CAAC,EAAE,UAAU,CAAC,IAAI,OAAO;QACvC,SAAS;QACT,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QACpC,YAAY;QACZ,wEAAwE;QACxE,gEAAgE;QAChE,WAAW,EACT,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE;YAC1D,CAAC,CAAC,IAAA,yBAAc,EAAC,GAAG,CAAC;YACrB,CAAC,CAAC,WAAW,YAAY,MAAM;QACnC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS;QACzD,KAAK;QACL,KAAK;QACL,SAAS,EAAE,OAAO;KACnB,CAAC,CAAC;IACH,iFAAiF;IACjF,IAAI,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,2CAAuB,CAAC,CAAC,EAAE,CAAC;QACvE,MAAM,CAAC,GAAG,MAAM,IAAA,wCAAoB,EAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;YAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;aACtC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;YAAE,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;;YAC3C,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,IAAI;QAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;;QAC/C,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,iBAAI,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAI,CAAC,EAAE,CAAC;AACnD,CAAC;AAED,SAAgB,WAAW,CAAC,MAAoB,EAAE,GAAwB;IACxE,GAAG,CAAC,YAAY,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IAC9B,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QAChF,GAAG,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACtC,2EAA2E;QAC3E,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QAChE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC,OAAO;YAAE,GAAG,CAAC,2BAA2B,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,QAAQ,UAAU,CAAC,CAAC;AACtF,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Check } from '../conformance';
|
|
2
|
+
import { type Get, type Probe } from './probe';
|
|
3
|
+
export interface CrawlResult {
|
|
4
|
+
check: Check;
|
|
5
|
+
/** Every listed URL that was fetched, with its answer. */
|
|
6
|
+
pages: Probe[];
|
|
7
|
+
}
|
|
8
|
+
/** `Sitemap:` lines of robots.txt, else the conventional address. */
|
|
9
|
+
export declare function sitemapsFromRobots(robots: string, origin: string): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Read sitemaps (following a sitemap index), returning the URL lists per
|
|
12
|
+
* document, so the sample can draw from every space rather than exhaust the
|
|
13
|
+
* first one: a site with a 900-page reference and a 4-page legal space would
|
|
14
|
+
* otherwise never have its legal pages looked at.
|
|
15
|
+
*/
|
|
16
|
+
export declare function readSitemaps(get: Get, start: string[]): Promise<{
|
|
17
|
+
lists: {
|
|
18
|
+
sitemap: string;
|
|
19
|
+
urls: string[];
|
|
20
|
+
}[];
|
|
21
|
+
problems: string[];
|
|
22
|
+
}>;
|
|
23
|
+
/** Up to `limit` URLs, round-robin across the sitemaps so every space is represented. */
|
|
24
|
+
export declare function sample(lists: {
|
|
25
|
+
urls: string[];
|
|
26
|
+
}[], limit: number): string[];
|
|
27
|
+
export declare function sitemapCrawl(get: Get, origin: string, robots: string | undefined, limit: number): Promise<CrawlResult>;
|
|
28
|
+
/**
|
|
29
|
+
* The space a URL belongs to, for grouping: its first path segment, which is
|
|
30
|
+
* where a path-mounted space lives (`/docs`, `/legal`). Pages at the root of a
|
|
31
|
+
* site domain group together under `/`. Coarse, and that is fine — a
|
|
32
|
+
* difference between two spaces is a choice; a difference inside one is what
|
|
33
|
+
* this is looking for.
|
|
34
|
+
*/
|
|
35
|
+
export declare function groupOf(url: string): string;
|
|
36
|
+
/** `public` (a shared cache may keep it) or `private` (it may not), by the directives that decide it. */
|
|
37
|
+
export declare function cacheClass(cc: string | null): 'public' | 'private' | 'none';
|
|
38
|
+
/**
|
|
39
|
+
* `cache-consistency`: the pages of one space are cached the same way.
|
|
40
|
+
*
|
|
41
|
+
* Every legal document answered `private, no-cache, no-store` to a reader with
|
|
42
|
+
* no cookie at all, while the legal index beside it was `public, s-maxage=10`
|
|
43
|
+
* (FMU-B05). Neither header is wrong on its own; the difference is the defect —
|
|
44
|
+
* either something is gated and should say so, or a code path forgot the
|
|
45
|
+
* public header and every reader pays a full origin render. A page that sends
|
|
46
|
+
* a reason header is taken at its word.
|
|
47
|
+
*/
|
|
48
|
+
export declare function cacheConsistency(pages: Probe[]): Check;
|
|
49
|
+
//# sourceMappingURL=crawl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../../src/doctor/crawl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAQL,KAAK,GAAG,EACR,KAAK,KAAK,EACX,MAAM,SAAS,CAAC;AAmBjB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,KAAK,CAAC;IACb,0DAA0D;IAC1D,KAAK,EAAE,KAAK,EAAE,CAAC;CAChB;AAED,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAG3E;AAQD;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,GAAG,EACR,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC;IAAE,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAkB/E;AAED,yFAAyF;AACzF,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAkB3E;AAED,wBAAsB,YAAY,CAChC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,WAAW,CAAC,CAuDtB;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY3C;AAED,yGAAyG;AACzG,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAK3E;AAKD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,CA0CtD"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sitemapsFromRobots = sitemapsFromRobots;
|
|
4
|
+
exports.readSitemaps = readSitemaps;
|
|
5
|
+
exports.sample = sample;
|
|
6
|
+
exports.sitemapCrawl = sitemapCrawl;
|
|
7
|
+
exports.groupOf = groupOf;
|
|
8
|
+
exports.cacheClass = cacheClass;
|
|
9
|
+
exports.cacheConsistency = cacheConsistency;
|
|
10
|
+
const probe_1 = require("./probe");
|
|
11
|
+
/**
|
|
12
|
+
* `sitemap-crawl`: every address the site hands a crawler, fetched.
|
|
13
|
+
*
|
|
14
|
+
* The sitemap is the list a search engine trusts most, so an entry that
|
|
15
|
+
* redirects, 404s, names a different canonical or sits on another host is a
|
|
16
|
+
* page the site is asking to have indexed and then refusing to be. A space root
|
|
17
|
+
* that 307s to its first page was listed as itself (FMU-B06); a platform copy
|
|
18
|
+
* that 308s elsewhere named itself canonical (FMU-B01). Each is invisible from
|
|
19
|
+
* the page a person happens to open, and obvious once every listed URL is asked.
|
|
20
|
+
*
|
|
21
|
+
* The pages fetched here are also the SAMPLE the other site-wide checks judge
|
|
22
|
+
* (cache headers, descriptions), so the crawl is done once.
|
|
23
|
+
*/
|
|
24
|
+
/** How many sitemap documents (index included) one run will read. */
|
|
25
|
+
const SITEMAP_DOC_LIMIT = 25;
|
|
26
|
+
/** `Sitemap:` lines of robots.txt, else the conventional address. */
|
|
27
|
+
function sitemapsFromRobots(robots, origin) {
|
|
28
|
+
const named = [...robots.matchAll(/^\s*sitemap:\s*(\S+)/gim)].map((m) => m[1]);
|
|
29
|
+
return named.length ? named : [`${origin}/sitemap.xml`];
|
|
30
|
+
}
|
|
31
|
+
function locs(xml) {
|
|
32
|
+
return [...xml.matchAll(/<loc>\s*([^<\s]+)\s*<\/loc>/gi)].map((m) => m[1].replace(/&/g, '&'));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read sitemaps (following a sitemap index), returning the URL lists per
|
|
36
|
+
* document, so the sample can draw from every space rather than exhaust the
|
|
37
|
+
* first one: a site with a 900-page reference and a 4-page legal space would
|
|
38
|
+
* otherwise never have its legal pages looked at.
|
|
39
|
+
*/
|
|
40
|
+
async function readSitemaps(get, start) {
|
|
41
|
+
const lists = [];
|
|
42
|
+
const problems = [];
|
|
43
|
+
const queue = [...new Set(start)];
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
while (queue.length && seen.size < SITEMAP_DOC_LIMIT) {
|
|
46
|
+
const url = queue.shift();
|
|
47
|
+
if (seen.has(url))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(url);
|
|
50
|
+
const p = await get(url, { accept: 'application/xml,text/xml' });
|
|
51
|
+
if (!p.ok || p.status !== 200) {
|
|
52
|
+
problems.push(`${url} → ${(0, probe_1.describeProbe)(p)}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (/<sitemapindex\b/i.test(p.text))
|
|
56
|
+
queue.push(...locs(p.text));
|
|
57
|
+
else
|
|
58
|
+
lists.push({ sitemap: url, urls: locs(p.text) });
|
|
59
|
+
}
|
|
60
|
+
return { lists, problems };
|
|
61
|
+
}
|
|
62
|
+
/** Up to `limit` URLs, round-robin across the sitemaps so every space is represented. */
|
|
63
|
+
function sample(lists, limit) {
|
|
64
|
+
const out = [];
|
|
65
|
+
const seen = new Set();
|
|
66
|
+
for (let i = 0; out.length < limit; i++) {
|
|
67
|
+
let any = false;
|
|
68
|
+
for (const l of lists) {
|
|
69
|
+
const u = l.urls[i];
|
|
70
|
+
if (u === undefined)
|
|
71
|
+
continue;
|
|
72
|
+
any = true;
|
|
73
|
+
if (!seen.has(u)) {
|
|
74
|
+
seen.add(u);
|
|
75
|
+
out.push(u);
|
|
76
|
+
if (out.length >= limit)
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!any)
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
async function sitemapCrawl(get, origin, robots, limit) {
|
|
86
|
+
const title = 'Every URL in the sitemap answers 200, as itself';
|
|
87
|
+
const matters = 'A sitemap entry that redirects, 404s or names another canonical is a page the site asks to have indexed and then disowns; search engines lower their trust in the whole list.';
|
|
88
|
+
const { lists, problems } = await readSitemaps(get, sitemapsFromRobots(robots ?? '', origin));
|
|
89
|
+
if (lists.length === 0) {
|
|
90
|
+
return {
|
|
91
|
+
check: (0, probe_1.check)('sitemap-crawl', title, 'fail', problems.length ? `No sitemap could be read: ${(0, probe_1.some)(problems)}` : 'No sitemap found.', matters),
|
|
92
|
+
pages: [],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const total = lists.reduce((n, l) => n + l.urls.length, 0);
|
|
96
|
+
const urls = sample(lists, limit);
|
|
97
|
+
const siteHost = (0, probe_1.hostOf)(origin);
|
|
98
|
+
const pages = await (0, probe_1.pool)(urls, 6, (u) => get(u, { accept: 'text/html' }));
|
|
99
|
+
const bad = [...problems];
|
|
100
|
+
for (const p of pages) {
|
|
101
|
+
if ((0, probe_1.hostOf)(p.url) !== siteHost) {
|
|
102
|
+
bad.push(`${p.url} is on another host than the sitemap (${siteHost})`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!p.ok) {
|
|
106
|
+
bad.push(`${p.url} → ${p.error}`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (p.status >= 300 && p.status < 400) {
|
|
110
|
+
bad.push(`${p.url} → ${p.status} ${p.headers.get('location') ?? ''} (list the final URL)`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (p.status !== 200) {
|
|
114
|
+
bad.push(`${p.url} → ${p.status}`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const canonical = (0, probe_1.canonicalOf)(p.text, p.url);
|
|
118
|
+
if (canonical && !(0, probe_1.sameUrl)(canonical, p.url))
|
|
119
|
+
bad.push(`${p.url} names canonical ${canonical}`);
|
|
120
|
+
}
|
|
121
|
+
const scope = `${pages.length} of ${total} listed URL${total === 1 ? '' : 's'} across ${lists.length} sitemap${lists.length === 1 ? '' : 's'}`;
|
|
122
|
+
return {
|
|
123
|
+
check: (0, probe_1.check)('sitemap-crawl', title, bad.length ? 'fail' : 'pass', bad.length
|
|
124
|
+
? `${bad.length} problem(s) in ${scope}: ${(0, probe_1.some)(bad)}`
|
|
125
|
+
: `${scope}: all 200, all self-canonical.`, bad.length ? matters : ''),
|
|
126
|
+
pages,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The space a URL belongs to, for grouping: its first path segment, which is
|
|
131
|
+
* where a path-mounted space lives (`/docs`, `/legal`). Pages at the root of a
|
|
132
|
+
* site domain group together under `/`. Coarse, and that is fine — a
|
|
133
|
+
* difference between two spaces is a choice; a difference inside one is what
|
|
134
|
+
* this is looking for.
|
|
135
|
+
*/
|
|
136
|
+
function groupOf(url) {
|
|
137
|
+
try {
|
|
138
|
+
const u = new URL(url);
|
|
139
|
+
const segs = u.pathname.split('/').filter(Boolean);
|
|
140
|
+
// A short reader prefix followed by owner and space (`/p/<org>/<space>/…`)
|
|
141
|
+
// is how a multi-tenant host addresses a space; grouping by `/p` alone
|
|
142
|
+
// would compare every tenant's pages with every other's.
|
|
143
|
+
if (segs.length >= 4 && segs[0].length <= 3)
|
|
144
|
+
return `${u.host}/${segs.slice(0, 3).join('/')}`;
|
|
145
|
+
return `${u.host}/${segs[0] ?? ''}`;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return url;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** `public` (a shared cache may keep it) or `private` (it may not), by the directives that decide it. */
|
|
152
|
+
function cacheClass(cc) {
|
|
153
|
+
if (!cc)
|
|
154
|
+
return 'none';
|
|
155
|
+
if (/\b(?:private|no-store)\b/i.test(cc))
|
|
156
|
+
return 'private';
|
|
157
|
+
if (/\b(?:public|s-maxage)\b/i.test(cc))
|
|
158
|
+
return 'public';
|
|
159
|
+
return 'private';
|
|
160
|
+
}
|
|
161
|
+
/** Headers a site may send to say a page is uncached on purpose (and why). */
|
|
162
|
+
const REASON_HEADERS = ['x-forumulate-cache-reason', 'x-cache-reason', 'x-cache-control-reason'];
|
|
163
|
+
/**
|
|
164
|
+
* `cache-consistency`: the pages of one space are cached the same way.
|
|
165
|
+
*
|
|
166
|
+
* Every legal document answered `private, no-cache, no-store` to a reader with
|
|
167
|
+
* no cookie at all, while the legal index beside it was `public, s-maxage=10`
|
|
168
|
+
* (FMU-B05). Neither header is wrong on its own; the difference is the defect —
|
|
169
|
+
* either something is gated and should say so, or a code path forgot the
|
|
170
|
+
* public header and every reader pays a full origin render. A page that sends
|
|
171
|
+
* a reason header is taken at its word.
|
|
172
|
+
*/
|
|
173
|
+
function cacheConsistency(pages) {
|
|
174
|
+
const title = 'Pages within a space are cached alike';
|
|
175
|
+
const groups = new Map();
|
|
176
|
+
for (const p of pages) {
|
|
177
|
+
if (!p.ok || p.status !== 200)
|
|
178
|
+
continue;
|
|
179
|
+
if (REASON_HEADERS.some((h) => p.headers.get(h)))
|
|
180
|
+
continue;
|
|
181
|
+
const cc = p.headers.get('cache-control');
|
|
182
|
+
const g = groupOf(p.url);
|
|
183
|
+
groups.set(g, [
|
|
184
|
+
...(groups.get(g) ?? []),
|
|
185
|
+
{ url: p.url, cls: cacheClass(cc), cc: cc ?? '(none)' },
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
if (groups.size === 0)
|
|
189
|
+
return (0, probe_1.check)('cache-consistency', title, 'skip', 'No sampled pages answered 200.', '');
|
|
190
|
+
const odd = [];
|
|
191
|
+
for (const [g, rows] of groups) {
|
|
192
|
+
const classes = [...new Set(rows.map((r) => r.cls))];
|
|
193
|
+
if (classes.length < 2)
|
|
194
|
+
continue;
|
|
195
|
+
// Every side of the split is named. Which side is the bug is the site's
|
|
196
|
+
// call (usually the private one — a published page has no reason to be),
|
|
197
|
+
// and a "majority" of three legal documents is evidence of nothing.
|
|
198
|
+
const sides = classes.map((cls) => {
|
|
199
|
+
const of = rows.filter((r) => r.cls === cls);
|
|
200
|
+
return `${of.length} ${cls} ("${of[0].cc}": ${(0, probe_1.some)(of.map((r) => r.url), 3)})`;
|
|
201
|
+
});
|
|
202
|
+
odd.push(`${g}: ${sides.join(' vs ')}`);
|
|
203
|
+
}
|
|
204
|
+
return (0, probe_1.check)('cache-consistency', title, odd.length ? 'fail' : 'pass', odd.length
|
|
205
|
+
? (0, probe_1.some)(odd)
|
|
206
|
+
: `${[...groups.values()].reduce((n, r) => n + r.length, 0)} pages in ${groups.size} space(s), one rule each.`, odd.length
|
|
207
|
+
? 'A published page answering no-store to an anonymous reader is rendered at the origin for every visit. If it is gated on purpose, say so with an x-forumulate-cache-reason header.'
|
|
208
|
+
: '');
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=crawl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crawl.js","sourceRoot":"","sources":["../../src/doctor/crawl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Check } from '../conformance';
|
|
2
|
+
import { type AddressSet } from './address-checks';
|
|
3
|
+
import { type Get } from './probe';
|
|
4
|
+
/**
|
|
5
|
+
* The site-wide checks added for FMU-D14, run after the single-page ones.
|
|
6
|
+
*
|
|
7
|
+
* Every one of them is a defect an integrator found on a live site after
|
|
8
|
+
* readers did, and every one is judged from the outside, by HTTP alone, so the
|
|
9
|
+
* report is the same whether it is pointed at our platform, a staging mount or
|
|
10
|
+
* any other docs host. The only thing a check knows about "the platform" is
|
|
11
|
+
* its hostname, which is an input (`--platform-host`, else the host of
|
|
12
|
+
* FORUMULATE_URL, else forumulate.com) — never a constant in the logic.
|
|
13
|
+
*/
|
|
14
|
+
/** Every check id this file can produce, in report order. `--only` validates against it. */
|
|
15
|
+
export declare const EXTENDED_CHECK_IDS: readonly ["canonical-both-addresses", "sitemap-crawl", "cache-consistency", "root-redirect", "meta-description", "llms-txt", "favicon", "consent-tag", "white-label-leaks", "robots-params"];
|
|
16
|
+
export declare const DEFAULT_PLATFORM_HOST = "forumulate.com";
|
|
17
|
+
/** URLs one run fetches from the sitemaps; enough to see a pattern, few enough for CI. */
|
|
18
|
+
export declare const DEFAULT_SAMPLE_LIMIT = 60;
|
|
19
|
+
export interface ExtendedInput {
|
|
20
|
+
/** The page judged: where the asked-for address landed after redirects. */
|
|
21
|
+
url: string;
|
|
22
|
+
/** The address as given, before any redirect. */
|
|
23
|
+
requestedUrl?: string;
|
|
24
|
+
/** The page's HTML as an anonymous reader got it ('' when it did not answer). */
|
|
25
|
+
html: string;
|
|
26
|
+
/** robots.txt, when it answered 200. */
|
|
27
|
+
robots?: string;
|
|
28
|
+
get: Get;
|
|
29
|
+
platformHost: string;
|
|
30
|
+
/** The platform's API base (`https://forumulate.com/api`), for the primary-address lookup. */
|
|
31
|
+
platformApi?: string;
|
|
32
|
+
/** Other addresses of this page/space, from `--also` or `--site`. */
|
|
33
|
+
addressSets?: AddressSet[];
|
|
34
|
+
/** Space roots to check, from `--site`; derived from the URL otherwise. */
|
|
35
|
+
roots?: string[];
|
|
36
|
+
limit?: number;
|
|
37
|
+
/** Run only these ids (undefined: all). */
|
|
38
|
+
only?: ReadonlySet<string>;
|
|
39
|
+
}
|
|
40
|
+
/** The roots a URL's space is served at: the mount (first segment), or the platform copy's space root. */
|
|
41
|
+
export declare function deriveRoots(url: string, platformHost: string): string[];
|
|
42
|
+
/**
|
|
43
|
+
* The customer's address of a platform-hosted page, asked of the platform.
|
|
44
|
+
*
|
|
45
|
+
* Only for a URL on the platform host in the platform-copy shape: the public
|
|
46
|
+
* primary-domain lookup answers where the space's readers are sent, which is
|
|
47
|
+
* what both copies must name as canonical. Any failure is "unknown", and the
|
|
48
|
+
* check then compares the copies it has with each other.
|
|
49
|
+
*/
|
|
50
|
+
export declare function discoverPrimary(get: Get, url: string, platformHost: string, platformApi: string | undefined): Promise<AddressSet | undefined>;
|
|
51
|
+
export declare function extendedChecks(input: ExtendedInput): Promise<Check[]>;
|
|
52
|
+
//# sourceMappingURL=extended.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extended.d.ts","sourceRoot":"","sources":["../../src/doctor/extended.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAiB,KAAK,GAAG,EAAc,MAAM,SAAS,CAAC;AAG9D;;;;;;;;;GASG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,kBAAkB,8LAWrB,CAAC;AAEX,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;AACtD,0FAA0F;AAC1F,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,MAAM,WAAW,aAAa;IAC5B,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qEAAqE;IACrE,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAC3B,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAC5B;AASD,0GAA0G;AAC1G,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,EAAE,CASvE;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAuBjC;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAiF3E"}
|