Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/actions/sync-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<CodeSnippet>` target is shipped by the same
product bundle. Fails on the product repo rather than breaking the website
build.
2. Copies `docs/` to `vendor/<product>/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.
Expand All @@ -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. |
30 changes: 30 additions & 0 deletions .github/actions/sync-docs/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'<CodeSnippet\b[^>]*\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"
Expand Down
5 changes: 2 additions & 3 deletions src/metadata/docs-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/<repo>` 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}`;
}
Expand Down
50 changes: 8 additions & 42 deletions src/pages/dynamic-apps.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -90,35 +87,19 @@ 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`,
}),
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');
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -504,15 +470,15 @@ 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';
feedback.classList.remove('opacity-0');
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');
Expand Down
7 changes: 3 additions & 4 deletions src/sitemap/docs-sources.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
3 changes: 0 additions & 3 deletions src/sitemap/docs-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, DocsSource> = Object.fromEntries(
Expand All @@ -38,7 +36,6 @@ export const DOCS_SOURCES: Record<string, DocsSource> = Object.fromEntries(
{
repo: product.repo,
localBundle: product.localBundle,
snippetFrom: product.snippetFrom,
},
]),
);
Expand Down
12 changes: 0 additions & 12 deletions src/sitemap/product-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -115,7 +105,6 @@ export const PRODUCTS: ProductMetadata[] = [
repo: "workflows",
color: "#6A4C93",
contrast: 5.95,
snippetFrom: "actors",
optionalTabs: [],
hiddenTabs: ["use-cases"],
},
Expand All @@ -129,7 +118,6 @@ export const PRODUCTS: ProductMetadata[] = [
repo: "dynamic-apps",
color: "#2F6B4B",
contrast: 5.49,
snippetFrom: "agentos",
optionalTabs: [],
hiddenTabs: ["use-cases"],
},
Expand Down
Loading