From fd311595df3639495869b46ad243d7fe398899e7 Mon Sep 17 00:00:00 2001 From: "Kenneth G. Franqueiro" Date: Fri, 6 Dec 2024 18:12:47 -0500 Subject: [PATCH] Tolerate specref API failures during local dev (#4167) Since #4124 was merged, all builds require an HTTP request to api.specref.org in order to resolve additional bibliographical references. This may cause issues in local dev in the case of being offline due to travel or internet hiccups, behind a proxy, etc. This commit adds a reusable function for cases where a HTTP failure during local dev is tolerable, and uses it for the bibrefs request. Running `npm build` will still result in an immediate failure and stack trace; this is intentional to ensure that nothing slips by during builds for PR previews, GitHub Pages, or w3.org. --- 11ty/biblio.ts | 14 +++++++++----- 11ty/common.ts | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/11ty/biblio.ts b/11ty/biblio.ts index 24a809fdb8..fcf5faca80 100644 --- a/11ty/biblio.ts +++ b/11ty/biblio.ts @@ -3,6 +3,8 @@ import { readFile } from "fs/promises"; import { glob } from "glob"; import uniq from "lodash-es/uniq"; +import { wrapAxiosRequest } from "./common"; + export const biblioPattern = /\[\[\??([\w-]+)\]\]/g; /** Compiles URLs from local biblio + specref for linking in Understanding documents. */ @@ -12,7 +14,7 @@ export async function getBiblio() { .replace(/^respecConfig\.localBiblio\s*=\s*/, "(") .replace("};", "})") ); - + const refs: string[] = []; for (const path of await glob(["guidelines/**/*.html", "understanding/*/*.html"])) { const content = await readFile(path, "utf8"); @@ -20,16 +22,18 @@ export async function getBiblio() { while ((match = biblioPattern.exec(content))) if (!localBiblio[match[1]]) refs.push(match[1]); } const uniqueRefs = uniq(refs); - - const response = await axios.get(`https://api.specref.org/bibrefs?refs=${uniqueRefs.join(",")}`); + + const response = await wrapAxiosRequest( + axios.get(`https://api.specref.org/bibrefs?refs=${uniqueRefs.join(",")}`) + ); const fullBiblio = { ...response.data, ...localBiblio, }; - + const resolvedRefs = Object.keys(fullBiblio); const unresolvedRefs = uniqueRefs.filter((ref) => !resolvedRefs.includes(ref)); if (unresolvedRefs.length) console.warn(`Unresolved biblio refs: ${unresolvedRefs.join(", ")}`); - + return fullBiblio; } diff --git a/11ty/common.ts b/11ty/common.ts index ade5d87f14..bda9ec4618 100644 --- a/11ty/common.ts +++ b/11ty/common.ts @@ -1,5 +1,7 @@ /** @fileoverview Common functions used by multiple parts of the build process */ +import { AxiosError, type AxiosResponse } from "axios"; + import type { Guideline, Principle, SuccessCriterion } from "./guidelines"; /** Generates an ID for heading permalinks. Equivalent to wcag:generate-id in base.xslt. */ @@ -28,3 +30,23 @@ export function wcagSort( } return 0; } + +/** + * Handles HTTP error responses from Axios requests in local dev; + * re-throws error during builds to fail loudly. + * This should only be used for non-critical requests that can tolerate null data + * without major side effects. + */ +export const wrapAxiosRequest = (promise: Promise>) => + promise.catch((error) => { + if (!(error instanceof AxiosError) || !error.response || !error.request) throw error; + const { response, request } = error; + console.warn( + `AxiosError: status ${response.status} received from ${ + request.protocol + "//" + request.host + }${request.path || ""}` + ); + + if (process.env.ELEVENTY_RUN_MODE === "build") throw error; + else return { data: null }; + });