A unified documentation portal — wraps independently-maintained doc apps into a single deployable with a persistent branded chrome, and can be embedded as a web fragment inside a host app.
knowledge-base is a build-time aggregator for static documentation sites. At
build time it:
- Obtains each registered app's built output (
dist/) — from a GitHub Release artifact (dist.tar.gz), a local repo, or a prebuilt tarball/dir. - Rewrites every page's URLs to absolute
/knowledge-base/{slug}/…paths and, in standalone mode, injects a persistent top chrome (nav + app switcher). - Generates a catalog landing page listing all registered apps.
- Produces a single
dist/served by nginx in Docker, or embedded into a host app as a web fragment.
Each app keeps its own sidebar, routing, and internal navigation.
Browser ──► host origin ──► /knowledge-base/ (landing catalog)
/knowledge-base/{slug}/… (each doc app)
/__wf/knowledge-base/… (fragment assets)
│
▼
nginx (Docker) ─or─ web-fragments gateway (embedded)
│
▼
dist/ (static)
When embedded, a host app's web-fragments
gateway proxies the /knowledge-base/* routes onto the host's single origin and
reframes the markup into a shadow root — no full page reload between pages.
Prerequisites: Node.js ≥ 24. For the GitHub-fetch build, gh CLI
authenticated (or GITHUB_TOKEN set).
npm install
# Hermetic build from the vendored docs-example fixture (no network/token):
npm run build:headless
# E2E tests (Playwright — auto-starts its own servers):
npm test| Command | Source of apps |
|---|---|
npm run build |
Fetch GitHub Release artifacts (needs GITHUB_TOKEN/gh) |
npm run build:headless |
Same fetch, headless/web-fragment output |
npm run build:local |
Build each app from a local checkout (localPath) |
npm run build:local:headless |
Local + headless |
--headless (or MP_HEADLESS=true) produces fragment-ready output: no chrome
bar, data-mp-headless="true" on <html>, shadow-DOM compat styles.
Orchestrator: scripts/build-vite.js (flags: --local, --headless,
--path-prefix=).
Each entry registers one doc app. A slug is required; the source is one of
repo (+ optional version), localPath, prebuilt, or an iframe URL:
Teams that already host their docs elsewhere and can't yet produce a headless
package can be listed immediately with an iframe entry — no repo,
marketplace.json, or artifact needed. It renders as a full-viewport <iframe>
inside the marketplace chrome and shows an External badge in the catalogue.
{
"type": "iframe",
"url": "https://my-team.example.com/docs",
"slug": "my-team",
"name": "My Team Docs",
"description": "...",
"icon": "book-open",
"tags": ["my-team"],
"temporary": true // stopgap — migrate to a headless package when ready
}The external site must permit embedding (its CSP frame-ancestors /
X-Frame-Options must not block the marketplace origin). See issue #10.
The repo ships an apps.json that registers the vendored docs-example fixture
twice (user-guide, guide-mirror) so the build and tests are hermetic out of
the box. Replace it with your own apps for a real deployment.
E2E tests use Playwright. Everything is hermetic — built from
tests/fixtures/docs-example.dist.tar.gz (no network, token, or sibling repo).
| Command | Layer |
|---|---|
npm test |
Embedded harness (playwright.config.js). Starts the fragment server (:3000) and a minimal web-fragments host gateway (tests/host/server.mjs, :4201) that embeds the fragment. Covers shadow-DOM isolation, smooth no-reload SPA routing, cross-app navigation, asset 404s, and the fragment-history limitation. |
npx playwright test --config=playwright.config.ci.js |
Standalone layer. Hits the fragment server (tests/fragment-server.mjs, :3000) directly. Covers HTTP header safety (X-Frame-Options), the headless contract, CSS-link stability (web-fragments #297), and asset routing. |
tests/fragment-server.mjsservesdist/and mirrors the productionnginx.confrewrites (including/__wf/knowledge-base/* → /knowledge-base/*).astro previewis not used as the fragment endpoint: its ViteconfigurePreviewServerrewrite hook does not run for static output, so the/__wfasset route would 404.
Both layers run in CI (.github/workflows/ci.yml).
knowledge-base can run as a web fragment inside any host app that uses a
web-fragments gateway (Express/Node, Cloudflare, Angular SSR, …).
tests/host/server.mjs is a minimal, runnable reference host.
Fragment server (this repo): serve the built dist/ mirroring the nginx
rewrites — e.g. node tests/fragment-server.mjs (port 3000), or nginx in Docker.
Host gateway registration:
import { FragmentGateway } from 'web-fragments/gateway';
import { getNodeMiddleware } from 'web-fragments/gateway/node';
const gateway = new FragmentGateway();
gateway.registerFragment({
fragmentId: 'knowledge-base',
endpoint: 'http://localhost:3000', // the fragment server
piercing: false,
routePatterns: [
'/knowledge-base/:_*', // landing + sub-app pages + assets
'/__wf/knowledge-base/:_*', // fragment asset prefix
],
});
app.use(getNodeMiddleware(gateway)); // before host static/catch-all routesHost page:
<script type="importmap">{ "imports": { "web-fragments": "/_wf/elements.js" } }</script>
<web-fragment fragment-id="knowledge-base" src="/knowledge-base/"></web-fragment>
<script type="module">
import { initializeWebFragments } from 'web-fragments';
initializeWebFragments();
</script>Fragment-internal routes are not mirrored to the host's top-window history and are not address-bar deep-linkable — design host-level routing if you need that.
Apps must comply with the marketplace contract before they can be registered:
| Document | Description |
|---|---|
contract/schema.json |
JSON Schema for marketplace.json |
contract/HEADLESS_RULES.md |
Headless HTML, relative paths, data-mp-headless |
contract/STYLE_GUIDE.md |
Design tokens (--color-kb-*), typography, dark mode |
-
marketplace.jsonin repo root, valid againstcontract/schema.json -
npm run build -- --headlessproduces a headlessdist/ -
data-mp-headless="true"on<html>in headless output - No fixed site-level header in headless output
- All asset paths relative (no leading
/) - GitHub Release tagged
v*with adist.tar.gzasset
Doc repos can enforce the contract in their own CI:
# .github/workflows/validate.yml (in your doc repo)
on: [push, pull_request]
jobs:
validate:
uses: AbsaOSS/knowledge-base/.github/workflows/validate-doc-app.yml@master# .github/workflows/release.yml (in your doc repo)
on:
push:
tags: ['v*']
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with: { node-version: '24', cache: npm }
- run: npm ci
- run: npm run build -- --headless
- run: tar -czf dist.tar.gz dist/ marketplace.json
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
with:
files: dist.tar.gzBuilt as a Docker image (nginx serving static files).
docker build -t knowledge-base .
docker run -p 8080:8080 knowledge-base # http://localhost:8080/knowledge-base/| Variable | Description |
|---|---|
AWS_REGION |
AWS region for ECR + ECS |
ECR_REPOSITORY |
ECR repository name |
ECS_CLUSTER / ECS_SERVICE |
ECS cluster / service name |
Provide AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY (or an OIDC role) with ECR
push + ECS deploy permissions via repository secrets.
knowledge-base/
├── apps.json ← Registry of doc apps
├── astro.config.mjs ← Astro SSG config (base /knowledge-base)
├── src/
│ ├── pages/
│ │ ├── index.astro ← Landing catalog
│ │ └── [...path].astro ← Catch-all: renders every sub-app page
│ ├── layouts/Base.astro
│ ├── components/ ← AppCard, AppIcon, Chrome
│ ├── templates/chrome.js ← Chrome HTML + client SPA router
│ ├── styles/marketplace.css ← Design tokens + Tailwind
│ └── utils/
│ ├── apps.js ← getAppPages() page enumeration
│ └── transform.js ← URL rewriting + chrome/headless injection
├── scripts/
│ ├── build-vite.js ← Build orchestrator
│ ├── fetch-apps.js ← GitHub Release download + extract
│ └── setup-test-apps.mjs ← Generates the hermetic test apps.json
├── tests/
│ ├── web-fragment.spec.js ← Embedded harness suite
│ ├── standalone.spec.js ← Standalone fragment-server suite
│ ├── build-integrity.spec.js
│ ├── host/server.mjs ← Reference web-fragments host (gateway)
│ ├── fragment-server.mjs ← nginx-mirroring static server
│ ├── support/fragment.js ← Shadow-DOM test helpers
│ └── fixtures/ ← Vendored docs-example dist.tar.gz
├── contract/ ← marketplace.json schema + rules + style guide
├── .github/workflows/ ← ci.yml, validate-doc-app.yml
├── Dockerfile
└── nginx.conf
See CONTRIBUTING.md and SECURITY.md.
[ { "slug": "my-app", "name": "My App Documentation", "description": "What this app documents.", "icon": "book-open", "tags": ["guide"], // pick ONE source: "repo": "AbsaOSS/my-docs", "version": "latest", // GitHub Release artifact // "localPath": "../my-docs", // build from local checkout // "prebuilt": "tests/fixtures/my-docs.dist.tar.gz" // prebuilt tarball or dist dir } ]