From f8811c9f1ceabd9b7136f37ea8db3af20bfee5da Mon Sep 17 00:00:00 2001 From: Nathan Flurry Date: Mon, 31 Aug 2026 01:05:09 -0700 Subject: [PATCH] fix(docs): keep embedded examples with product docs --- .github/actions/sync-docs/README.md | 6 ++-- .github/actions/sync-docs/action.yml | 30 +++++++++++++++++ src/metadata/docs-index.ts | 5 ++- src/pages/dynamic-apps.astro | 50 +++++----------------------- src/sitemap/docs-sources.node.ts | 7 ++-- src/sitemap/docs-sources.ts | 3 -- src/sitemap/product-metadata.ts | 12 ------- 7 files changed, 47 insertions(+), 66 deletions(-) diff --git a/.github/actions/sync-docs/README.md b/.github/actions/sync-docs/README.md index 090c0f41..411a01b5 100644 --- a/.github/actions/sync-docs/README.md +++ b/.github/actions/sync-docs/README.md @@ -37,8 +37,9 @@ is the intended shape; `GITHUB_TOKEN` cannot reach another repository. ## What it does 1. Validates the bundle — `docs/content/` exists, `docs/sidebar.json` parses, - at least one `.mdx`. Fails on the product repo's PR rather than breaking the - website build. + at least one `.mdx`, and every `` target is shipped by the same + product bundle. Fails on the product repo rather than breaking the website + build. 2. Copies `docs/` to `vendor//docs/` in the website, replacing it so deleted pages actually disappear. Repository instruction files are excluded, and broken symlinks fail the sync before they can poison the website checkout. @@ -53,4 +54,5 @@ is the intended shape; `GITHUB_TOKEN` cannot reach another repository. | `token` | — | Required. Write access to rivet-website. | | `source` | `docs` | Bundle path in this repo. | | `website-repo` | `rivet-dev/website` | Override for forks. | +| `extra-paths` | — | Repo-root-relative paths required by the docs, such as `examples`. | | `auto-merge` | `true` | Set `false` to review each sync. | diff --git a/.github/actions/sync-docs/action.yml b/.github/actions/sync-docs/action.yml index c4c7defe..fc94fadf 100644 --- a/.github/actions/sync-docs/action.yml +++ b/.github/actions/sync-docs/action.yml @@ -82,6 +82,36 @@ runs: # archive and making every product sync fail before the action can run. find "$root" \( -name AGENTS.md -o -name CLAUDE.md \) -delete + python3 - "$root" <<'PY' + import pathlib + import re + import sys + + root = pathlib.Path(sys.argv[1]).resolve() + pattern = re.compile(r']*\bfile=(["\'])(.*?)\1', re.DOTALL) + missing = [] + count = 0 + for page in sorted((root / "docs" / "content").rglob("*.mdx")): + body = page.read_text() + for match in pattern.finditer(body): + count += 1 + reference = match.group(2) + target = (root / reference).resolve() + try: + target.relative_to(root) + except ValueError: + missing.append(f"{page.relative_to(root)}: path escapes bundle: {reference}") + continue + if not target.is_file(): + missing.append(f"{page.relative_to(root)}: missing {reference}") + + if missing: + print("::error::docs bundle contains unresolved CodeSnippet files") + print("\n".join(f" {entry}" for entry in missing)) + raise SystemExit(1) + print(f"snippet bundle ok: {count} references") + PY + broken_links=$(find "$root" -xtype l -print) if [ -n "$broken_links" ]; then echo "::error::docs bundle contains broken symlinks" diff --git a/src/metadata/docs-index.ts b/src/metadata/docs-index.ts index 7ddb9fac..cbd5fc6f 100644 --- a/src/metadata/docs-index.ts +++ b/src/metadata/docs-index.ts @@ -89,13 +89,12 @@ export function listDocPages(): DocPage[] { /** Repo root a product's `examples/...` snippet paths resolve against. */ export function snippetRootFor(product: string): string | undefined { - return docsRoot(DOCS_SOURCES[product]?.snippetFrom ?? product); + return docsRoot(product); } /** `github.com/rivet-dev/` that owns a product's examples. */ export function examplesRepoFor(product: string): string { - const source = DOCS_SOURCES[product]; - const owner = getProductMetadata(source?.snippetFrom ?? product); + const owner = getProductMetadata(product); if (!owner) throw new Error(`Unknown product: ${product}`); return `rivet-dev/${owner.repo}`; } diff --git a/src/pages/dynamic-apps.astro b/src/pages/dynamic-apps.astro index 92e0a0a0..e2f4f5bc 100644 --- a/src/pages/dynamic-apps.astro +++ b/src/pages/dynamic-apps.astro @@ -49,17 +49,14 @@ import { SITE_SECTION_CLASS, } from '@/components/marketing/layout'; -const APPS_EXAMPLE_ROOT = 'https://github.com/rivet-dev/agentos/blob/main/examples'; +const APPS_EXAMPLE_ROOT = 'https://github.com/rivet-dev/dynamic-apps/blob/main/examples'; const AI_BUILDER_EXAMPLE_URL = - 'https://github.com/rivet-dev/agentos/tree/main/examples/apps-ai-builder'; -const STATIC_SITE_EXAMPLE_URL = - 'https://github.com/rivet-dev/agentos/tree/main/examples/apps-static-website'; + 'https://github.com/rivet-dev/dynamic-apps/tree/main/examples/apps-ai-builder'; const INSPECTOR_IMAGE = 'https://assets.rivet.dev/repo/website/src/components/marketing/images/screenshots/rivet-actor-inspector.png'; -const INSTALL_COMMAND = 'npm add @rivet-dev/agentos @rivet-dev/agentos-apps'; +const INSTALL_COMMAND = 'npm add @rivet-dev/dynamic-apps @hono/node-server hono'; -// Dynamic Apps snippets live in the agentOS repo (`snippetFrom: "agentos"`). -const agentosRoot = requireDocsRoot('agentos'); +const dynamicAppsRoot = requireDocsRoot('dynamic-apps'); const dynamicAppsProduct = getProduct('dynamic-apps'); if (!dynamicAppsProduct) { @@ -90,7 +87,7 @@ const appsTabs = await Promise.all([ sourceTab({ key: 'deploy', label: 'deploy.ts', - root: agentosRoot, + root: dynamicAppsRoot, file: 'examples/apps-hello-world/src/deploy.ts', documentationUrl: '/dynamic-apps/docs/deploy', sourceUrl: `${APPS_EXAMPLE_ROOT}/apps-hello-world/src/deploy.ts`, @@ -98,27 +95,11 @@ const appsTabs = await Promise.all([ sourceTab({ key: 'server', label: 'server.ts', - root: agentosRoot, + root: dynamicAppsRoot, file: 'examples/apps-hello-world/src/server.ts', documentationUrl: '/dynamic-apps/docs/routing', sourceUrl: `${APPS_EXAMPLE_ROOT}/apps-hello-world/src/server.ts`, }), - sourceTab({ - key: 'actors', - label: 'actors.ts', - root: agentosRoot, - file: 'examples/apps-hello-world/src/actors.ts', - documentationUrl: '/dynamic-apps/docs/quickstart', - sourceUrl: `${APPS_EXAMPLE_ROOT}/apps-hello-world/src/actors.ts`, - }), - sourceTab({ - key: 'generated-app', - label: 'app/src/index.ts', - root: agentosRoot, - file: 'examples/apps-sqlite/fixtures/app/src/index.ts', - documentationUrl: '/dynamic-apps/docs/state-and-data', - sourceUrl: `${APPS_EXAMPLE_ROOT}/apps-sqlite/fixtures/app/src/index.ts`, - }), ]); const appsHighlightedCode = await highlightCodeHtml(APPS_CODE, 'typescript'); @@ -154,26 +135,11 @@ const appCapabilities = [ body: 'Store durable relational data in an actor-owned SQLite database.', href: '/dynamic-apps/docs/state-and-data/', }, - { - title: 'Workflows & queues', - body: 'Run durable jobs that sleep, retry, and resume, plus ordered queue processing.', - href: '/dynamic-apps/docs/background-work/', - }, - { - title: 'Crons & schedules', - body: 'Schedule recurring work that runs even while the app is idle.', - href: '/dynamic-apps/docs/background-work/', - }, { title: 'Multiplayer', body: 'Share realtime state between every user connected to an app.', href: '/dynamic-apps/docs/realtime/', }, - { - title: 'Static sites', - body: 'Serve a directory with an index.html directly, no server code required.', - href: STATIC_SITE_EXAMPLE_URL, - }, ]; const observabilityCapabilities = [ @@ -504,7 +470,7 @@ const compositionLayers = [ const feedback = element.querySelector('[data-dynamic-apps-copy-feedback]'); element.addEventListener('click', async () => { try { - await navigator.clipboard.writeText('npm add @rivet-dev/agentos @rivet-dev/agentos-apps'); + await navigator.clipboard.writeText('npm add @rivet-dev/dynamic-apps @hono/node-server hono'); element.setAttribute('aria-label', 'Install command copied'); if (feedback instanceof HTMLElement) { feedback.textContent = 'Copied'; @@ -512,7 +478,7 @@ const compositionLayers = [ feedback.classList.add('opacity-100'); } window.setTimeout(() => { - element.setAttribute('aria-label', 'Copy npm add @rivet-dev/agentos @rivet-dev/agentos-apps'); + element.setAttribute('aria-label', 'Copy npm add @rivet-dev/dynamic-apps @hono/node-server hono'); if (feedback instanceof HTMLElement) { feedback.textContent = 'Copy'; feedback.classList.remove('opacity-100'); diff --git a/src/sitemap/docs-sources.node.ts b/src/sitemap/docs-sources.node.ts index 97b48cd4..2c8f3817 100644 --- a/src/sitemap/docs-sources.node.ts +++ b/src/sitemap/docs-sources.node.ts @@ -47,12 +47,11 @@ function rootFromContentLink(productId: string): string | undefined { } /** - * Where a product's snippets come from. Usually its own repo, but a product can - * be split out before its `examples/` tree is; see `snippetFrom`. + * Where a product's snippets come from. A product owns both its docs and the + * examples those docs embed. */ function snippetRoot(productId: string): string | undefined { - const from = DOCS_SOURCES[productId]?.snippetFrom; - return docsRoot(from ?? productId); + return docsRoot(productId); } /** Absolute path to a product's repo root, or undefined if none resolves. */ diff --git a/src/sitemap/docs-sources.ts b/src/sitemap/docs-sources.ts index 9656a969..2785409c 100644 --- a/src/sitemap/docs-sources.ts +++ b/src/sitemap/docs-sources.ts @@ -28,8 +28,6 @@ export interface DocsSource { * Takes precedence over `repo`. */ localBundle?: string; - /** Product whose repo owns this one's snippets, if not its own. */ - snippetFrom?: string; } export const DOCS_SOURCES: Record = Object.fromEntries( @@ -38,7 +36,6 @@ export const DOCS_SOURCES: Record = Object.fromEntries( { repo: product.repo, localBundle: product.localBundle, - snippetFrom: product.snippetFrom, }, ]), ); diff --git a/src/sitemap/product-metadata.ts b/src/sitemap/product-metadata.ts index 71d3aaa1..8c646592 100644 --- a/src/sitemap/product-metadata.ts +++ b/src/sitemap/product-metadata.ts @@ -68,16 +68,6 @@ export interface ProductMetadata { * these docs out later is a delete plus a checkout. */ localBundle?: string; - /** - * Product whose repo owns this one's code snippets. - * - * Snippets resolve from the repo that owns the docs, but a product can be - * split out before its `examples/` tree is. Workflows docs still reference - * `examples/docs/actors-workflows/**` in the Rivet repo, and Dynamic Apps - * references `examples/apps-*` in the agentOS repo. Drop this once the - * examples move with the docs. - */ - snippetFrom?: string; } /** Display order across the whole site. */ @@ -115,7 +105,6 @@ export const PRODUCTS: ProductMetadata[] = [ repo: "workflows", color: "#6A4C93", contrast: 5.95, - snippetFrom: "actors", optionalTabs: [], hiddenTabs: ["use-cases"], }, @@ -129,7 +118,6 @@ export const PRODUCTS: ProductMetadata[] = [ repo: "dynamic-apps", color: "#2F6B4B", contrast: 5.49, - snippetFrom: "agentos", optionalTabs: [], hiddenTabs: ["use-cases"], },