This is an automated email from the ASF dual-hosted git repository.

jason810496 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new be4a1e671e2 Fix unstyled TypeScript SDK API reference on the published 
site (#72105)
be4a1e671e2 is described below

commit be4a1e671e20cd278696a6deb033e6dd8d1d185c
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Thu Aug 27 00:03:03 2026 +0800

    Fix unstyled TypeScript SDK API reference on the published site (#72105)
---
 .github/workflows/publish-docs-to-s3.yml           |   2 +-
 .../airflow_breeze/commands/developer_commands.py  |   9 +-
 ts-sdk/DEVELOPMENT.md                              |  21 ++-
 ts-sdk/README.md                                   |   4 +-
 ts-sdk/docs/package.json                           |   6 +-
 ts-sdk/docs/scripts/deployment-checks.mjs          | 102 +++++++++++++
 ts-sdk/docs/scripts/strip-remote-fonts.mjs         |  43 ++++++
 ts-sdk/docs/scripts/verify-deployment-paths.mjs    |  71 +++++++++
 ts-sdk/docs/tests/deployment-checks.test.mjs       | 161 +++++++++++++++++++++
 ts-sdk/docs/typedoc.config.mjs                     |  67 +++++++++
 ts-sdk/docs/typedoc.json                           |  24 ---
 11 files changed, 474 insertions(+), 36 deletions(-)

diff --git a/.github/workflows/publish-docs-to-s3.yml 
b/.github/workflows/publish-docs-to-s3.yml
index ebd0b0bfd88..982751db9b3 100644
--- a/.github/workflows/publish-docs-to-s3.yml
+++ b/.github/workflows/publish-docs-to-s3.yml
@@ -526,7 +526,7 @@ jobs:
           if [[ "${INCLUDE_DOCS}" != "all" && " ${INCLUDE_DOCS} " != *" ts-sdk 
"* ]]; then
             echo "exists=false" >> "${GITHUB_OUTPUT}"
             echo "include-docs='${INCLUDE_DOCS}' is a specific package list 
without 'ts-sdk' — skipping TypeDoc build."
-          elif [[ -f "ts-sdk/docs/typedoc.json" ]]; then
+          elif [[ -f "ts-sdk/docs/typedoc.config.mjs" ]]; then
             echo "exists=true" >> "${GITHUB_OUTPUT}"
           else
             echo "exists=false" >> "${GITHUB_OUTPUT}"
diff --git a/dev/breeze/src/airflow_breeze/commands/developer_commands.py 
b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
index 55e09fd40b5..617f06329fc 100644
--- a/dev/breeze/src/airflow_breeze/commands/developer_commands.py
+++ b/dev/breeze/src/airflow_breeze/commands/developer_commands.py
@@ -852,14 +852,15 @@ def _build_ts_sdk_docs(generated_path: Path) -> int:
             f"node:{TYPESCRIPT_SDK_NODE_VERSION}-bookworm-slim",
             "sh",
             "-c",
-            # `npm ci` keeps the lock file authoritative; `npm run build` 
strips the ASF
-            # header from the landing page and then runs TypeDoc.
-            "npm ci --no-audit --no-fund && npm run build",
+            # `npm ci` keeps the lock file authoritative; `npm test` covers 
the postbuild
+            # checks; `npm run build` strips the ASF header from the landing 
page, runs
+            # TypeDoc, then checks the generated HTML is publishable.
+            "npm ci --no-audit --no-fund && npm test && npm run build",
         ],
         check=False,
     )
     if result.returncode != 0:
-        console_print("[error]TypeDoc build failed.")
+        console_print("[error]TypeScript SDK docs build failed.")
         return result.returncode
 
     _stage_sdk_docs(
diff --git a/ts-sdk/DEVELOPMENT.md b/ts-sdk/DEVELOPMENT.md
index 92a4a60d7f4..1966842c31b 100644
--- a/ts-sdk/DEVELOPMENT.md
+++ b/ts-sdk/DEVELOPMENT.md
@@ -75,6 +75,20 @@ a `stable.txt` holding the version from 
`ts-sdk/package.json`. To iterate on the
 docs directly instead, `npm ci && npm run build` inside `ts-sdk/docs/` writes 
to
 `ts-sdk/docs/_build/html/`, and `npm start` rebuilds on change.
 
+`typedoc.config.mjs` pins the theme's `basePath` to `/docs/ts-sdk/<version>`, 
so
+the generated HTML expects to be served from that prefix and looks unstyled 
when
+opened straight off disk. Override it for any local preview, `npm start`
+included:
+
+```bash
+TS_SDK_DOCS_BASE_PATH=/ npm run build && npx serve _build/html
+```
+
+`npm run build` also strips the theme's Google Fonts tags and then fails if the
+output still has root-relative asset URLs or remote font requests. `npm test`
+covers those checks; `npm start` skips them, so run a full build before
+publishing.
+
 CI builds the reference on every change under `ts-sdk/src/` or `ts-sdk/docs/`,
 so a broken docs build fails the PR rather than the release.
 
@@ -91,9 +105,10 @@ gh workflow run "Publish Docs to S3" --repo apache/airflow 
--ref main \
   -f destination=live
 ```
 
-Use `destination=staging` first to check the output, then `live`. Confirm that
-`https://airflow.apache.org/docs/ts-sdk/stable/` resolves (allow time for cache
-invalidation) and that `/docs/ts-sdk/` redirects to it.
+Use `destination=staging` first and check
+<https://airflow.staged.apache.org/docs/ts-sdk/stable/>, then publish to `live`
+and confirm that <https://airflow.apache.org/docs/ts-sdk/stable/> resolves 
(allow
+time for cache invalidation) and that `/docs/ts-sdk/` redirects to it.
 
 ## Publishing
 
diff --git a/ts-sdk/README.md b/ts-sdk/README.md
index 439a79e522e..b89d846969b 100644
--- a/ts-sdk/README.md
+++ b/ts-sdk/README.md
@@ -270,9 +270,9 @@ Do not edit the table by hand — update the manifest and run 
the
 
 ## Links
 
-- [TypeScript SDK guide (staged docs build from latest 
main)](http://apache-airflow-docs.s3-website.eu-central-1.amazonaws.com/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html)
+- [TypeScript SDK guide (staged 
docs)](https://airflow.staged.apache.org/docs/apache-airflow/stable/authoring-and-scheduling/language-sdks/typescript.html)
   — how Airflow runs TypeScript task handlers
-- [API reference 
(staged)](http://apache-airflow-docs.s3-website.eu-central-1.amazonaws.com/docs/ts-sdk/stable/)
+- [API reference 
(staged)](https://airflow.staged.apache.org/docs/ts-sdk/stable/)
   — generated from the TypeScript sources
 - [Source](https://github.com/apache/airflow/tree/main/ts-sdk) — the `ts-sdk/`
   directory of the Apache Airflow monorepo
diff --git a/ts-sdk/docs/package.json b/ts-sdk/docs/package.json
index fd0e1328f25..a0579cca8c0 100644
--- a/ts-sdk/docs/package.json
+++ b/ts-sdk/docs/package.json
@@ -5,10 +5,12 @@
   "description": "Documentation toolchain for the Apache Airflow TypeScript 
SDK (ts-sdk). TypeDoc renders the API reference straight to HTML with the 
clean-jsdoc-theme; it is kept in its own package so the docs-only dependencies 
stay out of the SDK's runtime and build toolchain.",
   "type": "module",
   "scripts": {
+    "test": "node --test \"tests/**/*.test.mjs\"",
     "prebuild": "node scripts/prepare-readme.mjs",
-    "build": "typedoc",
+    "build": "typedoc --options typedoc.config.mjs",
+    "postbuild": "node scripts/strip-remote-fonts.mjs && node 
scripts/verify-deployment-paths.mjs",
     "prestart": "node scripts/prepare-readme.mjs",
-    "start": "typedoc --watch"
+    "start": "typedoc --options typedoc.config.mjs --watch"
   },
   "devDependencies": {
     "@clean-jsdoc-theme/typedoc": "^5.1.1",
diff --git a/ts-sdk/docs/scripts/deployment-checks.mjs 
b/ts-sdk/docs/scripts/deployment-checks.mjs
new file mode 100644
index 00000000000..9d88e8e3c7c
--- /dev/null
+++ b/ts-sdk/docs/scripts/deployment-checks.mjs
@@ -0,0 +1,102 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/** Shared helpers and paths behind the docs postbuild steps. See 
tests/deployment-checks.test.mjs. */
+
+import { readdir } from "node:fs/promises";
+import { join } from "node:path";
+import { fileURLToPath } from "node:url";
+
+// One `<link>` shape covers the preconnect hints and the stylesheet alike: 
the theme emits
+// all three with the font host in `href`.
+const REMOTE_FONT_TAG =
+  
/<link\b[^>]*href="https:\/\/fonts\.(?:googleapis|gstatic)\.com[^"]*"[^>]*>/g;
+
+const ROOT_RELATIVE = /(?:href|src)="\/(?:_assets|_islands)\//;
+const REMOTE_FONTS = /fonts\.(?:googleapis|gstatic)\.com/;
+
+/** The rendered site, resolved from this file so every entry point agrees on 
it. */
+export const HTML_ROOT = fileURLToPath(new URL("../_build/html", 
import.meta.url));
+
+/** The SDK's package.json, not the docs toolchain's: its version is the 
published prefix. */
+export const SDK_PACKAGE_JSON = new URL("../../package.json", import.meta.url);
+
+/** Empty string means "served from the root", where root-relative URLs are 
correct. */
+export function normalizeBasePath(rawBasePath) {
+  return rawBasePath.replace(/\/+$/, "");
+}
+
+/**
+ * A blank ``TS_SDK_DOCS_BASE_PATH`` counts as unset rather than as the root, 
so an empty
+ * env var cannot quietly publish a root-relative build past checks that then 
skip themselves.
+ */
+export function resolveBasePath(version, override = 
process.env.TS_SDK_DOCS_BASE_PATH) {
+  return normalizeBasePath(override?.trim() || `/docs/ts-sdk/${version}`);
+}
+
+export function stripRemoteFontTags(html) {
+  return html.replace(REMOTE_FONT_TAG, "");
+}
+
+export function hasRemoteFonts(text) {
+  return REMOTE_FONTS.test(text);
+}
+
+export function inspectPage(html, basePath) {
+  return {
+    rootRelative: ROOT_RELATIVE.test(html),
+    remoteFonts: hasRemoteFonts(html),
+    usesBasePath: basePath !== "" && html.includes(`${basePath}/_assets/`),
+  };
+}
+
+const preview = (files) => `${files.slice(0, 5).join(", ")}${files.length > 5 
? ", ..." : ""}`;
+
+/** @returns {string[]} human-readable failures; empty means the build is 
publishable. */
+export function collectErrors({ basePath, scanned, rootRelative, remoteFonts, 
usingBasePath }) {
+  const errors = [];
+  const servedFromRoot = basePath === "";
+
+  if (scanned === 0) return ["no HTML found -- did the TypeDoc build run?"];
+
+  if (!servedFromRoot && rootRelative.length > 0) {
+    errors.push(
+      `${rootRelative.length} page(s) use root-relative /_assets or /_islands 
URLs, ` +
+        `which resolve against the domain root once published: 
${preview(rootRelative)}`,
+    );
+  }
+  if (remoteFonts.length > 0) {
+    errors.push(
+      `${remoteFonts.length} file(s) load fonts from Google, which ASF privacy 
guidance ` +
+        `does not allow: ${preview(remoteFonts)}`,
+    );
+  }
+  if (!servedFromRoot && usingBasePath === 0) {
+    errors.push(`no page references "${basePath}/_assets/" -- basePath is not 
the published path.`);
+  }
+  return errors;
+}
+
+export async function* filesUnder(dir, extensions) {
+  for (const entry of await readdir(dir, { withFileTypes: true })) {
+    const path = join(dir, entry.name);
+    if (entry.isDirectory()) yield* filesUnder(path, extensions);
+    else if (extensions.some((extension) => entry.name.endsWith(extension))) 
yield path;
+  }
+}
diff --git a/ts-sdk/docs/scripts/strip-remote-fonts.mjs 
b/ts-sdk/docs/scripts/strip-remote-fonts.mjs
new file mode 100644
index 00000000000..49fec8318fb
--- /dev/null
+++ b/ts-sdk/docs/scripts/strip-remote-fonts.mjs
@@ -0,0 +1,43 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Remove the theme's Google Fonts tags; ASF privacy guidance allows them only
+// when ASF-hosted. There is no theme option: it validates the font families
+// against the live Google Fonts API and restores its own defaults on a 400, so
+// overriding them regresses in CI, where the build has network. The theme's 
CSS
+// already declares Georgia / system-ui fallbacks, so nothing is substituted.
+
+import { readFile, writeFile } from "node:fs/promises";
+
+import { HTML_ROOT, filesUnder, stripRemoteFontTags } from 
"./deployment-checks.mjs";
+
+let scanned = 0;
+let stripped = 0;
+
+for await (const file of filesUnder(HTML_ROOT, [".html"])) {
+  scanned += 1;
+  const html = await readFile(file, "utf8");
+  const cleaned = stripRemoteFontTags(html);
+  if (cleaned !== html) {
+    await writeFile(file, cleaned, "utf8");
+    stripped += 1;
+  }
+}
+
+console.log(`strip-remote-fonts: removed remote font tags from 
${stripped}/${scanned} page(s).`);
diff --git a/ts-sdk/docs/scripts/verify-deployment-paths.mjs 
b/ts-sdk/docs/scripts/verify-deployment-paths.mjs
new file mode 100644
index 00000000000..c874719fca4
--- /dev/null
+++ b/ts-sdk/docs/scripts/verify-deployment-paths.mjs
@@ -0,0 +1,71 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Fail the build on HTML that renders locally but breaks once published.
+
+import { readFile } from "node:fs/promises";
+
+import {
+  HTML_ROOT,
+  SDK_PACKAGE_JSON,
+  collectErrors,
+  filesUnder,
+  hasRemoteFonts,
+  inspectPage,
+  resolveBasePath,
+} from "./deployment-checks.mjs";
+
+// The theme can inline a font host into a stylesheet or an island bundle as 
well as into the
+// page head, and those the stripper cannot repair -- so scan them too rather 
than promise a
+// font-free build that only holds for HTML.
+const ASSET_EXTENSIONS = [".css", ".js"];
+
+const { version } = JSON.parse(await readFile(SDK_PACKAGE_JSON, "utf8"));
+const basePath = resolveBasePath(version);
+
+const rootRelative = [];
+const remoteFonts = [];
+let scanned = 0;
+let usingBasePath = 0;
+
+for await (const file of filesUnder(HTML_ROOT, [".html", 
...ASSET_EXTENSIONS])) {
+  const name = file.slice(HTML_ROOT.length + 1);
+  const text = await readFile(file, "utf8");
+  if (!file.endsWith(".html")) {
+    if (hasRemoteFonts(text)) remoteFonts.push(name);
+    continue;
+  }
+  scanned += 1;
+  const result = inspectPage(text, basePath);
+  if (result.rootRelative) rootRelative.push(name);
+  if (result.remoteFonts) remoteFonts.push(name);
+  if (result.usesBasePath) usingBasePath += 1;
+}
+
+const errors = collectErrors({ basePath, scanned, rootRelative, remoteFonts, 
usingBasePath });
+
+if (errors.length > 0) {
+  console.error("verify-deployment-paths: FAILED");
+  for (const error of errors) console.error(`  - ${error}`);
+  process.exitCode = 1;
+} else {
+  console.log(
+    `verify-deployment-paths: ${scanned} page(s) OK (basePath "${basePath || 
"/"}", no remote fonts).`,
+  );
+}
diff --git a/ts-sdk/docs/tests/deployment-checks.test.mjs 
b/ts-sdk/docs/tests/deployment-checks.test.mjs
new file mode 100644
index 00000000000..df4bc9acd9d
--- /dev/null
+++ b/ts-sdk/docs/tests/deployment-checks.test.mjs
@@ -0,0 +1,161 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import assert from "node:assert/strict";
+import { describe, it } from "node:test";
+
+import {
+  collectErrors,
+  inspectPage,
+  normalizeBasePath,
+  resolveBasePath,
+  stripRemoteFontTags,
+} from "../scripts/deployment-checks.mjs";
+
+// Verbatim from a clean-jsdoc-theme build: three tags together, with the 
query separators
+// HTML-escaped the way the theme escapes the href it builds.
+const FONT_TAGS =
+  '<link rel="preconnect" href="https://fonts.googleapis.com"; />' +
+  '<link rel="preconnect" href="https://fonts.gstatic.com"; crossorigin />' +
+  '<link rel="stylesheet" 
href="https://fonts.googleapis.com/css2?family=Source+Serif+4:wght@400;500;600;700&amp;family=Roboto:wght@400;500;600;700&amp;display=swap";
 />';
+
+const BASE = "/docs/ts-sdk/0.1.0-beta1";
+const VERSION = "0.1.0-beta1";
+
+describe("normalizeBasePath", () => {
+  it("strips trailing slashes", () => {
+    assert.equal(normalizeBasePath(`${BASE}/`), BASE);
+    assert.equal(normalizeBasePath(BASE), BASE);
+  });
+
+  it("maps the root to an empty string", () => {
+    assert.equal(normalizeBasePath("/"), "");
+    assert.equal(normalizeBasePath("//"), "");
+  });
+});
+
+describe("resolveBasePath", () => {
+  it("defaults to the published prefix for the SDK version", () => {
+    assert.equal(resolveBasePath(VERSION, undefined), BASE);
+  });
+
+  it("honours an override, normalized", () => {
+    assert.equal(resolveBasePath(VERSION, "/preview/"), "/preview");
+    assert.equal(resolveBasePath(VERSION, "/"), "");
+  });
+
+  // A blank env var is how the override leaks in unintentionally, and 
treating it as the
+  // root would both emit root-relative HTML and make the checks skip 
themselves.
+  it("treats a blank override as unset", () => {
+    assert.equal(resolveBasePath(VERSION, ""), BASE);
+    assert.equal(resolveBasePath(VERSION, "   "), BASE);
+  });
+});
+
+describe("stripRemoteFontTags", () => {
+  it("removes preconnect and stylesheet tags", () => {
+    assert.equal(stripRemoteFontTags(`<head>${FONT_TAGS}</head>`), 
"<head></head>");
+  });
+
+  it("keeps local stylesheets", () => {
+    const local = `<link rel="stylesheet" href="${BASE}/_assets/styles.css" 
/>`;
+    assert.equal(stripRemoteFontTags(`<head>${FONT_TAGS}${local}</head>`), 
`<head>${local}</head>`);
+  });
+
+  it("is a no-op on already-clean HTML", () => {
+    const html = "<head><title>x</title></head>";
+    assert.equal(stripRemoteFontTags(html), html);
+  });
+});
+
+describe("inspectPage", () => {
+  it("flags root-relative asset and island URLs", () => {
+    assert.equal(inspectPage('<link href="/_assets/styles.css" />', 
BASE).rootRelative, true);
+    assert.equal(inspectPage('<script src="/_islands/cmdk.js">', 
BASE).rootRelative, true);
+  });
+
+  it("accepts prefixed URLs", () => {
+    const page = inspectPage(`<link href="${BASE}/_assets/styles.css" />`, 
BASE);
+    assert.equal(page.rootRelative, false);
+    assert.equal(page.usesBasePath, true);
+  });
+
+  it("does not credit a basePath from another version", () => {
+    const page = inspectPage('<link 
href="/docs/ts-sdk/9.9.9/_assets/styles.css" />', BASE);
+    assert.equal(page.rootRelative, false);
+    assert.equal(page.usesBasePath, false);
+  });
+
+  it("never credits a basePath when served from the root", () => {
+    assert.equal(inspectPage('<link href="/_assets/styles.css" />', 
"").usesBasePath, false);
+  });
+
+  it("detects both font hosts", () => {
+    assert.equal(inspectPage(FONT_TAGS, BASE).remoteFonts, true);
+    assert.equal(inspectPage('<link href="https://fonts.gstatic.com/x"; />', 
BASE).remoteFonts, true);
+    assert.equal(inspectPage("<head></head>", BASE).remoteFonts, false);
+  });
+});
+
+describe("collectErrors", () => {
+  const clean = {
+    basePath: BASE,
+    scanned: 36,
+    rootRelative: [],
+    remoteFonts: [],
+    usingBasePath: 36,
+  };
+
+  it("passes a correct build", () => {
+    assert.deepEqual(collectErrors(clean), []);
+  });
+
+  it("reports an empty build before anything else", () => {
+    const errors = collectErrors({ ...clean, scanned: 0, usingBasePath: 0 });
+    assert.equal(errors.length, 1);
+    assert.match(errors[0], /did the TypeDoc build run/);
+  });
+
+  it("reports every failure of the unfixed build at once", () => {
+    const errors = collectErrors({
+      ...clean,
+      rootRelative: ["index.html"],
+      remoteFonts: ["index.html"],
+      usingBasePath: 0,
+    });
+    assert.equal(errors.length, 3);
+    assert.match(errors[0], /root-relative/);
+    assert.match(errors[1], /ASF privacy guidance/);
+    assert.match(errors[2], /basePath is not the published path/);
+  });
+
+  it("truncates long file lists", () => {
+    const files = Array.from({ length: 36 }, (_, i) => `page-${i}.html`);
+    const [error] = collectErrors({ ...clean, remoteFonts: files });
+    assert.match(error, /^36 file\(s\) load fonts/);
+    assert.match(error, /page-4\.html, \.\.\.$/);
+    assert.equal(error.includes("page-5.html"), false);
+  });
+
+  it("skips the prefix checks when served from the root", () => {
+    const rooted = { ...clean, basePath: "", rootRelative: ["index.html"], 
usingBasePath: 0 };
+    assert.deepEqual(collectErrors(rooted), []);
+    assert.equal(collectErrors({ ...rooted, remoteFonts: ["index.html"] 
}).length, 1);
+  });
+});
diff --git a/ts-sdk/docs/typedoc.config.mjs b/ts-sdk/docs/typedoc.config.mjs
new file mode 100644
index 00000000000..e9955586fb7
--- /dev/null
+++ b/ts-sdk/docs/typedoc.config.mjs
@@ -0,0 +1,67 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// clean-jsdoc-theme defaults basePath to "/", which emits every asset and link
+// root-relative and leaves the published site with no CSS.
+//
+// The prefix is the versioned one, not /docs/ts-sdk/stable: publish-docs-to-s3
+// syncs one build to both, so only one can own the assets. Pinning to the
+// version keeps archived releases self-contained, since a later release
+// replaces stable/ (sync --delete) without touching assets they still cite.
+
+import { readFileSync } from "node:fs";
+
+import { resolveBasePath, SDK_PACKAGE_JSON } from 
"./scripts/deployment-checks.mjs";
+
+const { version } = JSON.parse(readFileSync(SDK_PACKAGE_JSON, "utf8"));
+
+export default {
+  plugin: ["@clean-jsdoc-theme/typedoc"],
+  outputs: [
+    {
+      name: "clean-jsdoc-theme",
+      path: "_build/html",
+    },
+  ],
+  entryPoints: ["../api-docs/*.ts"],
+  tsconfig: "./tsconfig.json",
+  name: "Apache Airflow TypeScript SDK",
+  readme: ".typedoc/readme.md",
+  excludeInternal: true,
+  excludePrivate: true,
+  excludeExternals: true,
+  cleanJsdocTheme: {
+    siteName: "Apache Airflow TypeScript SDK",
+    menu: [
+      {
+        title: "Airflow Docs",
+        link: "https://airflow.apache.org/docs/";,
+        target: "_blank",
+      },
+      {
+        title: "GitHub",
+        link: "https://github.com/apache/airflow/tree/main/ts-sdk";,
+        target: "_blank",
+      },
+    ],
+    footer:
+      "Apache Airflow, Apache, Airflow, the Apache feather logo, and the 
Apache Airflow logo are either registered trademarks or trademarks of The 
Apache Software Foundation.",
+    basePath: resolveBasePath(version),
+  },
+};
diff --git a/ts-sdk/docs/typedoc.json b/ts-sdk/docs/typedoc.json
deleted file mode 100644
index feed7d21ac0..00000000000
--- a/ts-sdk/docs/typedoc.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "$schema": "https://typedoc.org/schema.json";,
-  "plugin": ["@clean-jsdoc-theme/typedoc"],
-  "outputs": [{ "name": "clean-jsdoc-theme", "path": "_build/html" }],
-  "entryPoints": ["../api-docs/*.ts"],
-  "tsconfig": "./tsconfig.json",
-  "name": "Apache Airflow TypeScript SDK",
-  "readme": ".typedoc/readme.md",
-  "excludeInternal": true,
-  "excludePrivate": true,
-  "excludeExternals": true,
-  "cleanJsdocTheme": {
-    "siteName": "Apache Airflow TypeScript SDK",
-    "menu": [
-      { "title": "Airflow Docs", "link": "https://airflow.apache.org/docs/";, 
"target": "_blank" },
-      {
-        "title": "GitHub",
-        "link": "https://github.com/apache/airflow/tree/main/ts-sdk";,
-        "target": "_blank"
-      }
-    ],
-    "footer": "Apache Airflow, Apache, Airflow, the Apache feather logo, and 
the Apache Airflow logo are either registered trademarks or trademarks of The 
Apache Software Foundation."
-  }
-}

Reply via email to