@forumulate/cli 0.8.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.
Files changed (286) hide show
  1. package/README.md +11 -3
  2. package/dist/api/args.d.ts.map +1 -1
  3. package/dist/api/args.js +1 -0
  4. package/dist/api/args.js.map +1 -1
  5. package/dist/api/command.d.ts +1 -1
  6. package/dist/api/command.d.ts.map +1 -1
  7. package/dist/api/command.js +86 -11
  8. package/dist/api/command.js.map +1 -1
  9. package/dist/api/http.d.ts +8 -0
  10. package/dist/api/http.d.ts.map +1 -1
  11. package/dist/api/http.js +3 -2
  12. package/dist/api/http.js.map +1 -1
  13. package/dist/api/live-spec.d.ts +48 -0
  14. package/dist/api/live-spec.d.ts.map +1 -0
  15. package/dist/api/live-spec.js +72 -0
  16. package/dist/api/live-spec.js.map +1 -0
  17. package/dist/api/operations.json +1 -1
  18. package/dist/api/request.d.ts.map +1 -1
  19. package/dist/api/request.js +25 -1
  20. package/dist/api/request.js.map +1 -1
  21. package/dist/api/send.d.ts +10 -1
  22. package/dist/api/send.d.ts.map +1 -1
  23. package/dist/api/send.js +14 -3
  24. package/dist/api/send.js.map +1 -1
  25. package/dist/api/slim.d.ts +14 -0
  26. package/dist/api/slim.d.ts.map +1 -0
  27. package/dist/api/slim.js +174 -0
  28. package/dist/api/slim.js.map +1 -0
  29. package/dist/cache-command.d.ts +12 -0
  30. package/dist/cache-command.d.ts.map +1 -0
  31. package/dist/cache-command.js +93 -0
  32. package/dist/cache-command.js.map +1 -0
  33. package/dist/check/command.d.ts +48 -0
  34. package/dist/check/command.d.ts.map +1 -0
  35. package/dist/check/command.js +256 -0
  36. package/dist/check/command.js.map +1 -0
  37. package/dist/check/external.d.ts +66 -0
  38. package/dist/check/external.d.ts.map +1 -0
  39. package/dist/check/external.js +177 -0
  40. package/dist/check/external.js.map +1 -0
  41. package/dist/check/markdown-links.d.ts +38 -0
  42. package/dist/check/markdown-links.d.ts.map +1 -0
  43. package/dist/check/markdown-links.js +166 -0
  44. package/dist/check/markdown-links.js.map +1 -0
  45. package/dist/cli.js +42 -7
  46. package/dist/cli.js.map +1 -1
  47. package/dist/commands/completion.d.ts.map +1 -1
  48. package/dist/commands/completion.js +17 -0
  49. package/dist/commands/completion.js.map +1 -1
  50. package/dist/commands/context.d.ts.map +1 -1
  51. package/dist/commands/context.js +6 -2
  52. package/dist/commands/context.js.map +1 -1
  53. package/dist/commands/groups-api.d.ts.map +1 -1
  54. package/dist/commands/groups-api.js +20 -2
  55. package/dist/commands/groups-api.js.map +1 -1
  56. package/dist/commands/groups-docs.d.ts.map +1 -1
  57. package/dist/commands/groups-docs.js +8 -2
  58. package/dist/commands/groups-docs.js.map +1 -1
  59. package/dist/commands/groups-jobs.d.ts +10 -0
  60. package/dist/commands/groups-jobs.d.ts.map +1 -0
  61. package/dist/commands/groups-jobs.js +89 -0
  62. package/dist/commands/groups-jobs.js.map +1 -0
  63. package/dist/commands/groups-org.d.ts.map +1 -1
  64. package/dist/commands/groups-org.js +59 -2
  65. package/dist/commands/groups-org.js.map +1 -1
  66. package/dist/commands/groups-site.d.ts.map +1 -1
  67. package/dist/commands/groups-site.js +47 -10
  68. package/dist/commands/groups-site.js.map +1 -1
  69. package/dist/commands/groups-spaces.d.ts.map +1 -1
  70. package/dist/commands/groups-spaces.js +11 -5
  71. package/dist/commands/groups-spaces.js.map +1 -1
  72. package/dist/commands/groups-types.d.ts.map +1 -1
  73. package/dist/commands/groups-types.js +74 -0
  74. package/dist/commands/groups-types.js.map +1 -1
  75. package/dist/commands/groups.d.ts.map +1 -1
  76. package/dist/commands/groups.js +2 -0
  77. package/dist/commands/groups.js.map +1 -1
  78. package/dist/commands/index.d.ts +9 -0
  79. package/dist/commands/index.d.ts.map +1 -1
  80. package/dist/commands/index.js +18 -3
  81. package/dist/commands/index.js.map +1 -1
  82. package/dist/commands/job-wait.d.ts +64 -0
  83. package/dist/commands/job-wait.d.ts.map +1 -0
  84. package/dist/commands/job-wait.js +91 -0
  85. package/dist/commands/job-wait.js.map +1 -0
  86. package/dist/commands/resource.d.ts +19 -0
  87. package/dist/commands/resource.d.ts.map +1 -1
  88. package/dist/commands/resource.js +65 -3
  89. package/dist/commands/resource.js.map +1 -1
  90. package/dist/content/library.d.ts +8 -0
  91. package/dist/content/library.d.ts.map +1 -1
  92. package/dist/content/library.js +10 -2
  93. package/dist/content/library.js.map +1 -1
  94. package/dist/credentials.d.ts +43 -4
  95. package/dist/credentials.d.ts.map +1 -1
  96. package/dist/credentials.js +67 -4
  97. package/dist/credentials.js.map +1 -1
  98. package/dist/dev/command.d.ts +41 -0
  99. package/dist/dev/command.d.ts.map +1 -0
  100. package/dist/dev/command.js +158 -0
  101. package/dist/dev/command.js.map +1 -0
  102. package/dist/doctor/address-checks.d.ts +56 -0
  103. package/dist/doctor/address-checks.d.ts.map +1 -0
  104. package/dist/doctor/address-checks.js +183 -0
  105. package/dist/doctor/address-checks.js.map +1 -0
  106. package/dist/doctor/boilerplate.d.ts +33 -0
  107. package/dist/doctor/boilerplate.d.ts.map +1 -0
  108. package/dist/doctor/boilerplate.js +78 -0
  109. package/dist/doctor/boilerplate.js.map +1 -0
  110. package/dist/doctor/command.d.ts +18 -0
  111. package/dist/doctor/command.d.ts.map +1 -0
  112. package/dist/doctor/command.js +234 -0
  113. package/dist/doctor/command.js.map +1 -0
  114. package/dist/doctor/crawl.d.ts +49 -0
  115. package/dist/doctor/crawl.d.ts.map +1 -0
  116. package/dist/doctor/crawl.js +210 -0
  117. package/dist/doctor/crawl.js.map +1 -0
  118. package/dist/doctor/extended.d.ts +52 -0
  119. package/dist/doctor/extended.d.ts.map +1 -0
  120. package/dist/doctor/extended.js +150 -0
  121. package/dist/doctor/extended.js.map +1 -0
  122. package/dist/doctor/page-checks.d.ts +42 -0
  123. package/dist/doctor/page-checks.d.ts.map +1 -0
  124. package/dist/doctor/page-checks.js +200 -0
  125. package/dist/doctor/page-checks.js.map +1 -0
  126. package/dist/doctor/probe.d.ts +63 -0
  127. package/dist/doctor/probe.d.ts.map +1 -0
  128. package/dist/doctor/probe.js +170 -0
  129. package/dist/doctor/probe.js.map +1 -0
  130. package/dist/doctor/reader-auth-atlas.d.ts +18 -0
  131. package/dist/doctor/reader-auth-atlas.d.ts.map +1 -0
  132. package/dist/doctor/reader-auth-atlas.js +50 -0
  133. package/dist/doctor/reader-auth-atlas.js.map +1 -0
  134. package/dist/doctor/robots-params.d.ts +36 -0
  135. package/dist/doctor/robots-params.d.ts.map +1 -0
  136. package/dist/doctor/robots-params.js +97 -0
  137. package/dist/doctor/robots-params.js.map +1 -0
  138. package/dist/doctor/site.d.ts +42 -0
  139. package/dist/doctor/site.d.ts.map +1 -0
  140. package/dist/doctor/site.js +82 -0
  141. package/dist/doctor/site.js.map +1 -0
  142. package/dist/doctor.d.ts +21 -0
  143. package/dist/doctor.d.ts.map +1 -1
  144. package/dist/doctor.js +94 -3
  145. package/dist/doctor.js.map +1 -1
  146. package/dist/exit-codes.d.ts +40 -0
  147. package/dist/exit-codes.d.ts.map +1 -0
  148. package/dist/exit-codes.js +66 -0
  149. package/dist/exit-codes.js.map +1 -0
  150. package/dist/help-commands.d.ts +13 -0
  151. package/dist/help-commands.d.ts.map +1 -0
  152. package/dist/help-commands.js +215 -0
  153. package/dist/help-commands.js.map +1 -0
  154. package/dist/help.d.ts +1 -1
  155. package/dist/help.d.ts.map +1 -1
  156. package/dist/help.js +74 -11
  157. package/dist/help.js.map +1 -1
  158. package/dist/io.d.ts +33 -0
  159. package/dist/io.d.ts.map +1 -0
  160. package/dist/io.js +81 -0
  161. package/dist/io.js.map +1 -0
  162. package/dist/preview-comment.d.ts +20 -0
  163. package/dist/preview-comment.d.ts.map +1 -0
  164. package/dist/preview-comment.js +88 -0
  165. package/dist/preview-comment.js.map +1 -0
  166. package/dist/publish/about.d.ts +23 -0
  167. package/dist/publish/about.d.ts.map +1 -0
  168. package/dist/publish/about.js +66 -0
  169. package/dist/publish/about.js.map +1 -0
  170. package/dist/publish/api.d.ts +30 -12
  171. package/dist/publish/api.d.ts.map +1 -1
  172. package/dist/publish/api.js +62 -10
  173. package/dist/publish/api.js.map +1 -1
  174. package/dist/publish/client.d.ts +19 -0
  175. package/dist/publish/client.d.ts.map +1 -0
  176. package/dist/publish/client.js +3 -0
  177. package/dist/publish/client.js.map +1 -0
  178. package/dist/publish/command.d.ts +51 -2
  179. package/dist/publish/command.d.ts.map +1 -1
  180. package/dist/publish/command.js +157 -17
  181. package/dist/publish/command.js.map +1 -1
  182. package/dist/publish/context.d.ts +12 -1
  183. package/dist/publish/context.d.ts.map +1 -1
  184. package/dist/publish/context.js.map +1 -1
  185. package/dist/publish/diff.d.ts +21 -0
  186. package/dist/publish/diff.d.ts.map +1 -0
  187. package/dist/publish/diff.js +96 -0
  188. package/dist/publish/diff.js.map +1 -0
  189. package/dist/publish/docs-apply.d.ts +8 -1
  190. package/dist/publish/docs-apply.d.ts.map +1 -1
  191. package/dist/publish/docs-apply.js +49 -10
  192. package/dist/publish/docs-apply.js.map +1 -1
  193. package/dist/publish/docs-plan.d.ts +16 -3
  194. package/dist/publish/docs-plan.d.ts.map +1 -1
  195. package/dist/publish/docs-plan.js +57 -6
  196. package/dist/publish/docs-plan.js.map +1 -1
  197. package/dist/publish/extras.d.ts +10 -0
  198. package/dist/publish/extras.d.ts.map +1 -1
  199. package/dist/publish/extras.js +54 -2
  200. package/dist/publish/extras.js.map +1 -1
  201. package/dist/publish/frontmatter.d.ts +1 -1
  202. package/dist/publish/frontmatter.d.ts.map +1 -1
  203. package/dist/publish/frontmatter.js +1 -0
  204. package/dist/publish/frontmatter.js.map +1 -1
  205. package/dist/publish/index.d.ts +12 -0
  206. package/dist/publish/index.d.ts.map +1 -0
  207. package/dist/publish/index.js +17 -0
  208. package/dist/publish/index.js.map +1 -0
  209. package/dist/publish/manifest.d.ts.map +1 -1
  210. package/dist/publish/manifest.js +48 -3
  211. package/dist/publish/manifest.js.map +1 -1
  212. package/dist/publish/names.d.ts +28 -0
  213. package/dist/publish/names.d.ts.map +1 -0
  214. package/dist/publish/names.js +80 -0
  215. package/dist/publish/names.js.map +1 -0
  216. package/dist/publish/posts.d.ts.map +1 -1
  217. package/dist/publish/posts.js +46 -13
  218. package/dist/publish/posts.js.map +1 -1
  219. package/dist/publish/preview.d.ts +56 -0
  220. package/dist/publish/preview.d.ts.map +1 -0
  221. package/dist/publish/preview.js +133 -0
  222. package/dist/publish/preview.js.map +1 -0
  223. package/dist/publish/redirects.d.ts +13 -0
  224. package/dist/publish/redirects.d.ts.map +1 -0
  225. package/dist/publish/redirects.js +154 -0
  226. package/dist/publish/redirects.js.map +1 -0
  227. package/dist/publish/reserved.d.ts +49 -0
  228. package/dist/publish/reserved.d.ts.map +1 -0
  229. package/dist/publish/reserved.js +118 -0
  230. package/dist/publish/reserved.js.map +1 -0
  231. package/dist/publish/rewrite.d.ts +12 -0
  232. package/dist/publish/rewrite.d.ts.map +1 -1
  233. package/dist/publish/rewrite.js +22 -0
  234. package/dist/publish/rewrite.js.map +1 -1
  235. package/dist/publish/scan-docs.d.ts +10 -0
  236. package/dist/publish/scan-docs.d.ts.map +1 -0
  237. package/dist/publish/scan-docs.js +160 -0
  238. package/dist/publish/scan-docs.js.map +1 -0
  239. package/dist/publish/scan.d.ts +21 -17
  240. package/dist/publish/scan.d.ts.map +1 -1
  241. package/dist/publish/scan.js +99 -232
  242. package/dist/publish/scan.js.map +1 -1
  243. package/dist/publish/slots.d.ts +39 -0
  244. package/dist/publish/slots.d.ts.map +1 -0
  245. package/dist/publish/slots.js +64 -0
  246. package/dist/publish/slots.js.map +1 -0
  247. package/dist/publish/studio-edits.d.ts +25 -0
  248. package/dist/publish/studio-edits.d.ts.map +1 -0
  249. package/dist/publish/studio-edits.js +53 -0
  250. package/dist/publish/studio-edits.js.map +1 -0
  251. package/dist/publish/tags.d.ts +9 -0
  252. package/dist/publish/tags.d.ts.map +1 -0
  253. package/dist/publish/tags.js +194 -0
  254. package/dist/publish/tags.js.map +1 -0
  255. package/dist/publish/types.d.ts +84 -1
  256. package/dist/publish/types.d.ts.map +1 -1
  257. package/dist/publish/types.js +0 -10
  258. package/dist/publish/types.js.map +1 -1
  259. package/dist/publish-command.d.ts +55 -0
  260. package/dist/publish-command.d.ts.map +1 -0
  261. package/dist/publish-command.js +197 -0
  262. package/dist/publish-command.js.map +1 -0
  263. package/dist/run.d.ts +3 -10
  264. package/dist/run.d.ts.map +1 -1
  265. package/dist/run.js +171 -112
  266. package/dist/run.js.map +1 -1
  267. package/dist/translations/target.d.ts.map +1 -1
  268. package/dist/translations/target.js +2 -2
  269. package/dist/translations/target.js.map +1 -1
  270. package/dist/validate.d.ts +39 -0
  271. package/dist/validate.d.ts.map +1 -0
  272. package/dist/validate.js +166 -0
  273. package/dist/validate.js.map +1 -0
  274. package/dist/version.d.ts +8 -0
  275. package/dist/version.d.ts.map +1 -0
  276. package/dist/version.js +11 -0
  277. package/dist/version.js.map +1 -0
  278. package/dist/whoami.d.ts +41 -0
  279. package/dist/whoami.d.ts.map +1 -0
  280. package/dist/whoami.js +161 -0
  281. package/dist/whoami.js.map +1 -0
  282. package/package.json +2 -2
  283. package/dist/publish-dir.d.ts +0 -54
  284. package/dist/publish-dir.d.ts.map +0 -1
  285. package/dist/publish-dir.js +0 -119
  286. package/dist/publish-dir.js.map +0 -1
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rootRedirect = rootRedirect;
4
+ exports.metaDescription = metaDescription;
5
+ exports.favicon = favicon;
6
+ exports.llmsProblems = llmsProblems;
7
+ exports.llmsTxt = llmsTxt;
8
+ const boilerplate_1 = require("./boilerplate");
9
+ const probe_1 = require("./probe");
10
+ /** A redirect needs a Location and nothing else; more than this is a page nobody sees. */
11
+ const REDIRECT_BODY_LIMIT = 1024;
12
+ /**
13
+ * `root-redirect`: a space's root either renders or moves permanently, cheaply.
14
+ *
15
+ * `GET /docs` answered `307` with a 66 KB HTML body and a canonical naming
16
+ * `/docs` (FMU-B06). A 307 says "this is temporary, keep asking me", so a
17
+ * crawler keeps the root as the indexed URL and never settles on the page it
18
+ * lands on; the body is bytes every visitor downloads and throws away. A 301
19
+ * or 308 with an empty body, or the front page rendered in place, are both fine.
20
+ */
21
+ async function rootRedirect(get, roots) {
22
+ const title = 'Space roots render, or redirect permanently with no body';
23
+ const matters = 'A temporary (302/307) redirect keeps the root as the indexed address and the destination as a guest; a heavy redirect body is downloaded by every visitor and shown to none.';
24
+ const bad = [];
25
+ const good = [];
26
+ for (const root of [...new Set(roots)]) {
27
+ const p = await get(root, { accept: 'text/html' });
28
+ if (!p.ok) {
29
+ bad.push(`${root} → ${p.error}`);
30
+ continue;
31
+ }
32
+ if (p.status === 200) {
33
+ good.push(`${root} renders`);
34
+ continue;
35
+ }
36
+ if (p.status < 300 || p.status >= 400) {
37
+ bad.push(`${root} → ${p.status}`);
38
+ continue;
39
+ }
40
+ const to = p.headers.get('location') ?? '';
41
+ const bytes = Buffer.byteLength(p.text);
42
+ const problems = [];
43
+ if (p.status !== 301 && p.status !== 308)
44
+ problems.push(`${p.status} is temporary`);
45
+ if (bytes > REDIRECT_BODY_LIMIT)
46
+ problems.push(`${bytes}-byte body`);
47
+ if (to) {
48
+ const target = new URL(to, root).toString();
49
+ const landed = await get(target, { accept: 'text/html' });
50
+ const canonical = landed.ok ? (0, probe_1.canonicalOf)(landed.text, target) : undefined;
51
+ if (canonical && !(0, probe_1.sameUrl)(canonical, target))
52
+ problems.push(`its target ${target} names canonical ${canonical}`);
53
+ }
54
+ if (problems.length)
55
+ bad.push(`${root} → ${p.status} ${to}: ${problems.join(', ')}`);
56
+ else
57
+ good.push(`${root} → ${p.status} ${to}`);
58
+ }
59
+ if (bad.length === 0 && good.length === 0)
60
+ return (0, probe_1.check)('root-redirect', title, 'skip', 'No space root to check.', '');
61
+ return (0, probe_1.check)('root-redirect', title, bad.length ? 'fail' : 'pass', bad.length ? (0, probe_1.some)(bad) : (0, probe_1.some)(good), bad.length ? matters : '');
62
+ }
63
+ /**
64
+ * `meta-description`: every page says what it is, in words somebody wrote.
65
+ *
66
+ * Checked across the crawl sample, not just the page given, because the
67
+ * preset fallbacks live on the pages nobody opens while testing — a space
68
+ * index, a blog home, a generated reference landing (FMU-B08, FMU-B09).
69
+ */
70
+ function metaDescription(pages) {
71
+ const title = 'Pages carry a real meta and og:description';
72
+ const html = pages.filter((p) => p.ok && p.status === 200);
73
+ if (html.length === 0)
74
+ return (0, probe_1.check)('meta-description', title, 'skip', 'No page answered 200 to read.', '');
75
+ const bad = [];
76
+ for (const p of html) {
77
+ const meta = (0, boilerplate_1.descriptionProblem)((0, probe_1.metaOf)(p.text, 'description'));
78
+ const og = (0, boilerplate_1.descriptionProblem)((0, probe_1.metaOf)(p.text, 'og:description'));
79
+ if (meta)
80
+ bad.push(`${p.url}: description ${meta}`);
81
+ else if (og)
82
+ bad.push(`${p.url}: og:description ${og}`);
83
+ }
84
+ return (0, probe_1.check)('meta-description', title, bad.length ? 'fail' : 'pass', bad.length
85
+ ? `${bad.length} of ${html.length}: ${(0, probe_1.some)(bad)}`
86
+ : `${html.length} page(s) described.`, bad.length
87
+ ? 'The description is what a search result and every link preview show. A missing one is chosen by the crawler; a template sentence tells the reader nothing about this page.'
88
+ : '');
89
+ }
90
+ const IMAGE_TYPE = /^image\/|^application\/octet-stream/i;
91
+ /**
92
+ * `favicon`: the icons the page and the host advertise are images.
93
+ *
94
+ * `GET /docs/favicon.ico` on a customer's domain answered a 404 with a 104 KB
95
+ * HTML page (FMU-B17). Browsers, feed readers and link unfurlers ask for these
96
+ * addresses on their own; a 100 KB error page per ask is load, and a missing
97
+ * icon is a blank tab. An asset-shaped path that is not there should be a tiny
98
+ * 404, not the site's whole not-found page.
99
+ */
100
+ async function favicon(get, pageUrl, html, roots) {
101
+ const title = 'The favicon resolves to an image';
102
+ const origin = new URL(pageUrl).origin;
103
+ const declared = (0, probe_1.tags)(html, 'link')
104
+ .filter((t) => /\bicon\b/i.test((0, probe_1.attr)(t, 'rel') ?? ''))
105
+ .map((t) => (0, probe_1.attr)(t, 'href'))
106
+ .filter((h) => !!h)
107
+ .map((h) => new URL(h, pageUrl).toString());
108
+ const required = [...new Set([`${origin}/favicon.ico`, ...declared])];
109
+ // Under a mount the host's own favicon is the customer's, but the mount's
110
+ // asset paths are ours: they must at least not answer with a page.
111
+ const mounted = [...new Set(roots.map((r) => `${r.replace(/\/+$/, '')}/favicon.ico`))].filter((u) => !required.includes(u));
112
+ const bad = [];
113
+ for (const url of required) {
114
+ const p = await get(url, { accept: 'image/*' });
115
+ const type = p.ok ? (p.headers.get('content-type') ?? '') : '';
116
+ if (!p.ok || p.status !== 200 || !IMAGE_TYPE.test(type))
117
+ bad.push(`${url} → ${(0, probe_1.describeProbe)(p)}${type ? ` ${type}` : ''}${p.ok ? ` (${Buffer.byteLength(p.text)} bytes)` : ''}`);
118
+ }
119
+ for (const url of mounted) {
120
+ const p = await get(url, { accept: 'image/*' });
121
+ if (!p.ok)
122
+ continue;
123
+ const type = p.headers.get('content-type') ?? '';
124
+ const bytes = Buffer.byteLength(p.text);
125
+ if (p.status === 200 && IMAGE_TYPE.test(type))
126
+ continue;
127
+ if (/html/i.test(type) && bytes > 2048)
128
+ bad.push(`${url} → ${p.status} with a ${bytes}-byte HTML page`);
129
+ }
130
+ return (0, probe_1.check)('favicon', title, bad.length ? 'fail' : 'pass', bad.length ? (0, probe_1.some)(bad) : `${required.length + mounted.length} icon address(es) answered.`, bad.length
131
+ ? 'Browsers and link previews fetch the icon on their own; an HTML error page in its place is a blank tab and wasted bytes on every request.'
132
+ : '');
133
+ }
134
+ /** `Title (some-slug)`: the generator disambiguated two titles with a slug a reader never sees. */
135
+ const SLUG_LEAK = /\[[^\]]*\(([a-z0-9]+(?:-[a-z0-9]+)+)\)\]/;
136
+ /** One llms.txt judged against the llmstxt.org shape. */
137
+ function llmsProblems(text, url, platformHost) {
138
+ const lines = text.split(/\r?\n/);
139
+ const first = lines.find((l) => l.trim());
140
+ const out = [];
141
+ if (!first || !/^#\s+\S/.test(first))
142
+ out.push('does not open with an H1 title');
143
+ const quote = lines.find((l) => /^>\s*/.test(l));
144
+ const summary = quote?.replace(/^>\s*/, '').trim();
145
+ if (summary && (0, boilerplate_1.isPresetDescription)(summary))
146
+ out.push(`summary is preset text "${summary}"`);
147
+ const links = [...text.matchAll(/^\s*[-*]\s*\[([^\]]*)\]\(([^)\s]+)\)(.*)$/gm)];
148
+ if (links.length === 0)
149
+ out.push('lists no links');
150
+ // Links elsewhere are fine (a GitHub repo, a status page). Links back to
151
+ // the PLATFORM from a file on the customer's own domain are the leak: the
152
+ // agent is sent to a host the customer did not publish on (FMU-B15/B16).
153
+ const host = (0, probe_1.hostOf)(url);
154
+ const platform = platformHost?.toLowerCase();
155
+ const foreign = platform && host !== platform
156
+ ? links
157
+ .map((m) => m[2])
158
+ .filter((href) => /^https?:/i.test(href) && (0, probe_1.hostOf)(href)?.replace(/^www\./, '') === platform)
159
+ : [];
160
+ if (foreign.length)
161
+ out.push(`links to the platform host: ${(0, probe_1.some)(foreign, 3)}`);
162
+ const leaks = links.filter((m) => SLUG_LEAK.test(`[${m[1]}]`)).map((m) => m[1]);
163
+ if (leaks.length)
164
+ out.push(`titles carry slugs: ${(0, probe_1.some)(leaks, 3)}`);
165
+ const sections = lines.filter((l) => /^##\s+\S/.test(l)).length;
166
+ const described = links.filter((m) => /^\s*:\s*\S/.test(m[3] ?? '')).length;
167
+ if (links.length > 0 && sections === 0 && described === 0)
168
+ out.push(`${links.length} links in one flat list with no sections and no descriptions`);
169
+ return out;
170
+ }
171
+ /**
172
+ * `llms-txt`: the file an AI agent reads first exists and is shaped for one.
173
+ *
174
+ * The space's llms.txt opened with "Published documentation for API Docs.",
175
+ * listed pages in recent-edit order with no descriptions, and titled a clash
176
+ * "Authentication (ref-authentication)" (FMU-B16). An agent picks pages from
177
+ * this list by their descriptions; without them it fetches at random.
178
+ */
179
+ async function llmsTxt(get, origin, roots, platformHost) {
180
+ const title = 'llms.txt is present and follows llmstxt.org';
181
+ const candidates = [
182
+ ...new Set([`${origin}/llms.txt`, ...roots.map((r) => `${r.replace(/\/+$/, '')}/llms.txt`)]),
183
+ ];
184
+ const found = [];
185
+ const bad = [];
186
+ for (const url of candidates) {
187
+ const p = await get(url, { accept: 'text/plain,text/markdown' });
188
+ if (!p.ok || p.status !== 200)
189
+ continue;
190
+ found.push(url);
191
+ for (const problem of llmsProblems(p.text, url, platformHost))
192
+ bad.push(`${url} ${problem}`);
193
+ }
194
+ if (found.length === 0)
195
+ return (0, probe_1.check)('llms-txt', title, 'fail', `None of ${candidates.join(', ')} answered 200.`, 'Agents look for /llms.txt before anything else; without it they crawl HTML and guess.');
196
+ return (0, probe_1.check)('llms-txt', title, bad.length ? 'fail' : 'pass', bad.length ? (0, probe_1.some)(bad) : `${found.join(', ')}: well formed.`, bad.length
197
+ ? 'An agent chooses which pages to read from this list; links with no descriptions, or back to the platform’s host, send it to the wrong ones.'
198
+ : '');
199
+ }
200
+ //# sourceMappingURL=page-checks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-checks.js","sourceRoot":"","sources":["../../src/doctor/page-checks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import type { Check } from '../conformance';
2
+ /**
3
+ * The HTTP and HTML plumbing every doctor check shares.
4
+ *
5
+ * One `get`, so every request the doctor makes has the same three properties:
6
+ * it never follows a redirect on its own (a redirect is usually the thing being
7
+ * checked, and `fetch`'s default hides both the status and the body), it gives
8
+ * up after a fixed time (one hung address must not hang a CI job), and it never
9
+ * throws (a refused connection is a finding, not a crash).
10
+ */
11
+ export type Probe = {
12
+ ok: true;
13
+ url: string;
14
+ status: number;
15
+ headers: Headers;
16
+ text: string;
17
+ } | {
18
+ ok: false;
19
+ url: string;
20
+ error: string;
21
+ };
22
+ export interface ProbeOptions {
23
+ fetchImpl: typeof fetch;
24
+ /** Per-request budget. A crawl of a hundred pages must not wait on one forever. */
25
+ timeoutMs?: number;
26
+ }
27
+ export declare const DEFAULT_TIMEOUT_MS = 15000;
28
+ export declare function makeGet(opts: ProbeOptions): (url: string, headers?: Record<string, string>) => Promise<Probe>;
29
+ export type Get = ReturnType<typeof makeGet>;
30
+ /**
31
+ * Run `fn` over `items`, at most `limit` at a time, in input order.
32
+ *
33
+ * Bounded on purpose: the doctor runs from a laptop or a CI runner against a
34
+ * customer's production site, and a hundred simultaneous requests is how a
35
+ * health check becomes the incident it was meant to catch.
36
+ */
37
+ export declare function pool<T, R>(items: T[], limit: number, fn: (t: T) => Promise<R>): Promise<R[]>;
38
+ /** Follow redirects by hand (bounded), keeping every hop — the hops are what a check judges. */
39
+ export declare function follow(get: Get, url: string, headers?: Record<string, string>, maxHops?: number): Promise<{
40
+ hops: Probe[];
41
+ final: Probe;
42
+ }>;
43
+ /** One attribute's value from one tag's source, quoted or not. */
44
+ declare function attrOf(tag: string, name: string): string | undefined;
45
+ export declare function tags(html: string, name: string): string[];
46
+ export declare const attr: typeof attrOf;
47
+ /** `<link rel="canonical">`, absolute against `base`, or undefined. */
48
+ export declare function canonicalOf(html: string, base: string): string | undefined;
49
+ /** `<meta name|property=X content=…>`. */
50
+ export declare function metaOf(html: string, key: string): string | undefined;
51
+ /** The document's declared language (`<html lang>`), lower-cased, or undefined. */
52
+ export declare function htmlLang(html: string): string | undefined;
53
+ export declare function decodeEntities(s: string): string;
54
+ export declare function hostOf(href: string): string | undefined;
55
+ /** Compare two URLs the way a search engine would: no fragment, no trailing slash. */
56
+ export declare function sameUrl(a: string | undefined, b: string | undefined): boolean;
57
+ export declare function describeProbe(p: Probe): string;
58
+ /** A check under construction: the shape `runDoctor` reports. */
59
+ export declare function check(id: string, title: string, state: Check['state'], detail: string, matters?: string): Check;
60
+ /** At most `n` items of a list in a detail line, and how many were left out. */
61
+ export declare function some(list: string[], n?: number): string;
62
+ export {};
63
+ //# sourceMappingURL=probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/doctor/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.attr = exports.DEFAULT_TIMEOUT_MS = void 0;
4
+ exports.makeGet = makeGet;
5
+ exports.pool = pool;
6
+ exports.follow = follow;
7
+ exports.tags = tags;
8
+ exports.canonicalOf = canonicalOf;
9
+ exports.metaOf = metaOf;
10
+ exports.htmlLang = htmlLang;
11
+ exports.decodeEntities = decodeEntities;
12
+ exports.hostOf = hostOf;
13
+ exports.sameUrl = sameUrl;
14
+ exports.describeProbe = describeProbe;
15
+ exports.check = check;
16
+ exports.some = some;
17
+ exports.DEFAULT_TIMEOUT_MS = 15_000;
18
+ function makeGet(opts) {
19
+ const timeoutMs = opts.timeoutMs ?? exports.DEFAULT_TIMEOUT_MS;
20
+ return async (url, headers = {}) => {
21
+ const ctl = typeof AbortController === 'function' ? new AbortController() : undefined;
22
+ const timer = ctl ? setTimeout(() => ctl.abort(), timeoutMs) : undefined;
23
+ try {
24
+ const res = await opts.fetchImpl(url, {
25
+ headers,
26
+ redirect: 'manual',
27
+ ...(ctl ? { signal: ctl.signal } : {}),
28
+ });
29
+ const text = await res.text();
30
+ return { ok: true, url, status: res.status, headers: res.headers, text };
31
+ }
32
+ catch (e) {
33
+ const aborted = e instanceof Error && e.name === 'AbortError';
34
+ return {
35
+ ok: false,
36
+ url,
37
+ error: aborted
38
+ ? `no answer within ${timeoutMs} ms`
39
+ : e instanceof Error
40
+ ? e.message
41
+ : String(e),
42
+ };
43
+ }
44
+ finally {
45
+ if (timer)
46
+ clearTimeout(timer);
47
+ }
48
+ };
49
+ }
50
+ /**
51
+ * Run `fn` over `items`, at most `limit` at a time, in input order.
52
+ *
53
+ * Bounded on purpose: the doctor runs from a laptop or a CI runner against a
54
+ * customer's production site, and a hundred simultaneous requests is how a
55
+ * health check becomes the incident it was meant to catch.
56
+ */
57
+ async function pool(items, limit, fn) {
58
+ const out = new Array(items.length);
59
+ let next = 0;
60
+ const workers = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {
61
+ while (next < items.length) {
62
+ const i = next++;
63
+ out[i] = await fn(items[i]);
64
+ }
65
+ });
66
+ await Promise.all(workers);
67
+ return out;
68
+ }
69
+ /** Follow redirects by hand (bounded), keeping every hop — the hops are what a check judges. */
70
+ async function follow(get, url, headers = {}, maxHops = 5) {
71
+ const hops = [];
72
+ let at = url;
73
+ for (let i = 0; i <= maxHops; i++) {
74
+ const p = await get(at, headers);
75
+ hops.push(p);
76
+ if (!p.ok || p.status < 300 || p.status >= 400)
77
+ return { hops, final: p };
78
+ const loc = p.headers.get('location');
79
+ if (!loc)
80
+ return { hops, final: p };
81
+ at = new URL(loc, at).toString();
82
+ }
83
+ return { hops, final: hops[hops.length - 1] };
84
+ }
85
+ /** One attribute's value from one tag's source, quoted or not. */
86
+ function attrOf(tag, name) {
87
+ const m = new RegExp(`\\b${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s>]+))`, 'i').exec(tag);
88
+ return m ? (m[1] ?? m[2] ?? m[3]) : undefined;
89
+ }
90
+ function tags(html, name) {
91
+ return html.match(new RegExp(`<${name}\\b[^>]*>`, 'gi')) ?? [];
92
+ }
93
+ exports.attr = attrOf;
94
+ /** `<link rel="canonical">`, absolute against `base`, or undefined. */
95
+ function canonicalOf(html, base) {
96
+ for (const t of tags(html, 'link')) {
97
+ const rel = (attrOf(t, 'rel') ?? '').toLowerCase().split(/\s+/);
98
+ if (!rel.includes('canonical'))
99
+ continue;
100
+ const href = attrOf(t, 'href');
101
+ if (!href)
102
+ continue;
103
+ try {
104
+ return new URL(decodeEntities(href), base).toString();
105
+ }
106
+ catch {
107
+ return href;
108
+ }
109
+ }
110
+ return undefined;
111
+ }
112
+ /** `<meta name|property=X content=…>`. */
113
+ function metaOf(html, key) {
114
+ for (const t of tags(html, 'meta')) {
115
+ const k = (attrOf(t, 'name') ?? attrOf(t, 'property') ?? '').toLowerCase();
116
+ if (k === key.toLowerCase())
117
+ return decodeEntities(attrOf(t, 'content') ?? '');
118
+ }
119
+ return undefined;
120
+ }
121
+ /** The document's declared language (`<html lang>`), lower-cased, or undefined. */
122
+ function htmlLang(html) {
123
+ const t = tags(html, 'html')[0];
124
+ return t ? attrOf(t, 'lang')?.toLowerCase() : undefined;
125
+ }
126
+ function decodeEntities(s) {
127
+ return s
128
+ .replace(/&amp;/g, '&')
129
+ .replace(/&quot;/g, '"')
130
+ .replace(/&#x27;|&#39;/g, "'")
131
+ .replace(/&lt;/g, '<')
132
+ .replace(/&gt;/g, '>');
133
+ }
134
+ function hostOf(href) {
135
+ try {
136
+ return new URL(href).host.toLowerCase();
137
+ }
138
+ catch {
139
+ return undefined;
140
+ }
141
+ }
142
+ /** Compare two URLs the way a search engine would: no fragment, no trailing slash. */
143
+ function sameUrl(a, b) {
144
+ if (!a || !b)
145
+ return false;
146
+ const norm = (u) => {
147
+ try {
148
+ const x = new URL(u);
149
+ x.hash = '';
150
+ return `${x.protocol}//${x.host.toLowerCase()}${x.pathname.replace(/\/+$/, '') || '/'}${x.search}`;
151
+ }
152
+ catch {
153
+ return u;
154
+ }
155
+ };
156
+ return norm(a) === norm(b);
157
+ }
158
+ function describeProbe(p) {
159
+ return p.ok ? `${p.status}` : p.error;
160
+ }
161
+ /** A check under construction: the shape `runDoctor` reports. */
162
+ function check(id, title, state, detail, matters = '') {
163
+ return { id, title, state, detail, matters };
164
+ }
165
+ /** At most `n` items of a list in a detail line, and how many were left out. */
166
+ function some(list, n = 5) {
167
+ const shown = list.slice(0, n).join('; ');
168
+ return list.length > n ? `${shown}; …and ${list.length - n} more` : shown;
169
+ }
170
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/doctor/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { Check } from '../conformance';
2
+ import type { Http } from '../api/http';
3
+ export declare const READER_AUTH_ATLAS_CHECK = "reader-auth-atlas";
4
+ /**
5
+ * `reader-auth-atlas` (FMU-X01): does this organization sign its readers in
6
+ * the recommended way?
7
+ *
8
+ * The rule is the API's, not the CLI's: `GET /orgs/{org}/reader-auth`
9
+ * answers `recommendations`, computed from the org's own configuration (an
10
+ * Atlas issuer on an `external_jwt` provider, a linked Atlas instance), and
11
+ * the manifest plan answers the same list. This prints what it says.
12
+ *
13
+ * Advice, like `robots-params`: a recommendation is a SKIP with the steps,
14
+ * never a FAIL, because signing readers in from your own app is a supported
15
+ * setup and a scheduled doctor must not fail CI over it.
16
+ */
17
+ export declare function readerAuthAtlasCheck(http: Http, org: string): Promise<Check>;
18
+ //# sourceMappingURL=reader-auth-atlas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader-auth-atlas.d.ts","sourceRoot":"","sources":["../../src/doctor/reader-auth-atlas.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,eAAO,MAAM,uBAAuB,sBAAsB,CAAC;AAe3D;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAsClF"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.READER_AUTH_ATLAS_CHECK = void 0;
4
+ exports.readerAuthAtlasCheck = readerAuthAtlasCheck;
5
+ const probe_1 = require("./probe");
6
+ exports.READER_AUTH_ATLAS_CHECK = 'reader-auth-atlas';
7
+ const TITLE = 'Readers sign in with the Atlas reader provider when the organization uses Atlas';
8
+ const MATTERS = 'A hand-rolled external_jwt route is code you maintain that the Atlas provider does for you: Forumulate verifies Atlas’s own reader token and keys members on (iss, sub).';
9
+ /**
10
+ * `reader-auth-atlas` (FMU-X01): does this organization sign its readers in
11
+ * the recommended way?
12
+ *
13
+ * The rule is the API's, not the CLI's: `GET /orgs/{org}/reader-auth`
14
+ * answers `recommendations`, computed from the org's own configuration (an
15
+ * Atlas issuer on an `external_jwt` provider, a linked Atlas instance), and
16
+ * the manifest plan answers the same list. This prints what it says.
17
+ *
18
+ * Advice, like `robots-params`: a recommendation is a SKIP with the steps,
19
+ * never a FAIL, because signing readers in from your own app is a supported
20
+ * setup and a scheduled doctor must not fail CI over it.
21
+ */
22
+ async function readerAuthAtlasCheck(http, org) {
23
+ let body;
24
+ try {
25
+ body = (await http.send({ method: 'GET', path: `/orgs/${encodeURIComponent(org)}/reader-auth` }))
26
+ .data;
27
+ }
28
+ catch (e) {
29
+ const err = e;
30
+ return (0, probe_1.check)(exports.READER_AUTH_ATLAS_CHECK, TITLE, 'skip', `Could not read the organization's reader sign-in (${err.status ?? 'error'}): it needs a token with org.manage_settings.`, '');
31
+ }
32
+ const rec = (body.recommendations ?? []).find((r) => r.code === 'reader_auth.use_atlas');
33
+ if (!rec) {
34
+ const methods = (body.providers ?? []).map((p) => p.method);
35
+ return (0, probe_1.check)(exports.READER_AUTH_ATLAS_CHECK, TITLE, 'pass', methods.includes('atlas_byo')
36
+ ? 'atlas_byo is configured.'
37
+ : `Nothing to recommend${methods.length ? ` (${methods.join(', ')})` : ''}.`, '');
38
+ }
39
+ const lines = [
40
+ `Recommended — ${rec.message}`,
41
+ rec.relink
42
+ ? ` 1. Relink the members: POST /orgs/${org}/members/relink ${JSON.stringify({ from: rec.relink.from, to: rec.relink.to })} (dry run first; ?apply=true writes)`
43
+ : null,
44
+ ` ${rec.relink ? '2' : '1'}. Switch: PATCH /orgs/${org}/reader-auth ${JSON.stringify(rec.patch)}`,
45
+ ` or in a manifest (version 2): ${JSON.stringify(rec.manifest)}`,
46
+ rec.docs ? ` See ${rec.docs}` : null,
47
+ ].filter((l) => l !== null);
48
+ return (0, probe_1.check)(exports.READER_AUTH_ATLAS_CHECK, TITLE, 'skip', lines.join('\n'), MATTERS);
49
+ }
50
+ //# sourceMappingURL=reader-auth-atlas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader-auth-atlas.js","sourceRoot":"","sources":["../../src/doctor/reader-auth-atlas.ts"],"names":[],"mappings":";;;AAgCA,oDAsCC;AApED,mCAAgC;AAEnB,QAAA,uBAAuB,GAAG,mBAAmB,CAAC;AAE3D,MAAM,KAAK,GAAG,iFAAiF,CAAC;AAChG,MAAM,OAAO,GACX,0KAA0K,CAAC;AAW7K;;;;;;;;;;;;GAYG;AACI,KAAK,UAAU,oBAAoB,CAAC,IAAU,EAAE,GAAW;IAChE,IAAI,IAA8E,CAAC;IACnF,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,kBAAkB,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC;aAC9F,IAAmB,CAAC;IACzB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,CAA0C,CAAC;QACvD,OAAO,IAAA,aAAK,EACV,+BAAuB,EACvB,KAAK,EACL,MAAM,EACN,qDAAqD,GAAG,CAAC,MAAM,IAAI,OAAO,+CAA+C,EACzH,EAAE,CACH,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,uBAAuB,CAAC,CAAC;IACzF,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAC5D,OAAO,IAAA,aAAK,EACV,+BAAuB,EACvB,KAAK,EACL,MAAM,EACN,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;YAC3B,CAAC,CAAC,0BAA0B;YAC5B,CAAC,CAAC,uBAAuB,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAC9E,EAAE,CACH,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG;QACZ,iBAAiB,GAAG,CAAC,OAAO,EAAE;QAC9B,GAAG,CAAC,MAAM;YACR,CAAC,CAAC,uCAAuC,GAAG,mBAAmB,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC,sCAAsC;YACjK,CAAC,CAAC,IAAI;QACR,KAAK,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,yBAAyB,GAAG,gBAAgB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;QAClG,sCAAsC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;QACpE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;KACtC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IACzC,OAAO,IAAA,aAAK,EAAC,+BAAuB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC;AAClF,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { Check } from '../conformance';
2
+ /**
3
+ * `robots-params`: the `Disallow` lines that keep crawlers off the re-cut
4
+ * addresses of a mounted space's lists (SEO spec §1, item 5).
5
+ *
6
+ * A reader page offers its sort, order, scope, period, filter, solved and
7
+ * range choices as GET forms, which crawlers do not submit — but an address
8
+ * already in a crawler's queue, a backlink, or another site's
9
+ * `?sort=name&order=desc` link still reaches it. Those copies say `noindex`
10
+ * (or name the uncut page as canonical), and are still a crawl each. The host's
11
+ * robots.txt is the one place that can say "do not even fetch these", and for
12
+ * a space mounted on somebody else's domain (`acme.com/community`) it is THEIR
13
+ * file — we cannot write it. So the doctor prints the lines to paste.
14
+ *
15
+ * Advisory, never a failure: a site that wants those copies crawled is making
16
+ * a choice. Passes once every line for this host is already in its robots.txt.
17
+ * Judged by HTTP alone, so it is the same for any host the doctor is pointed at.
18
+ */
19
+ /**
20
+ * The query keys a reader list is re-cut by — the web reader's
21
+ * `VARIANT_PARAMS` (apps/web/src/lib/reader-variant-robots.ts). Never `page`
22
+ * (a page of a list is a document of its own) and never a search.
23
+ */
24
+ export declare const VARIANT_PARAMS: readonly ["sort", "order", "scope", "period", "window", "filter", "solved", "range", "replies", "release"];
25
+ /**
26
+ * The lines for one mount. `*` and the `?` are robots wildcards (RFC 9309 §2.2.3
27
+ * — Google, Bing and every major crawler read them): `/community*?*sort=` is any
28
+ * address under the mount whose query names `sort`, first or not.
29
+ */
30
+ export declare function disallowLines(mountPath: string): string[];
31
+ /** The roots grouped by the origin whose robots.txt would carry them. */
32
+ export declare function linesByOrigin(roots: readonly string[]): Map<string, string[]>;
33
+ export declare function robotsParams(roots: readonly string[],
34
+ /** The origin whose robots.txt was read. */
35
+ origin: string, robots: string | undefined): Check;
36
+ //# sourceMappingURL=robots-params.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"robots-params.d.ts","sourceRoot":"","sources":["../../src/doctor/robots-params.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;GAIG;AACH,eAAO,MAAM,cAAc,4GAWjB,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAIzD;AAED,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAc7E;AAUD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,SAAS,MAAM,EAAE;AACxB,4CAA4C;AAC5C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,KAAK,CAuBP"}