@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,256 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CHECK_HELP = exports.CHECK_VALUE_FLAGS = void 0;
4
+ exports.runCheck = runCheck;
5
+ exports.folderReport = folderReport;
6
+ const node_fs_1 = require("node:fs");
7
+ const node_path_1 = require("node:path");
8
+ const manifest_1 = require("../publish/manifest");
9
+ const names_1 = require("../publish/names");
10
+ const scan_1 = require("../publish/scan");
11
+ const exit_codes_1 = require("../exit-codes");
12
+ const external_1 = require("./external");
13
+ const markdown_links_1 = require("./markdown-links");
14
+ exports.CHECK_VALUE_FLAGS = ['--allow-redirect', '--ignore'];
15
+ exports.CHECK_HELP = `forumulate check links [folder] [flags]
16
+ forumulate check links --space <id|slug> [--org <id|slug>] [flags]
17
+
18
+ Find broken links before readers do (FMU-F05).
19
+
20
+ On a folder (the default, "."): reads it the way \`publish\` does — forumulate.yaml
21
+ and every space in it — and checks, without a token and without publishing:
22
+ internal links ./other.md, ../guide/setup.md, /slug, ./slug — the page must exist
23
+ anchors #section and page#section — the heading (or {#id}, or id="…") must exist,
24
+ with the product's own anchor rule (duplicates get -2, -3)
25
+ pictures a local image or file the page points at must be on disk
26
+ external links with --external: a 2xx passes; a redirect passes only to a host
27
+ --allow-redirect allows (default same-host); never followed
28
+ On a published space (--space): asks the API for the same report, which adds
29
+ reference-page operation anchors (#operationId and #get-v1-x) and uploads.
30
+
31
+ Flags:
32
+ --external also check outside links (slow; paced per host; cached a day)
33
+ --allow-redirect <list> hosts a redirect may land on: same-host, docs.x.com, *.x.com
34
+ (space mode: the space/org key links.check_allowed_redirect_hosts)
35
+ --ignore <pattern> skip links containing this text (repeatable)
36
+ --no-cache ask every outside link again
37
+ --json the report as JSON on stdout
38
+
39
+ Exit status: 0 nothing broken, 1 something broken, 2 usage, 3 auth, 4 not found.`;
40
+ const flagValues = (argv, name) => argv.flatMap((a, i) => a.startsWith(`${name}=`) ? [a.slice(name.length + 1)] : a === name ? [argv[i + 1] ?? ''] : []);
41
+ /** `forumulate check …` — only `links` today; the noun leaves room for the next check. */
42
+ async function runCheck(argv, rest, deps) {
43
+ const [what, folder] = rest;
44
+ if (what !== 'links' || argv.includes('--help')) {
45
+ (what === 'links' || argv.includes('--help') ? deps.out : deps.err)(exports.CHECK_HELP);
46
+ return what === 'links' || argv.includes('--help') ? exit_codes_1.EXIT.OK : exit_codes_1.EXIT.USAGE;
47
+ }
48
+ const json = argv.includes('--json');
49
+ const external = argv.includes('--external');
50
+ const allow = flagValues(argv, '--allow-redirect').flatMap((v) => v.split(',')).map((v) => v.trim()).filter(Boolean);
51
+ const ignore = flagValues(argv, '--ignore').filter(Boolean);
52
+ const space = flagValues(argv, '--space')[0];
53
+ let report;
54
+ try {
55
+ report = space
56
+ ? await spaceReport(space, flagValues(argv, '--org')[0], external, deps)
57
+ : folderReport((0, node_path_1.resolve)(folder ?? '.'), deps);
58
+ }
59
+ catch (e) {
60
+ deps.err(`check links: ${e.message}`);
61
+ // A folder that cannot be read is the command line's fault (2); an API
62
+ // refusal maps as every command maps it (3 auth, 4 not found, …).
63
+ return space ? (0, exit_codes_1.exitCodeForError)(e) : exit_codes_1.EXIT.USAGE;
64
+ }
65
+ if (!space && external) {
66
+ const checker = new external_1.ExternalChecker({
67
+ allowRedirects: allow.length ? allow : ['same-host'],
68
+ cacheFile: argv.includes('--no-cache') ? null : (0, external_1.defaultCacheFile)(deps.env),
69
+ fetch: deps.fetch,
70
+ });
71
+ const pending = report.external;
72
+ const verdicts = await checker.checkAll(pending.keys());
73
+ report.checked.external = pending.size;
74
+ for (const [url, v] of verdicts) {
75
+ if (v.ok)
76
+ continue;
77
+ for (const at of pending.get(url) ?? [])
78
+ report.broken.push({
79
+ ...at,
80
+ url,
81
+ reason: v.reason ?? 'external_unreachable',
82
+ status: v.status,
83
+ ...(v.location ? { location: v.location } : {}),
84
+ });
85
+ }
86
+ }
87
+ report.broken = report.broken.filter((b) => !ignore.some((p) => b.url.includes(p)));
88
+ print(report, json, deps);
89
+ return report.broken.length ? exit_codes_1.EXIT.FAILED : exit_codes_1.EXIT.OK;
90
+ }
91
+ function print(report, json, deps) {
92
+ const { external: _x, ...plain } = report;
93
+ if (json) {
94
+ deps.out(JSON.stringify(plain, null, 2));
95
+ return;
96
+ }
97
+ for (const b of report.broken) {
98
+ const extra = b.anchor
99
+ ? ` #${b.anchor} is not on that page`
100
+ : b.location
101
+ ? ` → ${b.location} (${b.status})`
102
+ : b.status
103
+ ? ` (${b.status})`
104
+ : '';
105
+ deps.out(`${b.where}${b.line ? `:${b.line}` : ''} ${b.reason.padEnd(20)} ${b.url}${extra}`);
106
+ }
107
+ const c = report.checked;
108
+ deps.out(`${report.broken.length ? `${report.broken.length} broken` : 'No broken links'} — ` +
109
+ `${c.links} link(s) on ${c.pages} page(s), ${c.anchors} anchor(s)` +
110
+ (c.external ? `, ${c.external} outside address(es)` : '') +
111
+ (c.unchecked ? `; ${c.unchecked} link(s) into other spaces not checked from a folder` : '') +
112
+ '.');
113
+ }
114
+ async function spaceReport(space, orgFlag, external, deps) {
115
+ const org = orgFlag ?? deps.env?.FORUMULATE_ORG ?? process.env.FORUMULATE_ORG;
116
+ if (!org)
117
+ throw Object.assign(new Error('which organization? Pass --org or set FORUMULATE_ORG.'), { exitCode: exit_codes_1.EXIT.USAGE });
118
+ if (!deps.request)
119
+ throw Object.assign(new Error('no API client'), { exitCode: exit_codes_1.EXIT.AUTH });
120
+ const r = await deps.request('GET', `/orgs/${encodeURIComponent(org)}/spaces/${encodeURIComponent(space)}/docs/broken-links`, {
121
+ query: { external, refresh: true },
122
+ });
123
+ return {
124
+ mode: 'space',
125
+ checked: { ...r.checked, unchecked: 0 },
126
+ broken: r.broken.map(({ pageSlug, ...b }) => ({ where: pageSlug, ...b })),
127
+ };
128
+ }
129
+ /** Path inside a space folder → the page it is, case-insensitively. */
130
+ function pageIndex(root, scan) {
131
+ const bySlug = new Map();
132
+ const byFile = new Map();
133
+ for (const c of scan.collections)
134
+ for (const p of c.pages) {
135
+ if (p.stub)
136
+ continue;
137
+ bySlug.set(p.slug, p);
138
+ // `file` is relative to the publish root; links are relative to the page.
139
+ byFile.set((0, node_path_1.relative)(scan.absDir, (0, node_path_1.resolve)(root, p.file)).split(node_path_1.sep).join('/').toLowerCase(), p);
140
+ }
141
+ return { bySlug, byFile };
142
+ }
143
+ /** The folder half: every space the manifest names, read as `publish` reads it. */
144
+ function folderReport(root, _deps = {}) {
145
+ if (!(0, node_fs_1.existsSync)(root) || !(0, node_fs_1.statSync)(root).isDirectory())
146
+ throw new Error(`${root} is not a folder`);
147
+ const manifest = (0, manifest_1.loadManifest)((0, manifest_1.findManifest)(root));
148
+ const spaces = manifest.spaces.map((spec) => {
149
+ const scan = (0, scan_1.scanSpace)(root, spec, 'docs', []);
150
+ return { key: spec.space ?? spec.path, spec, scan, ...pageIndex(root, scan) };
151
+ });
152
+ const byKey = new Map(spaces.map((s) => [s.key ?? '', s]));
153
+ const anchorsMemo = new Map();
154
+ const anchorsOf = (p) => {
155
+ let a = anchorsMemo.get(p);
156
+ if (!a)
157
+ anchorsMemo.set(p, (a = (0, markdown_links_1.markdownAnchors)(p.source, p.title)));
158
+ return a;
159
+ };
160
+ const report = {
161
+ mode: 'folder',
162
+ checked: { pages: 0, links: 0, anchors: 0, external: 0, unchecked: 0 },
163
+ broken: [],
164
+ external: new Map(),
165
+ };
166
+ for (const s of spaces) {
167
+ for (const page of s.bySlug.values()) {
168
+ report.checked.pages += 1;
169
+ const fileAbs = (0, node_path_1.resolve)(root, page.file);
170
+ const inSpace = (0, node_path_1.relative)(s.scan.absDir, fileAbs).split(node_path_1.sep).join('/');
171
+ for (const link of (0, markdown_links_1.markdownLinks)(page.source)) {
172
+ const href = link.href.trim();
173
+ if (!href || /^(mailto|tel|javascript|data):/i.test(href))
174
+ continue;
175
+ report.checked.links += 1;
176
+ const where = { where: page.file, line: link.line };
177
+ if (/^https?:\/\//i.test(href)) {
178
+ const bare = href.replace(/#.*$/, '');
179
+ report.external.set(bare, [...(report.external.get(bare) ?? []), where]);
180
+ continue;
181
+ }
182
+ if (/^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith('//'))
183
+ continue;
184
+ const hash = href.indexOf('#');
185
+ const path = (hash >= 0 ? href.slice(0, hash) : href).replace(/\?.*$/, '');
186
+ let anchor = null;
187
+ if (hash >= 0 && hash < href.length - 1) {
188
+ try {
189
+ anchor = decodeURIComponent(href.slice(hash + 1));
190
+ }
191
+ catch {
192
+ anchor = href.slice(hash + 1);
193
+ }
194
+ }
195
+ let target;
196
+ if (!path)
197
+ target = page;
198
+ else if (names_1.MD.test(path)) {
199
+ let rel;
200
+ try {
201
+ rel = node_path_1.posix.normalize(node_path_1.posix.join(node_path_1.posix.dirname(inSpace), decodeURI(path))).toLowerCase();
202
+ }
203
+ catch {
204
+ rel = path.toLowerCase();
205
+ }
206
+ target = s.byFile.get(rel);
207
+ if (!target) {
208
+ report.broken.push({ ...where, url: href, reason: 'internal_not_found' });
209
+ continue;
210
+ }
211
+ }
212
+ else if (names_1.ASSET.test(path) || link.media) {
213
+ // A picture or file next to the page; a root path is the site's to serve.
214
+ if (path.startsWith('/'))
215
+ continue;
216
+ let abs;
217
+ try {
218
+ abs = (0, node_path_1.join)((0, node_path_1.dirname)(fileAbs), decodeURI(path));
219
+ }
220
+ catch {
221
+ abs = (0, node_path_1.join)((0, node_path_1.dirname)(fileAbs), path);
222
+ }
223
+ if (!(0, node_fs_1.existsSync)(abs))
224
+ report.broken.push({ ...where, url: href, reason: 'asset_missing' });
225
+ continue;
226
+ }
227
+ else {
228
+ const segments = path.split('/').filter((x) => x && x !== '.');
229
+ const last = (segments[segments.length - 1] ?? '').replace(/\.md$/i, '');
230
+ if (path.startsWith('/') && segments.length > 1) {
231
+ const other = byKey.get(segments[0]);
232
+ if (!other) {
233
+ // Another section of the site we cannot see from this folder.
234
+ report.checked.unchecked += 1;
235
+ continue;
236
+ }
237
+ target = other.bySlug.get(last);
238
+ }
239
+ else
240
+ target = s.bySlug.get(last);
241
+ if (!target) {
242
+ report.broken.push({ ...where, url: href, reason: 'internal_not_found' });
243
+ continue;
244
+ }
245
+ }
246
+ if (anchor && target) {
247
+ report.checked.anchors += 1;
248
+ if (!anchorsOf(target).has(anchor))
249
+ report.broken.push({ ...where, url: href, reason: 'anchor_not_found', anchor });
250
+ }
251
+ }
252
+ }
253
+ }
254
+ return report;
255
+ }
256
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/check/command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /** One outside link's verdict. */
2
+ export interface Verdict {
3
+ ok: boolean;
4
+ status: number;
5
+ location?: string;
6
+ reason?: 'external_unreachable' | 'external_redirect';
7
+ /** When it was asked, ms since the epoch — for the cache. */
8
+ at: number;
9
+ }
10
+ export type Fetcher = (url: string, init: {
11
+ method: string;
12
+ redirect: 'manual';
13
+ signal: AbortSignal;
14
+ headers: Record<string, string>;
15
+ }) => Promise<{
16
+ status: number;
17
+ headers: {
18
+ get(name: string): string | null;
19
+ };
20
+ }>;
21
+ export interface ExternalOptions {
22
+ /** Hosts a redirect may land on and still pass: `same-host`, `docs.x.com`, `*.x.com`, `*`. */
23
+ allowRedirects: string[];
24
+ concurrency?: number;
25
+ /** Gap between two requests to one host, ms. */
26
+ perHostGapMs?: number;
27
+ timeoutMs?: number;
28
+ /** Where verdicts are kept between runs; null for none. */
29
+ cacheFile?: string | null;
30
+ okTtlMs?: number;
31
+ brokenTtlMs?: number;
32
+ fetch?: Fetcher;
33
+ sleep?: (ms: number) => Promise<void>;
34
+ now?: () => number;
35
+ }
36
+ /** Where the verdicts live: `$XDG_CACHE_HOME/forumulate/link-check.json`. */
37
+ export declare function defaultCacheFile(env?: NodeJS.ProcessEnv): string;
38
+ /**
39
+ * Whether a redirect from `from` to `location` is one the caller accepts —
40
+ * the same rule as the API's `redirectAllowed` (packages/shared docs-workflow),
41
+ * mirrored because this package ships alone.
42
+ */
43
+ export declare function redirectAllowed(from: string, location: string, allow: readonly string[]): boolean;
44
+ /**
45
+ * Outside links, checked the way the API checks them (FMU-F05): no redirect is
46
+ * followed — a 3xx passes only to an allowed host, else it is
47
+ * `external_redirect` naming where it goes; `HEAD` first, `GET` when a host
48
+ * refuses `HEAD`; a few requests at once and a gap between two to the same
49
+ * host, so a docs folder with 300 links to one site is not a burst; and a
50
+ * verdict file, so a second run in a minute asks nothing (a day for a working
51
+ * link, an hour for a broken one).
52
+ */
53
+ export declare class ExternalChecker {
54
+ private readonly last;
55
+ private cache;
56
+ private readonly o;
57
+ constructor(opts: ExternalOptions);
58
+ checkAll(urls: Iterable<string>): Promise<Map<string, Verdict>>;
59
+ private fresh;
60
+ check(url: string): Promise<Verdict>;
61
+ private pace;
62
+ private request;
63
+ private ask;
64
+ private save;
65
+ }
66
+ //# sourceMappingURL=external.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/check/external.ts"],"names":[],"mappings":"AAIA,kCAAkC;AAClC,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,sBAAsB,GAAG,mBAAmB,CAAC;IACtD,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,MAAM,OAAO,GAAG,CACpB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KAC/F,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAAE,CAAC,CAAC;AAEhF,MAAM,WAAW,eAAe;IAC9B,8FAA8F;IAC9F,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAG7E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAmBjG;AAED;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;IAClD,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAC,CAAC,CAGhB;gBAEU,IAAI,EAAE,eAAe;IAsB3B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAWrE,OAAO,CAAC,KAAK;IAKP,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAQ5B,IAAI;YAaJ,OAAO;YAgBP,GAAG;IAyBjB,OAAO,CAAC,IAAI;CAWb"}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExternalChecker = void 0;
4
+ exports.defaultCacheFile = defaultCacheFile;
5
+ exports.redirectAllowed = redirectAllowed;
6
+ const node_fs_1 = require("node:fs");
7
+ const node_os_1 = require("node:os");
8
+ const node_path_1 = require("node:path");
9
+ /** Where the verdicts live: `$XDG_CACHE_HOME/forumulate/link-check.json`. */
10
+ function defaultCacheFile(env = process.env) {
11
+ const base = env.XDG_CACHE_HOME || (0, node_path_1.join)((0, node_os_1.homedir)(), '.cache');
12
+ return (0, node_path_1.join)(base, 'forumulate', 'link-check.json');
13
+ }
14
+ /**
15
+ * Whether a redirect from `from` to `location` is one the caller accepts —
16
+ * the same rule as the API's `redirectAllowed` (packages/shared docs-workflow),
17
+ * mirrored because this package ships alone.
18
+ */
19
+ function redirectAllowed(from, location, allow) {
20
+ let a;
21
+ let b;
22
+ try {
23
+ a = new URL(from);
24
+ b = new URL(location, from);
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ const host = b.hostname.toLowerCase();
30
+ for (const raw of allow) {
31
+ const p = raw.trim().toLowerCase();
32
+ if (!p)
33
+ continue;
34
+ if (p === 'same-host' && host === a.hostname.toLowerCase())
35
+ return true;
36
+ if (p === '*')
37
+ return true;
38
+ if (p.startsWith('*.') && (host === p.slice(2) || host.endsWith(p.slice(1))))
39
+ return true;
40
+ if (host === p)
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ /**
46
+ * Outside links, checked the way the API checks them (FMU-F05): no redirect is
47
+ * followed — a 3xx passes only to an allowed host, else it is
48
+ * `external_redirect` naming where it goes; `HEAD` first, `GET` when a host
49
+ * refuses `HEAD`; a few requests at once and a gap between two to the same
50
+ * host, so a docs folder with 300 links to one site is not a burst; and a
51
+ * verdict file, so a second run in a minute asks nothing (a day for a working
52
+ * link, an hour for a broken one).
53
+ */
54
+ class ExternalChecker {
55
+ last = new Map();
56
+ cache = {};
57
+ o;
58
+ constructor(opts) {
59
+ this.o = {
60
+ concurrency: 4,
61
+ perHostGapMs: 300,
62
+ timeoutMs: 10_000,
63
+ okTtlMs: 86_400_000,
64
+ brokenTtlMs: 3_600_000,
65
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
66
+ now: () => Date.now(),
67
+ ...opts,
68
+ fetch: opts.fetch ?? globalThis.fetch,
69
+ cacheFile: opts.cacheFile === undefined ? null : opts.cacheFile,
70
+ };
71
+ if (this.o.cacheFile && (0, node_fs_1.existsSync)(this.o.cacheFile)) {
72
+ try {
73
+ this.cache = JSON.parse((0, node_fs_1.readFileSync)(this.o.cacheFile, 'utf8'));
74
+ }
75
+ catch {
76
+ this.cache = {};
77
+ }
78
+ }
79
+ }
80
+ async checkAll(urls) {
81
+ const queue = [...new Set(urls)];
82
+ const out = new Map();
83
+ const worker = async () => {
84
+ for (let u = queue.shift(); u !== undefined; u = queue.shift())
85
+ out.set(u, await this.check(u));
86
+ };
87
+ await Promise.all(Array.from({ length: Math.min(this.o.concurrency, queue.length) }, worker));
88
+ this.save();
89
+ return out;
90
+ }
91
+ fresh(v) {
92
+ if (!v)
93
+ return false;
94
+ return this.o.now() - v.at < (v.ok ? this.o.okTtlMs : this.o.brokenTtlMs);
95
+ }
96
+ async check(url) {
97
+ const hit = this.cache[url];
98
+ if (this.fresh(hit))
99
+ return hit;
100
+ const v = await this.ask(url);
101
+ this.cache[url] = v;
102
+ return v;
103
+ }
104
+ async pace(url) {
105
+ let host = '';
106
+ try {
107
+ host = new URL(url).host;
108
+ }
109
+ catch {
110
+ return;
111
+ }
112
+ const due = (this.last.get(host) ?? 0) + this.o.perHostGapMs;
113
+ this.last.set(host, Math.max(this.o.now(), due));
114
+ const wait = due - this.o.now();
115
+ if (wait > 0)
116
+ await this.o.sleep(wait);
117
+ }
118
+ async request(url, method) {
119
+ await this.pace(url);
120
+ const ctrl = new AbortController();
121
+ const timer = setTimeout(() => ctrl.abort(), this.o.timeoutMs);
122
+ try {
123
+ return await this.o.fetch(url, {
124
+ method,
125
+ redirect: 'manual',
126
+ signal: ctrl.signal,
127
+ headers: { 'user-agent': 'forumulate-cli link check', accept: 'text/html,*/*;q=0.8' },
128
+ });
129
+ }
130
+ finally {
131
+ clearTimeout(timer);
132
+ }
133
+ }
134
+ async ask(url) {
135
+ const at = this.o.now();
136
+ try {
137
+ let res = await this.request(url, 'HEAD');
138
+ if ([403, 405, 501].includes(res.status))
139
+ res = await this.request(url, 'GET');
140
+ const status = res.status;
141
+ if (status >= 200 && status < 300)
142
+ return { ok: true, status, at };
143
+ if (status >= 300 && status < 400) {
144
+ const raw = res.headers.get('location') ?? '';
145
+ let location = raw;
146
+ try {
147
+ location = new URL(raw, url).toString();
148
+ }
149
+ catch {
150
+ /* kept as sent */
151
+ }
152
+ if (raw && redirectAllowed(url, location, this.o.allowRedirects))
153
+ return { ok: true, status, location, at };
154
+ return { ok: false, status, location, reason: 'external_redirect', at };
155
+ }
156
+ return { ok: false, status, reason: 'external_unreachable', at };
157
+ }
158
+ catch {
159
+ return { ok: false, status: 0, reason: 'external_unreachable', at };
160
+ }
161
+ }
162
+ save() {
163
+ if (!this.o.cacheFile)
164
+ return;
165
+ try {
166
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(this.o.cacheFile), { recursive: true });
167
+ // Only verdicts still worth keeping, so the file does not grow forever.
168
+ const keep = Object.fromEntries(Object.entries(this.cache).filter(([, v]) => this.fresh(v)));
169
+ (0, node_fs_1.writeFileSync)(this.o.cacheFile, JSON.stringify(keep));
170
+ }
171
+ catch {
172
+ /* a cache that cannot be written is only a slower next run */
173
+ }
174
+ }
175
+ }
176
+ exports.ExternalChecker = ExternalChecker;
177
+ //# sourceMappingURL=external.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/check/external.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Links and anchors in a Markdown file, read the way the product reads them —
3
+ * for `forumulate check links` on a folder (FMU-F05), before anything is
4
+ * published.
5
+ *
6
+ * This package ships alone (it depends on the SDK and `yaml`, nothing else), so
7
+ * the heading rule is mirrored here rather than imported, the way `names.ts`
8
+ * mirrors the slug rule: `headingSlug` below is the docs-schema's
9
+ * `headingSlug` (packages/docs-schema/src/heading-slug.ts), and
10
+ * `markdown-links.test.ts` pins the same cases that file's tests do. A checker
11
+ * whose anchors differ from the renderer's would report working links broken.
12
+ */
13
+ /** A heading's anchor id from its text — the renderer's rule. */
14
+ export declare function headingSlug(text: string): string;
15
+ /** The id the renderer used before the accent fix, still answered as an alias. */
16
+ export declare function legacyHeadingSlug(text: string): string | null;
17
+ /** A link as written, and the line it is on (1-based, in the file). */
18
+ export interface FoundLink {
19
+ href: string;
20
+ line: number;
21
+ /** An image or embedded file, rather than a link a reader follows. */
22
+ media: boolean;
23
+ }
24
+ /**
25
+ * Every `#fragment` the page will answer to: each heading's id (duplicates
26
+ * suffixed `-2`, `-3`, as the renderer claims them), its pre-fix alias, an
27
+ * explicit `{#id}` on a heading, and any `id="…"` / `name="…"` in HTML.
28
+ * `title` drops a leading `# Title` that repeats the page title, which the
29
+ * renderer does not render as a heading either.
30
+ */
31
+ export declare function markdownAnchors(text: string, title?: string): Set<string>;
32
+ /**
33
+ * Every link and picture in the file: `[t](href)`, `![a](src)`, reference
34
+ * definitions `[id]: href`, and HTML `href=` / `src=` — not inside a code
35
+ * fence or an inline code span, where a URL is an example, not a link.
36
+ */
37
+ export declare function markdownLinks(text: string): FoundLink[];
38
+ //# sourceMappingURL=markdown-links.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown-links.d.ts","sourceRoot":"","sources":["../../src/check/markdown-links.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUhD;AAED,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS7D;AA4BD,uEAAuE;AACvE,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,KAAK,EAAE,OAAO,CAAC;CAChB;AAwBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAkCzE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAgBvD"}