hdoc-tools 0.62.1 → 0.62.2

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.
@@ -10,7 +10,8 @@
10
10
  // github.com/Hornbill-Docs/<docId>.
11
11
  // 2. The target article must exist in the book's GitHub repo (default
12
12
  // branch): <docId>/<path>.md, <path>/index.md, <path>.html or <path>.htm.
13
- // A path matched by the target book's redirects[] also passes.
13
+ // A path matched by the target book's redirects[] also passes, unless
14
+ // the redirect code is 410 (content deliberately removed) — error.
14
15
  // 3. If the source link carries a #hash-anchor, a matching heading anchor
15
16
  // must exist in the target article. Anchor ids are derived from h2/h3
16
17
  // headings with the same hdoc.makeAnchorIdFriendly slug used at build.
@@ -32,12 +33,22 @@
32
33
 
33
34
  const LIBRARY_URL = "https://docs.hornbill.com/_books/library.json";
34
35
  const GITHUB_ORG_FALLBACK = "https://github.com/Hornbill-Docs";
36
+ const PUBLISHED_SITE = "https://docs.hornbill.com";
37
+
38
+ // Books generated outside GitHub (built in Hornbill Projects, published
39
+ // straight to docs.hornbill.com) have no repo to inspect, so they are keyed
40
+ // off the docId suffix instead:
41
+ // -api / -db → not verifiable at all, skip
42
+ // -packages → verify the page exists on the published site
43
+ const UNVERIFIABLE_SUFFIXES = ["-api", "-db"];
44
+ const PUBLISHED_ONLY_SUFFIXES = ["-packages"];
35
45
 
36
46
  let git_token = "";
37
47
  let library_promise = null;
38
48
  const book_cache = {}; // docId -> promise of book resolution
39
49
  const article_cache = {}; // docId|path -> promise of article fetch
40
50
  const redirects_cache = {}; // repo url -> promise of redirect url set
51
+ const published_cache = {}; // published url -> promise of { ok, status }
41
52
 
42
53
  const gh_headers = () => {
43
54
  const headers = {
@@ -139,8 +150,10 @@
139
150
  };
140
151
  };
141
152
 
142
- // The redirect urls declared in the target book's hdocbook-project.json
143
- // a link to a redirected path is valid (the published site serves 301/308).
153
+ // The redirect urls declared in the target book's hdocbook-project.json,
154
+ // mapped to their redirect code. A link to a 301/308-redirected path is
155
+ // valid (the published site forwards it); a 410 means the content was
156
+ // deliberately removed, so a link to it is broken.
144
157
  const get_redirect_urls = (repo) => {
145
158
  if (!redirects_cache[repo]) {
146
159
  redirects_cache[repo] = (async () => {
@@ -150,7 +163,7 @@
150
163
  if (file.status === 200) {
151
164
  const project = JSON.parse(file.content);
152
165
  for (const redirect of project.redirects || []) {
153
- if (redirect.url) urls[redirect.url] = true;
166
+ if (redirect.url) urls[redirect.url] = redirect.code || 301;
154
167
  }
155
168
  }
156
169
  } catch {
@@ -196,8 +209,9 @@
196
209
 
197
210
  // Locate an article inside a book repo, trying the same resolution order
198
211
  // the published site uses. Returns
199
- // { found, redirected, unverifiable, anchors } — anchors null when the
200
- // article was matched via redirect (content not fetched).
212
+ // { found, redirected, gone, unverifiable, anchors } — anchors null when
213
+ // the article was matched via redirect (content not fetched); gone true
214
+ // when the path matched a 410 redirect (content removed).
201
215
  const resolve_article = (repo, doc_id, article_path) => {
202
216
  const cache_key = `${doc_id}|${article_path}`;
203
217
  if (!article_cache[cache_key]) {
@@ -227,7 +241,9 @@
227
241
  }
228
242
  }
229
243
  const redirect_urls = await get_redirect_urls(repo);
230
- if (redirect_urls[`/${base}`]) {
244
+ const redirect_code = redirect_urls[`/${base}`];
245
+ if (redirect_code) {
246
+ if (redirect_code === 410) return { found: false, gone: true };
231
247
  return { found: true, redirected: true, anchors: null };
232
248
  }
233
249
  return { found: false };
@@ -236,6 +252,29 @@
236
252
  return article_cache[cache_key];
237
253
  };
238
254
 
255
+ const has_suffix = (doc_id, suffixes) =>
256
+ suffixes.some((suffix) => doc_id.endsWith(suffix));
257
+
258
+ // GET the published site to confirm a generated book page exists. HEAD is
259
+ // NOT usable here — docs.hornbill.com answers 404 to HEAD on pages that
260
+ // GET serves 200.
261
+ const check_published = (url) => {
262
+ if (!published_cache[url]) {
263
+ published_cache[url] = (async () => {
264
+ const resp = await hdoc.fetchWithRetry(
265
+ url,
266
+ {
267
+ headers: { "User-Agent": "HornbillDocsBuild" },
268
+ timeoutMs: 10000,
269
+ },
270
+ 2,
271
+ );
272
+ return { ok: resp.ok, status: resp.status };
273
+ })();
274
+ }
275
+ return published_cache[url];
276
+ };
277
+
239
278
  exports.init = (token) => {
240
279
  git_token = token || "";
241
280
  };
@@ -251,6 +290,48 @@
251
290
  const doc_id = segments.shift();
252
291
  const article_path = segments.length > 0 ? segments.join("/") : "index";
253
292
 
293
+ // Books not sourced from GitHub — resolved by docId suffix, no repo
294
+ if (has_suffix(doc_id, UNVERIFIABLE_SUFFIXES)) {
295
+ return {
296
+ level: "skip",
297
+ message: `Inter-book link target book [${doc_id}] is generated - link not verified: ${link}`,
298
+ };
299
+ }
300
+ if (has_suffix(doc_id, PUBLISHED_ONLY_SUFFIXES)) {
301
+ const url = `${PUBLISHED_SITE}${link_path}`;
302
+ let published;
303
+ try {
304
+ published = await check_published(url);
305
+ } catch (e) {
306
+ return {
307
+ level: "warning",
308
+ message: `Unable to verify inter-book link [${link}]: ${e}`,
309
+ };
310
+ }
311
+ if (published.status === 404) {
312
+ return {
313
+ level: "error",
314
+ message: `Inter-book link target page does not exist on ${PUBLISHED_SITE} [${doc_id}]: ${link}`,
315
+ };
316
+ }
317
+ if (!published.ok) {
318
+ return {
319
+ level: "warning",
320
+ message: `Unable to verify inter-book link [${link}]: ${url} returned HTTP ${published.status}`,
321
+ };
322
+ }
323
+ if (hash_anchor) {
324
+ return {
325
+ level: "skip",
326
+ message: `Inter-book link page verified on ${PUBLISHED_SITE} - hash anchor not verified: ${link}`,
327
+ };
328
+ }
329
+ return {
330
+ level: "ok",
331
+ message: `Inter-book link verified on ${PUBLISHED_SITE}: ${link}`,
332
+ };
333
+ }
334
+
254
335
  let book;
255
336
  try {
256
337
  book = await resolve_book(doc_id);
@@ -296,6 +377,12 @@
296
377
  };
297
378
  }
298
379
  if (!article.found) {
380
+ if (article.gone) {
381
+ return {
382
+ level: "error",
383
+ message: `Inter-book link target article was removed (410 gone) from book [${doc_id}]: ${link}`,
384
+ };
385
+ }
299
386
  return {
300
387
  level: "error",
301
388
  message: `Inter-book link target article does not exist in book [${doc_id}]: ${link}`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hdoc-tools",
3
- "version": "0.62.1",
3
+ "version": "0.62.2",
4
4
  "description": "Hornbill HDocBook Development Support Tool",
5
5
  "main": "hdoc.js",
6
6
  "bin": {