From 9ced0469bbaa331dc5f0e4b389159a99a6961b2e Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 5 Nov 2026 23:17:30 -0600 Subject: [PATCH] feat(site): serve the V2 docs from services/site with SolidStart (#62229) Co-authored-by: Dax Raad --- .gitattributes | 1 - .github/workflows/check-docs.yml | 42 ++++ .github/workflows/deploy-www.yml | 37 --- .opencode/skills/app-e2e-tests/SKILL.md | 2 +- CONTRIBUTING.md | 2 +- bun.lock | 57 ++--- package.json | 1 - packages/cli/script/schema.ts | 2 +- packages/core/src/plugin/skill/opencode.md | 2 +- script/generate.ts | 2 +- services/README.md | 3 +- services/site/.gitignore | 5 +- services/site/AGENTS.md | 3 +- services/site/README.md | 5 +- services/site/cloudflare.config.ts | 18 +- services/site/package.json | 23 +- services/site/public/_headers | 3 + .../public/v2}/assets/docs/cli/settings.png | Bin .../public/v2}/assets/logo-dark.svg | 0 .../public/v2}/assets/logo-light.svg | 0 .../public => site/public/v2}/favicon.svg | 0 services/site/script/build.ts | 7 +- services/site/script/check-worker-start.ts | 14 +- services/site/script/docs-file-icons.ts | 2 +- services/site/script/opentui-mono.py | 6 +- .../src/component/site/docs-code-gallery.tsx | 2 +- services/site/src/docs/AGENTS.md | 42 ++++ .../docs/assets/fonts/OpenTUIMono-Bold.woff2 | Bin .../assets/fonts/OpenTUIMono-BoldItalic.woff2 | Bin .../assets/fonts/OpenTUIMono-Italic.woff2 | Bin .../assets/fonts/OpenTUIMono-Regular.woff2 | Bin services/site/src/docs/client.ts | 180 ++++++++++++++ services/site/src/docs/components/Callout.tsx | 12 + services/site/src/docs/components/Card.tsx | 12 + .../site/src/docs/components/CardGroup.tsx | 9 + .../site/src/docs/components/CodeBlock.tsx | 13 + .../site/src/docs/components/CodeTabs.tsx | 40 ++++ .../site/src/docs/components/DocsFooter.tsx | 12 + .../site/src/docs/components/DocsHeader.tsx | 97 ++++++++ .../site/src/docs/components/DocsSidebar.tsx | 29 +++ .../docs/components/DocsTableOfContents.tsx | 22 ++ .../src/docs/components/OpenApiReference.tsx | 224 ++++++++++++++++++ .../src/docs/components/OpenApiSchema.tsx | 114 +++++++++ .../site/src/docs/components/PlanTabs.tsx | 47 ++++ services/site/src/docs/components/Slot.tsx | 15 ++ services/site/src/docs/config.ts | 3 + services/site/src/docs/content.ts | 20 ++ .../{www => site}/src/docs/content/agents.mdx | 0 .../src/docs/content/attachments.mdx | 0 .../src/docs/content/build/client/effect.mdx | 0 .../src/docs/content/build/client/index.mdx | 0 .../src/docs/content/build/index.mdx | 0 .../src/docs/content/build/plugins/cli.mdx | 0 .../src/docs/content/build/plugins/effect.mdx | 0 .../docs/content/build/plugins/effect/rpc.mdx | 0 .../docs/content/build/plugins/gui/index.mdx | 0 .../src/docs/content/build/plugins/index.mdx | 0 .../docs/content/build/plugins/migrate-v1.mdx | 0 .../src/docs/content/build/plugins/rpc.mdx | 0 .../src/docs/content/build/plugins/server.mdx | 0 .../src/docs/content/build/sdk/cloudflare.mdx | 0 .../src/docs/content/build/sdk/effect.mdx | 0 .../src/docs/content/build/sdk/index.mdx | 0 .../src/docs/content/claude-agent.mdx | 0 .../src/docs/content/cli/acp.mdx | 0 .../src/docs/content/cli/commands.mdx | 0 .../src/docs/content/cli/config.mdx | 0 .../src/docs/content/cli/index.mdx | 0 .../src/docs/content/cli/keybinds.mdx | 0 .../src/docs/content/cli/plugins.mdx | 0 .../src/docs/content/cli/providers.mdx | 0 .../src/docs/content/cli/theme.mdx | 0 .../src/docs/content/cli/tui.mdx | 0 .../src/docs/content/cli/web.mdx | 0 .../src/docs/content/commands.mdx | 0 .../src/docs/content/compaction.mdx | 0 .../{www => site}/src/docs/content/config.mdx | 0 .../src/docs/content/console/api/index.mdx | 0 .../docs/content/console/api/inference.mdx | 0 .../docs/content/console/api/providers.mdx | 0 .../src/docs/content/console/billing.mdx | 0 .../src/docs/content/console/budgets.mdx | 0 .../src/docs/content/console/go.mdx | 0 .../content/console/harnesses/claude-code.mdx | 0 .../docs/content/console/harnesses/codex.mdx | 0 .../docs/content/console/harnesses/hermes.mdx | 0 .../docs/content/console/harnesses/index.mdx | 0 .../src/docs/content/console/harnesses/pi.mdx | 0 .../content/console/harnesses/raycast.mdx | 0 .../src/docs/content/console/index.mdx | 0 .../src/docs/content/console/members.mdx | 0 .../src/docs/content/console/models.mdx | 0 .../src/docs/content/console/providers.mdx | 0 .../src/docs/content/console/scim.mdx | 0 .../src/docs/content/console/sso.mdx | 0 .../src/docs/content/console/websearch.mdx | 0 .../src/docs/content/console/workspaces.mdx | 0 .../src/docs/content/formatters.mdx | 0 .../{www => site}/src/docs/content/index.mdx | 0 .../src/docs/content/instructions.mdx | 0 .../src/docs/content/mcp-servers.mdx | 0 .../src/docs/content/migrate-v1.mdx | 0 .../{www => site}/src/docs/content/models.mdx | 0 .../src/docs/content/network.mdx | 0 .../src/docs/content/permissions.mdx | 0 .../src/docs/content/plugins.mdx | 0 .../src/docs/content/policies.mdx | 0 .../src/docs/content/providers.mdx | 0 .../src/docs/content/references.mdx | 0 .../{www => site}/src/docs/content/skills.mdx | 0 .../src/docs/content/snapshots.mdx | 0 .../{www => site}/src/docs/content/themes.mdx | 0 .../{www => site}/src/docs/content/tools.mdx | 0 .../src/docs/content/troubleshooting.mdx | 0 .../src/docs/content/warming.mdx | 0 .../src/docs/content/websearch.mdx | 0 services/site/src/docs/document.tsx | 54 +++++ services/site/src/docs/frontmatter.ts | 19 ++ services/site/src/docs/layouts/DocsLayout.tsx | 139 +++++++++++ services/{www => site}/src/docs/lib/llms.ts | 5 +- .../{www => site}/src/docs/lib/navigation.ts | 17 +- .../{www => site}/src/docs/lib/openapi.ts | 0 .../{www => site}/src/docs/lib/sitemap.ts | 0 services/site/src/docs/middleware.ts | 102 ++++++++ services/site/src/docs/modules.d.ts | 11 + services/site/src/docs/recma-solid.ts | 18 ++ services/site/src/docs/rehype-headings.ts | 47 ++++ services/site/src/docs/rehype-shiki.ts | 119 ++++++++++ .../src/docs/rehype-table-scroll.ts | 0 .../{www => site}/src/docs/remark-links.ts | 0 services/site/src/docs/remark-slots.ts | 32 +++ services/site/src/docs/render.tsx | 26 ++ services/site/src/docs/script/build.ts | 118 +++++++++ .../src/docs}/script/validate-links.ts | 8 +- .../{www => site}/src/docs/styles/global.css | 5 + services/site/src/docs/vite.ts | 83 +++++++ services/site/src/lib/docs.ts | 2 +- services/site/src/lib/security-headers.ts | 7 +- services/site/src/lib/sitemap.ts | 2 +- services/site/src/middleware.ts | 12 +- services/site/vite.config.ts | 2 + services/www/.gitignore | 6 - services/www/AGENTS.md | 19 -- services/www/README.md | 28 --- services/www/astro.config.ts | 57 ----- services/www/package.json | 31 --- services/www/script/generate-openapi.ts | 6 - services/www/script/pagefind.ts | 8 - services/www/script/prepare-cloudflare.ts | 21 -- services/www/src/content.config.ts | 26 -- services/www/src/docs/AGENTS.md | 27 --- .../www/src/docs/components/Callout.astro | 14 -- services/www/src/docs/components/Card.astro | 15 -- .../www/src/docs/components/CardGroup.astro | 9 - .../www/src/docs/components/CodeBlock.astro | 20 -- .../www/src/docs/components/CodeTabs.astro | 89 ------- .../www/src/docs/components/DocsFooter.astro | 31 --- .../www/src/docs/components/DocsHeader.astro | 130 ---------- .../www/src/docs/components/DocsPage.astro | 28 --- .../www/src/docs/components/DocsSidebar.astro | 27 --- .../docs/components/DocsTableOfContents.astro | 24 -- .../docs/components/OpenApiReference.astro | 151 ------------ .../src/docs/components/OpenApiSchema.astro | 69 ------ .../www/src/docs/components/PlanTabs.astro | 104 -------- .../www/src/docs/layouts/DocsLayout.astro | 137 ----------- .../www/src/pages/docs-index/[...slug].astro | 21 -- services/www/src/pages/docs/[...slug].astro | 26 -- services/www/src/pages/docs/api/index.astro | 17 -- .../src/pages/docs/console/inference.astro | 5 - services/www/src/pages/index.astro | 5 - services/www/src/pages/install.astro | 13 - services/www/src/pages/llms.txt.ts | 9 - services/www/src/pages/sitemap.xml.ts | 9 - services/www/tsconfig.json | 7 - services/www/wrangler.jsonc | 36 --- 175 files changed, 1844 insertions(+), 1339 deletions(-) create mode 100644 .github/workflows/check-docs.yml delete mode 100644 .github/workflows/deploy-www.yml rename services/{www/public => site/public/v2}/assets/docs/cli/settings.png (100%) rename services/{www/public => site/public/v2}/assets/logo-dark.svg (100%) rename services/{www/public => site/public/v2}/assets/logo-light.svg (100%) rename services/{www/public => site/public/v2}/favicon.svg (100%) create mode 100644 services/site/src/docs/AGENTS.md rename services/{www => site}/src/docs/assets/fonts/OpenTUIMono-Bold.woff2 (100%) rename services/{www => site}/src/docs/assets/fonts/OpenTUIMono-BoldItalic.woff2 (100%) rename services/{www => site}/src/docs/assets/fonts/OpenTUIMono-Italic.woff2 (100%) rename services/{www => site}/src/docs/assets/fonts/OpenTUIMono-Regular.woff2 (100%) create mode 100644 services/site/src/docs/client.ts create mode 100644 services/site/src/docs/components/Callout.tsx create mode 100644 services/site/src/docs/components/Card.tsx create mode 100644 services/site/src/docs/components/CardGroup.tsx create mode 100644 services/site/src/docs/components/CodeBlock.tsx create mode 100644 services/site/src/docs/components/CodeTabs.tsx create mode 100644 services/site/src/docs/components/DocsFooter.tsx create mode 100644 services/site/src/docs/components/DocsHeader.tsx create mode 100644 services/site/src/docs/components/DocsSidebar.tsx create mode 100644 services/site/src/docs/components/DocsTableOfContents.tsx create mode 100644 services/site/src/docs/components/OpenApiReference.tsx create mode 100644 services/site/src/docs/components/OpenApiSchema.tsx create mode 100644 services/site/src/docs/components/PlanTabs.tsx create mode 100644 services/site/src/docs/components/Slot.tsx create mode 100644 services/site/src/docs/config.ts create mode 100644 services/site/src/docs/content.ts rename services/{www => site}/src/docs/content/agents.mdx (100%) rename services/{www => site}/src/docs/content/attachments.mdx (100%) rename services/{www => site}/src/docs/content/build/client/effect.mdx (100%) rename services/{www => site}/src/docs/content/build/client/index.mdx (100%) rename services/{www => site}/src/docs/content/build/index.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/cli.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/effect.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/effect/rpc.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/gui/index.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/index.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/migrate-v1.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/rpc.mdx (100%) rename services/{www => site}/src/docs/content/build/plugins/server.mdx (100%) rename services/{www => site}/src/docs/content/build/sdk/cloudflare.mdx (100%) rename services/{www => site}/src/docs/content/build/sdk/effect.mdx (100%) rename services/{www => site}/src/docs/content/build/sdk/index.mdx (100%) rename services/{www => site}/src/docs/content/claude-agent.mdx (100%) rename services/{www => site}/src/docs/content/cli/acp.mdx (100%) rename services/{www => site}/src/docs/content/cli/commands.mdx (100%) rename services/{www => site}/src/docs/content/cli/config.mdx (100%) rename services/{www => site}/src/docs/content/cli/index.mdx (100%) rename services/{www => site}/src/docs/content/cli/keybinds.mdx (100%) rename services/{www => site}/src/docs/content/cli/plugins.mdx (100%) rename services/{www => site}/src/docs/content/cli/providers.mdx (100%) rename services/{www => site}/src/docs/content/cli/theme.mdx (100%) rename services/{www => site}/src/docs/content/cli/tui.mdx (100%) rename services/{www => site}/src/docs/content/cli/web.mdx (100%) rename services/{www => site}/src/docs/content/commands.mdx (100%) rename services/{www => site}/src/docs/content/compaction.mdx (100%) rename services/{www => site}/src/docs/content/config.mdx (100%) rename services/{www => site}/src/docs/content/console/api/index.mdx (100%) rename services/{www => site}/src/docs/content/console/api/inference.mdx (100%) rename services/{www => site}/src/docs/content/console/api/providers.mdx (100%) rename services/{www => site}/src/docs/content/console/billing.mdx (100%) rename services/{www => site}/src/docs/content/console/budgets.mdx (100%) rename services/{www => site}/src/docs/content/console/go.mdx (100%) rename services/{www => site}/src/docs/content/console/harnesses/claude-code.mdx (100%) rename services/{www => site}/src/docs/content/console/harnesses/codex.mdx (100%) rename services/{www => site}/src/docs/content/console/harnesses/hermes.mdx (100%) rename services/{www => site}/src/docs/content/console/harnesses/index.mdx (100%) rename services/{www => site}/src/docs/content/console/harnesses/pi.mdx (100%) rename services/{www => site}/src/docs/content/console/harnesses/raycast.mdx (100%) rename services/{www => site}/src/docs/content/console/index.mdx (100%) rename services/{www => site}/src/docs/content/console/members.mdx (100%) rename services/{www => site}/src/docs/content/console/models.mdx (100%) rename services/{www => site}/src/docs/content/console/providers.mdx (100%) rename services/{www => site}/src/docs/content/console/scim.mdx (100%) rename services/{www => site}/src/docs/content/console/sso.mdx (100%) rename services/{www => site}/src/docs/content/console/websearch.mdx (100%) rename services/{www => site}/src/docs/content/console/workspaces.mdx (100%) rename services/{www => site}/src/docs/content/formatters.mdx (100%) rename services/{www => site}/src/docs/content/index.mdx (100%) rename services/{www => site}/src/docs/content/instructions.mdx (100%) rename services/{www => site}/src/docs/content/mcp-servers.mdx (100%) rename services/{www => site}/src/docs/content/migrate-v1.mdx (100%) rename services/{www => site}/src/docs/content/models.mdx (100%) rename services/{www => site}/src/docs/content/network.mdx (100%) rename services/{www => site}/src/docs/content/permissions.mdx (100%) rename services/{www => site}/src/docs/content/plugins.mdx (100%) rename services/{www => site}/src/docs/content/policies.mdx (100%) rename services/{www => site}/src/docs/content/providers.mdx (100%) rename services/{www => site}/src/docs/content/references.mdx (100%) rename services/{www => site}/src/docs/content/skills.mdx (100%) rename services/{www => site}/src/docs/content/snapshots.mdx (100%) rename services/{www => site}/src/docs/content/themes.mdx (100%) rename services/{www => site}/src/docs/content/tools.mdx (100%) rename services/{www => site}/src/docs/content/troubleshooting.mdx (100%) rename services/{www => site}/src/docs/content/warming.mdx (100%) rename services/{www => site}/src/docs/content/websearch.mdx (100%) create mode 100644 services/site/src/docs/document.tsx create mode 100644 services/site/src/docs/frontmatter.ts create mode 100644 services/site/src/docs/layouts/DocsLayout.tsx rename services/{www => site}/src/docs/lib/llms.ts (83%) rename services/{www => site}/src/docs/lib/navigation.ts (90%) rename services/{www => site}/src/docs/lib/openapi.ts (100%) rename services/{www => site}/src/docs/lib/sitemap.ts (100%) create mode 100644 services/site/src/docs/middleware.ts create mode 100644 services/site/src/docs/modules.d.ts create mode 100644 services/site/src/docs/recma-solid.ts create mode 100644 services/site/src/docs/rehype-headings.ts create mode 100644 services/site/src/docs/rehype-shiki.ts rename services/{www => site}/src/docs/rehype-table-scroll.ts (100%) rename services/{www => site}/src/docs/remark-links.ts (100%) create mode 100644 services/site/src/docs/remark-slots.ts create mode 100644 services/site/src/docs/render.tsx create mode 100644 services/site/src/docs/script/build.ts rename services/{www => site/src/docs}/script/validate-links.ts (79%) rename services/{www => site}/src/docs/styles/global.css (99%) create mode 100644 services/site/src/docs/vite.ts delete mode 100644 services/www/.gitignore delete mode 100644 services/www/AGENTS.md delete mode 100644 services/www/README.md delete mode 100644 services/www/astro.config.ts delete mode 100644 services/www/package.json delete mode 100644 services/www/script/generate-openapi.ts delete mode 100644 services/www/script/pagefind.ts delete mode 100644 services/www/script/prepare-cloudflare.ts delete mode 100644 services/www/src/content.config.ts delete mode 100644 services/www/src/docs/AGENTS.md delete mode 100644 services/www/src/docs/components/Callout.astro delete mode 100644 services/www/src/docs/components/Card.astro delete mode 100644 services/www/src/docs/components/CardGroup.astro delete mode 100644 services/www/src/docs/components/CodeBlock.astro delete mode 100644 services/www/src/docs/components/CodeTabs.astro delete mode 100644 services/www/src/docs/components/DocsFooter.astro delete mode 100644 services/www/src/docs/components/DocsHeader.astro delete mode 100644 services/www/src/docs/components/DocsPage.astro delete mode 100644 services/www/src/docs/components/DocsSidebar.astro delete mode 100644 services/www/src/docs/components/DocsTableOfContents.astro delete mode 100644 services/www/src/docs/components/OpenApiReference.astro delete mode 100644 services/www/src/docs/components/OpenApiSchema.astro delete mode 100644 services/www/src/docs/components/PlanTabs.astro delete mode 100644 services/www/src/docs/layouts/DocsLayout.astro delete mode 100644 services/www/src/pages/docs-index/[...slug].astro delete mode 100644 services/www/src/pages/docs/[...slug].astro delete mode 100644 services/www/src/pages/docs/api/index.astro delete mode 100644 services/www/src/pages/docs/console/inference.astro delete mode 100644 services/www/src/pages/index.astro delete mode 100644 services/www/src/pages/install.astro delete mode 100644 services/www/src/pages/llms.txt.ts delete mode 100644 services/www/src/pages/sitemap.xml.ts delete mode 100644 services/www/tsconfig.json delete mode 100644 services/www/wrangler.jsonc diff --git a/.gitattributes b/.gitattributes index 079c5277d60..149a7c2ba9a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,5 +10,4 @@ packages/client/src/effect/api/** linguist-generated packages/core/src/**/*.txt text eol=lf packages/httpapi-codegen/test/generated/*.ts text eol=lf # The site tests compare source text and humans.txt byte for byte, so Windows checkouts keep LF. -services/www/** text=auto eol=lf services/site/** text=auto eol=lf diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml new file mode 100644 index 00000000000..7b9691a3cd2 --- /dev/null +++ b/.github/workflows/check-docs.yml @@ -0,0 +1,42 @@ +name: check-docs + +# Builds the V2 docs on their own (services/site/src/docs/script/build.ts): compiles every MDX page, +# prerenders it, builds search, checks every link and that pages load nothing from outside /v2. The +# site's deploy runs the same step, so a broken page fails here instead of blocking the deploy. +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - services/site/** + - packages/protocol/openapi.json + - packages/cli/src/config/** + - .github/workflows/check-docs.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + docs: + name: docs build + if: ${{ !github.event.pull_request.draft }} + runs-on: blacksmith-4vcpu-ubuntu-2404 + timeout-minutes: 10 + defaults: + run: + working-directory: services/site + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + + - uses: ./.github/actions/setup-bun + + # The API reference and the config schema the pages link (/v2/openapi.json, /v2/cli.json). + - name: Generate + run: bun run generate + + - name: Build docs + run: bun src/docs/script/build.ts --mode dev --out "$RUNNER_TEMP/docs" diff --git a/.github/workflows/deploy-www.yml b/.github/workflows/deploy-www.yml deleted file mode 100644 index 45f4cbebece..00000000000 --- a/.github/workflows/deploy-www.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: deploy-www - -on: - push: - branches: - - dev - - production - workflow_dispatch: - -concurrency: - group: deploy-www-${{ github.ref_name }} - cancel-in-progress: false - -permissions: - contents: read - -jobs: - deploy: - if: github.repository == 'anomalyco/opencode' && (github.ref_name == 'dev' || github.ref_name == 'production') - runs-on: blacksmith-4vcpu-ubuntu-2404 - environment: ${{ github.ref_name }} - steps: - - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 - - - uses: ./.github/actions/setup-bun - - - name: Build - working-directory: services/www - run: bun run build - env: - CLOUDFLARE_ENV: ${{ github.ref_name == 'production' && 'production' || 'dev' }} - - - name: Deploy - working-directory: services/www - run: bun run deploy - env: - CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/.opencode/skills/app-e2e-tests/SKILL.md b/.opencode/skills/app-e2e-tests/SKILL.md index d7d0f0690ea..ac57961b9f6 100644 --- a/.opencode/skills/app-e2e-tests/SKILL.md +++ b/.opencode/skills/app-e2e-tests/SKILL.md @@ -105,7 +105,7 @@ Most of these rarely reproduce on an idle local machine. Run from `packages/app`: - [ ] Run in built mode, as CI does: `bun run test:e2e:built -- e2e/regression/.spec.ts`. Dev mode serves unbundled modules, with the API on another origin (`playwright.config.ts:19-21`). -- [ ] Use a free port when another worktree or agent may hold 3000: `PLAYWRIGHT_PORT=4310 bun run test:e2e:built -- ...`. Built runs never reuse a server, and `serve.ts` fails on a busy port. Stay below 49152 on Windows: it reserves ports from there up and hands them out for outgoing connections (`services/www/src/docs/content/cli/web.mdx:43`, `packages/client/test/fixture/service-fixture.ts:108-109`). +- [ ] Use a free port when another worktree or agent may hold 3000: `PLAYWRIGHT_PORT=4310 bun run test:e2e:built -- ...`. Built runs never reuse a server, and `serve.ts` fails on a busy port. Stay below 49152 on Windows: it reserves ports from there up and hands them out for outgoing connections (`services/site/src/docs/content/cli/web.mdx:43`, `packages/client/test/fixture/service-fixture.ts:108-109`). - [ ] To repeat without rebuilding: - build once with `VITE_OPENCODE_TEST_FIXTURES=1 bun run build`; - keep `bun e2e/utils/serve.ts --host 127.0.0.1 --port 4310` running; diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1795a8eab03..6cc9adbcd3a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,7 +43,7 @@ For web development, run the backend and app in separate terminals. Other interf bun dev serve --port 4096 bun run dev:web bun run dev:desktop -bun run dev:www +bun run dev:site ``` ### Packages diff --git a/bun.lock b/bun.lock index b5da555c079..008550a213f 100644 --- a/bun.lock +++ b/bun.lock @@ -987,20 +987,39 @@ "zod": "catalog:", }, "devDependencies": { + "@babel/core": "7.29.7", "@cloudflare/build-output-utils": "0.8.5", "@cloudflare/config": "0.23.0", + "@mdx-js/mdx": "3.1.1", + "@types/babel__core": "7.20.5", "@types/bun": "catalog:", + "@types/estree-jsx": "1.0.5", + "@types/hast": "3.0.5", + "@types/mdast": "4.0.4", "@types/react": "19.3.0", "@types/three": "0.180.0", "@typescript/native": "catalog:", "@webgpu/types": "0.1.54", + "babel-preset-solid": "1.9.15", "cf": "1.0.0-beta.12", + "estree-util-visit": "2.0.0", + "github-slugger": "2.0.0", + "hast-util-to-text": "4.0.2", + "mdast-util-mdx-jsx": "3.2.0", "miniflare": "5.20260930.0-alpha", + "pagefind": "1.5.2", "playwright-core": "1.59.1", + "remark-gfm": "4.0.1", + "remark-smartypants": "3.0.3", "satori": "0.63.2", "sharp": "0.34.5", + "shiki": "catalog:", "typescript": "catalog:", + "unist-util-visit": "5.1.0", + "vfile": "6.0.3", + "vite-plugin-solid": "2.11.14", "wawoff2": "2.0.1", + "yaml": "2.9.1", }, }, "services/update": { @@ -1020,22 +1039,6 @@ "wrangler": "4.110.0", }, }, - "services/www": { - "name": "@opencode/www", - "dependencies": { - "@astrojs/mdx": "7.0.5", - "astro": "catalog:", - }, - "devDependencies": { - "@astrojs/check": "0.9.6", - "@astrojs/cloudflare": "14.1.4", - "@astrojs/markdown-remark": "7.3.2", - "@types/bun": "catalog:", - "pagefind": "1.5.2", - "typescript": "catalog:", - "wrangler": "4.110.0", - }, - }, }, "trustedDependencies": [ "electron", @@ -1197,8 +1200,6 @@ "@astrojs/cloudflare": ["@astrojs/cloudflare@14.1.4", "", { "dependencies": { "@astrojs/internal-helpers": "0.10.1", "@astrojs/underscore-redirects": "1.0.3", "@cloudflare/vite-plugin": "^1.39.0", "piccolore": "^0.1.3", "vite": "^8.0.13" }, "peerDependencies": { "astro": "^7.0.0", "wrangler": "^4.83.0" } }, "sha512-Zyo1E/5/dmegmKODbwUzOd67euNd6oKcbllhAwe3uFFprA7mIFNkpF6yBBa78vIkpMNuU13MkBJHCLd+yJSuEA=="], - "@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], - "@astrojs/compiler-binding": ["@astrojs/compiler-binding@0.5.2", "", { "optionalDependencies": { "@astrojs/compiler-binding-android-arm64": "0.5.2", "@astrojs/compiler-binding-darwin-arm64": "0.5.2", "@astrojs/compiler-binding-darwin-x64": "0.5.2", "@astrojs/compiler-binding-linux-arm64-gnu": "0.5.2", "@astrojs/compiler-binding-linux-arm64-musl": "0.5.2", "@astrojs/compiler-binding-linux-x64-gnu": "0.5.2", "@astrojs/compiler-binding-linux-x64-musl": "0.5.2", "@astrojs/compiler-binding-wasm32-wasi": "0.5.2", "@astrojs/compiler-binding-win32-arm64-msvc": "0.5.2", "@astrojs/compiler-binding-win32-x64-msvc": "0.5.2" } }, "sha512-lEn76+0JcMQ5RDXU0wSMWJg2ZppiZvSULpghC29cfv4o9bhvWFDbQjsCtvjjXN9iVLFQ7fL3wTR2L2UU13mP8g=="], "@astrojs/compiler-binding-android-arm64": ["@astrojs/compiler-binding-android-arm64@0.5.2", "", { "os": "android", "cpu": "arm64" }, "sha512-kckIxon6Rm/Bxzh9mGPYpRRqUbToUQoHptCmpMmE+Yt0Xumq8CqrKUidyYhrBsrHVO7bxuRzzWUUqvpf+iYceQ=="], @@ -2045,8 +2046,6 @@ "@opencode/web": ["@opencode/web@workspace:packages/web"], - "@opencode/www": ["@opencode/www@workspace:services/www"], - "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.219.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-FFx7YnaYJlIjqWW/AG/yAZ0L/NEY724PipXXXQLdtZPbLwBGbUMTGL1i/esI56TWfTUXxhLfpgrnWJCG8aUJyg=="], @@ -4929,7 +4928,7 @@ "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], "yaml-language-server": ["yaml-language-server@1.23.0", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "ajv-i18n": "^4.2.0", "prettier": "^3.8.1", "request-light": "^0.5.7", "vscode-json-languageservice": "4.1.8", "vscode-languageserver": "^9.0.0", "vscode-languageserver-textdocument": "^1.0.1", "vscode-languageserver-types": "^3.16.0", "vscode-uri": "^3.0.2", "yaml": "2.8.3" }, "bin": { "yaml-language-server": "bin/yaml-language-server" } }, "sha512-3qVyCOexLCWw06PQa5kRPwvMWMZ/eZeCRWUvgD6a0OkqL/4iCnxy2WumbWifa937Uo5xhyWJ0uxlU39ljhNh7A=="], @@ -4993,6 +4992,8 @@ "@astrojs/telemetry/is-docker": ["is-docker@4.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA=="], + "@astrojs/yaml2ts/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], @@ -5133,7 +5134,7 @@ "@opencode/update/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - "@opencode/www/@astrojs/check": ["@astrojs/check@0.9.6", "", { "dependencies": { "@astrojs/language-server": "^2.16.1", "chokidar": "^4.0.1", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "bin/astro-check.js" } }, "sha512-jlaEu5SxvSgmfGIFfNgcn5/f+29H61NJzEMfAZ82Xopr4XBchXB1GVlcJsE+elUlsYSbXlptZLX+JMG3b/wZEA=="], + "@opencode/update/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], "@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], @@ -5557,8 +5558,6 @@ "@astrojs/compiler-binding-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="], - "@astrojs/markdown-remark/@astrojs/internal-helpers/yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], - "@astrojs/markdown-satteri/@astrojs/internal-helpers/js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="], "@astrojs/markdown-satteri/@astrojs/internal-helpers/picomatch": ["picomatch@4.0.7", "", {}, "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA=="], @@ -5673,10 +5672,6 @@ "@opencode/stories/@tailwindcss/vite/tailwindcss": ["tailwindcss@4.1.11", "", {}, "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA=="], - "@opencode/www/@astrojs/check/@astrojs/language-server": ["@astrojs/language-server@2.16.14", "", { "dependencies": { "@astrojs/compiler": "^2.13.1", "@astrojs/yaml2ts": "^0.2.4", "@jridgewell/sourcemap-codec": "^1.5.5", "@volar/kit": "~2.4.28", "@volar/language-core": "~2.4.28", "@volar/language-server": "~2.4.28", "@volar/language-service": "~2.4.28", "muggle-string": "^0.4.1", "tinyglobby": "^0.2.16", "volar-service-css": "0.0.71", "volar-service-emmet": "0.0.71", "volar-service-html": "0.0.71", "volar-service-prettier": "0.0.71", "volar-service-typescript": "0.0.71", "volar-service-typescript-twoslash-queries": "0.0.71", "volar-service-yaml": "0.0.71", "vscode-html-languageservice": "^5.6.2", "vscode-uri": "^3.1.0" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier", "prettier-plugin-astro"], "bin": { "astro-ls": "./bin/nodeServer.js" } }, "sha512-YPXkBu6N4d1sT09pvBmIDGZay+1MemV551FSgdEM3aZRDzbkxd2H7Cvf8MsVJLVB1mIEyTf1XbMN/30gp7s46w=="], - - "@opencode/www/@astrojs/check/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "@opentelemetry/instrumentation/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="], "@opentui/solid/babel-preset-solid/babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.11", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-k7TkOv7uVYnrq/bYyVvG5cWOC9IObAGfmyW3aMkG8l2sq2qt/5THkUBja69PMLEGGi8G+fXB2Fh7eS4OwZRBoQ=="], @@ -5727,8 +5722,6 @@ "astro/@astrojs/internal-helpers/picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], - "astro/@astrojs/internal-helpers/yaml": ["yaml@2.9.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="], - "astro/@astrojs/markdown-satteri/satteri": ["satteri@0.10.5", "", { "dependencies": { "@types/estree-jsx": "^1.0.5", "@types/hast": "^3.0.5", "@types/mdast": "^4.0.4", "@types/unist": "^3.0.3" }, "optionalDependencies": { "@bruits/satteri-darwin-arm64": "0.10.5", "@bruits/satteri-darwin-x64": "0.10.5", "@bruits/satteri-linux-arm64-gnu": "0.10.5", "@bruits/satteri-linux-arm64-musl": "0.10.5", "@bruits/satteri-linux-x64-gnu": "0.10.5", "@bruits/satteri-linux-x64-musl": "0.10.5", "@bruits/satteri-wasm32-wasi": "0.10.5", "@bruits/satteri-win32-arm64-msvc": "0.10.5", "@bruits/satteri-win32-x64-msvc": "0.10.5" } }, "sha512-Ao1LKpAEa9Wdg0otgbVKViZHEq9ebdXe4DMrp3s9vQAU0HNIuHnFEuMuOcm0ZIXyV0Yzxj91NvhLpvXZJO/5ZQ=="], "astro/@clack/prompts/@clack/core": ["@clack/core@1.5.1", "", { "dependencies": { "fast-wrap-ansi": "^0.2.0", "sisteransi": "^1.0.5" } }, "sha512-iHTrHA8MtVuLl2TfZySmcKv1qO2PoyC9Z7pfSDozEuV5vtY3/wcOPKJXlqJ5Oq2Cx5DDGQGAMVx6HZfRRoVEbQ=="], @@ -6325,10 +6318,6 @@ "@opencode/stories/@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.11", "", { "os": "win32", "cpu": "x64" }, "sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg=="], - "@opencode/www/@astrojs/check/@astrojs/language-server/vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="], - - "@opencode/www/@astrojs/check/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], - "@opentui/solid/babel-preset-solid/babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "@puppeteer/browsers/yargs/cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], diff --git a/package.json b/package.json index e5f7cc1eea6..1bc5279e54f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,6 @@ "dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev", "dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev", "dev:site": "bun run --cwd services/site dev", - "dev:www": "bun run --cwd services/www dev", "lint": "oxlint && ast-grep scan -c script/ast-grep/gui-plugins/sgconfig.yml && bun script/sdk-docs.ts", "lint:changed": "bun script/lint-changed.ts", "lint:effect-patterns": "ast-grep scan -c script/ast-grep/sgconfig.yml packages/util/src packages/core/src packages/server/src packages/protocol/src packages/cli/src", diff --git a/packages/cli/script/schema.ts b/packages/cli/script/schema.ts index 3086aec462a..1d2a629be2a 100644 --- a/packages/cli/script/schema.ts +++ b/packages/cli/script/schema.ts @@ -18,7 +18,7 @@ const content = await format( if (process.argv.includes("--check")) { if ((await Bun.file(target).text()) !== content) { - console.error("Generated CLI config schema is stale. Run `bun run generate` from services/www.") + console.error("Generated CLI config schema is stale. Run `bun run generate` from services/site.") process.exit(1) } process.exit(0) diff --git a/packages/core/src/plugin/skill/opencode.md b/packages/core/src/plugin/skill/opencode.md index ceca99f7919..749caf97d61 100644 --- a/packages/core/src/plugin/skill/opencode.md +++ b/packages/core/src/plugin/skill/opencode.md @@ -143,7 +143,7 @@ secret into configuration. For any request to migrate OpenCode configuration, agents, commands, skills, plugins, integrations, or other behavior from V1 to V2, read the full [migration guide](https://opencode.ai/v2/docs/migrate-v1) before acting. In -the repository, its source is `services/www/src/docs/content/migrate-v1.mdx`. +the repository, its source is `services/site/src/docs/content/migrate-v1.mdx`. V1 config files and `.opencode/` definitions are intended to remain compatible. The only intentional breaking changes are the server API and plugin API. Native diff --git a/script/generate.ts b/script/generate.ts index cd2e0bb450b..1acdd4c8212 100755 --- a/script/generate.ts +++ b/script/generate.ts @@ -4,6 +4,6 @@ import { $ } from "bun" await $`bun run generate`.cwd("packages/protocol") -await $`bun run generate`.cwd("services/www") +await $`bun run generate`.cwd("services/site") await $`./script/format.ts` diff --git a/services/README.md b/services/README.md index 9262a2c6747..83900bbc024 100644 --- a/services/README.md +++ b/services/README.md @@ -5,8 +5,7 @@ and clients live in `packages/`. | Service | Purpose | Deployment | | -------------------------- | -------------------------------------- | -------------------------------- | -| [`site`](./site) | The opencode.ai website | `deploy-site.yml`, `cf` | -| [`www`](./www) | V2 documentation at `/v2` | `deploy-www.yml`, Wrangler | +| [`site`](./site) | The opencode.ai website and V2 docs | `deploy-site.yml`, `cf` | | [`updates`](./updates) | Release metadata and update selection | `release.yml`, Wrangler | | [`files`](./files) | Public file distribution at `/files/*` | `deploy-files.yml`, Wrangler | | [`changelog`](./changelog) | Release notes at `/changelog` | `deploy-changelog.yml`, Wrangler | diff --git a/services/site/.gitignore b/services/site/.gitignore index a922b2c63dc..eebc61f7dcd 100644 --- a/services/site/.gitignore +++ b/services/site/.gitignore @@ -33,9 +33,12 @@ Thumbs.db # Written by script/build-stamp.ts (bun run build / dev) /src/lib/build-stamp.json -# Written by `bun run generate` (packages/protocol/openapi.json and packages/schema/script/config-schema.ts) +# Written by `bun run generate` (packages/protocol/openapi.json, packages/schema/script/config-schema.ts and +# packages/cli/script/schema.ts) /public/openapi.json /public/config.json +/public/v2/openapi.json +/public/v2/cli.json # Written by script/github-snapshot.ts (bun run build) /src/lib/github-snapshot.json diff --git a/services/site/AGENTS.md b/services/site/AGENTS.md index 167bbfd7d03..712f3cb7d8a 100644 --- a/services/site/AGENTS.md +++ b/services/site/AGENTS.md @@ -21,7 +21,8 @@ Read the design-system rules in `DESIGN.md` (also shown at `/dev/components#rule ## Boundaries -- This service is only the website, deployed as its own Worker with `cf` (`cloudflare.config.ts`, `script/build.ts`); the V2 docs at `/v2*` are `services/www`. Never add Wrangler or SST here. +- This service is the website and the V2 docs, deployed as its own Worker with `cf` (`cloudflare.config.ts`, `script/build.ts`). Never add Wrangler or SST here. +- The V2 docs at `/v2` are a self-contained feature in `src/docs/` (read `src/docs/AGENTS.md`). They render their own pages with their own styles and script, never the site's, and the site's styles and scripts never load on them. - Old URLs kept for outside links live in `src/lib/legacy.ts`, each commented with why it exists and who still uses it; nothing may link to them. Short links (`/discord`, `/feishu`, `/desktop-feedback`) are ordinary routes. - The site also serves fixed-URL machine files: `/config.json` and `/openapi.json` (written by `bun run generate`), `/tui.json` (`public/`), and `/oauth/opencode/client.json` and `/.well-known/apple-app-site-association` (`src/lib/meta.ts`). Their bytes and headers are a contract with clients. - Heavy media (video, audio, zips, 3D models) is never a Worker asset: link it with `mediaUrl()` (`src/lib/media.ts`), which points into the files bucket behind `/files`. diff --git a/services/site/README.md b/services/site/README.md index 22bd84c0794..1d974af0fdd 100644 --- a/services/site/README.md +++ b/services/site/README.md @@ -46,6 +46,9 @@ Specification tree in `.cloudflare/output`; `bun run deploy:dev` builds and runs `cf deploy --prebuilt --mode dev`. `cloudflare.config.ts` defines the Worker (`opencode-site-dev` on dev.opencode.ai, `opencode-site-production` on opencode.ai), its bindings and its routes. CI deploys on pushes to `dev` and `production` (`.github/workflows/deploy-site.yml`). Production takes no routes until -`productionOwnsRoot` is switched on. The V2 docs at `/v2*` are a separate Worker (`services/www`). +`productionOwnsRoot` is switched on. + +The V2 docs at `/v2` are part of this app (`src/docs/`, see `src/docs/AGENTS.md`). `script/build.ts` prerenders +them into the Worker's assets after `vite build`. ## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli) diff --git a/services/site/cloudflare.config.ts b/services/site/cloudflare.config.ts index 5cbf1fe854c..6602761e3dd 100644 --- a/services/site/cloudflare.config.ts +++ b/services/site/cloudflare.config.ts @@ -2,12 +2,15 @@ import { bindings, defineConfig, triggers } from "cf/config" // The opencode.ai website (SolidStart). `--mode dev` deploys opencode-site-dev on dev.opencode.ai; // any other mode deploys opencode-site-production on opencode.ai. It is the catch-all: more specific -// routes on the zone (the V2 docs at /v2* in services/www, Console, inference, changelog, update, -// files, stats, …) keep beating it. +// routes on the zone (Console, inference, changelog, update, files, stats, …) keep beating it. It +// also serves the V2 docs at /v2 (src/docs). // // Production takes no routes until the go-live flips this switch (the `production` branch is shared // with every other service, so an unrelated push must not take the domain). const productionOwnsRoot = false +// Until then, production's /v2* can move to this Worker on its own: flip this once the route has +// been moved over from the old docs Worker (opencode-www-production), so deploys keep it. +const productionOwnsDocs = false export default defineConfig(({ mode }) => { const dev = mode === "dev" @@ -20,7 +23,12 @@ export default defineConfig(({ mode }) => { compatibilityDate: "2026-07-25", // The Nitro server is built with node compatibility (vite.config.ts). compatibilityFlags: ["nodejs_compat"], - assets: { htmlHandling: "drop-trailing-slash" }, + assets: { + htmlHandling: "drop-trailing-slash", + // Docs pages answer with HTML or Markdown from one URL, so the Worker picks the asset + // (src/docs/middleware.ts). Search's files are plain assets. + runWorkerFirst: ["/v2/docs", "/v2/docs/*", "!/v2/docs/pagefind/*"], + }, env: { ASSETS: bindings.assets(), STAGE: bindings.text(dev ? "dev" : "production"), @@ -31,7 +39,9 @@ export default defineConfig(({ mode }) => { triggers.fetch({ pattern: host, zone: "opencode.ai" }), triggers.fetch({ pattern: `${host}/*`, zone: "opencode.ai" }), ] - : [], + : productionOwnsDocs + ? [triggers.fetch({ pattern: `${host}/v2*`, zone: "opencode.ai" })] + : [], workersDev: false, previewUrls: false, // Workers Logs: requests, errors and console output, searchable in the Cloudflare dashboard. diff --git a/services/site/package.json b/services/site/package.json index bc39886d996..5299c26b542 100644 --- a/services/site/package.json +++ b/services/site/package.json @@ -15,7 +15,7 @@ "build": "bun script/build.ts", "deploy:dev": "bun script/build.ts --mode dev && cf deploy --prebuilt --mode dev", "deploy:production": "bun script/build.ts --mode production && cf deploy --prebuilt --mode production", - "generate": "cp ../../packages/protocol/openapi.json public/openapi.json && bun ../../packages/schema/script/config-schema.ts public/config.json", + "generate": "cp ../../packages/protocol/openapi.json public/openapi.json && bun ../../packages/schema/script/config-schema.ts public/config.json && cp ../../packages/protocol/openapi.json public/v2/openapi.json && bun --cwd ../../packages/cli script/schema.ts ../../services/site/public/v2/cli.json", "typecheck": "bun check --no-script && bun --cwd textjs check --no-script", "typecheck:tsc": "tsc --noEmit && tsc --noEmit -p textjs", "test": "bun test --only-failures" @@ -43,20 +43,39 @@ "zod": "catalog:" }, "devDependencies": { + "@babel/core": "7.29.7", "@cloudflare/build-output-utils": "0.8.5", "@cloudflare/config": "0.23.0", + "@mdx-js/mdx": "3.1.1", + "@types/babel__core": "7.20.5", "@types/bun": "catalog:", + "@types/estree-jsx": "1.0.5", + "@types/hast": "3.0.5", + "@types/mdast": "4.0.4", "@types/react": "19.3.0", "@types/three": "0.180.0", "@typescript/native": "catalog:", "@webgpu/types": "0.1.54", + "babel-preset-solid": "1.9.15", "cf": "1.0.0-beta.12", + "estree-util-visit": "2.0.0", + "github-slugger": "2.0.0", + "hast-util-to-text": "4.0.2", + "mdast-util-mdx-jsx": "3.2.0", "miniflare": "5.20260930.0-alpha", + "pagefind": "1.5.2", "playwright-core": "1.59.1", + "remark-gfm": "4.0.1", + "remark-smartypants": "3.0.3", "satori": "0.63.2", "sharp": "0.34.5", + "shiki": "catalog:", "typescript": "catalog:", - "wawoff2": "2.0.1" + "unist-util-visit": "5.1.0", + "vfile": "6.0.3", + "vite-plugin-solid": "2.11.14", + "wawoff2": "2.0.1", + "yaml": "2.9.1" }, "engines": { "node": ">=24" diff --git a/services/site/public/_headers b/services/site/public/_headers index 55351f52e6c..4b764e9cace 100644 --- a/services/site/public/_headers +++ b/services/site/public/_headers @@ -15,6 +15,9 @@ # Yoga's WebAssembly, named by a hash of its bytes (textjs/script/build.ts). /textjs/yoga-* Cache-Control: public, max-age=31536000, immutable +# The V2 docs' stylesheet, script and fonts, named by a hash of their contents (src/docs/script/build.ts). +/v2/_build/* + Cache-Control: public, max-age=31536000, immutable # Link preview cards, named by a hash of the PNG (script/og.ts). /og/* Cache-Control: public, max-age=31536000, immutable diff --git a/services/www/public/assets/docs/cli/settings.png b/services/site/public/v2/assets/docs/cli/settings.png similarity index 100% rename from services/www/public/assets/docs/cli/settings.png rename to services/site/public/v2/assets/docs/cli/settings.png diff --git a/services/www/public/assets/logo-dark.svg b/services/site/public/v2/assets/logo-dark.svg similarity index 100% rename from services/www/public/assets/logo-dark.svg rename to services/site/public/v2/assets/logo-dark.svg diff --git a/services/www/public/assets/logo-light.svg b/services/site/public/v2/assets/logo-light.svg similarity index 100% rename from services/www/public/assets/logo-light.svg rename to services/site/public/v2/assets/logo-light.svg diff --git a/services/www/public/favicon.svg b/services/site/public/v2/favicon.svg similarity index 100% rename from services/www/public/favicon.svg rename to services/site/public/v2/favicon.svg diff --git a/services/site/script/build.ts b/services/site/script/build.ts index cefae8fac50..eb859588c33 100644 --- a/services/site/script/build.ts +++ b/services/site/script/build.ts @@ -34,6 +34,8 @@ await requireSnapshot() await Bun.$`bun ./script/og.ts`.cwd(root).env(env) await Bun.$`bun ./textjs/script/build.ts`.cwd(root).env(env) await Bun.$`vite build`.cwd(root).env(env) +// The V2 docs at /v2: prerendered pages, Markdown twins and search (src/docs/script/build.ts). +await Bun.$`bun ./src/docs/script/build.ts --mode ${mode}`.cwd(root).env(env) await Bun.$`bun ./script/check-server-bundle.ts`.cwd(root).env(env) await Bun.$`bun ./script/check-client-bundle.ts`.cwd(root).env(env) @@ -59,7 +61,10 @@ await cp(path.join(root, ".output", "public"), assets, { }) const shipped = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: assets, dot: true })) // Every opencode.json and V1 TUI config names these as its $schema; /openapi.json is the protocol's. -const fixed = ["config.json", "tui.json", "openapi.json"].filter((file) => !shipped.includes(file)) +// The V2 config names /v2/cli.json, and the docs link /v2/openapi.json. +const fixed = ["config.json", "tui.json", "openapi.json", "v2/cli.json", "v2/openapi.json"].filter( + (file) => !shipped.includes(file), +) if (fixed.length) throw new Error(`fixed-URL files missing from Worker assets: ${fixed.join(", ")}`) await writeAssets({ root, sourceDirectory: assets }) diff --git a/services/site/script/check-worker-start.ts b/services/site/script/check-worker-start.ts index 996c40fce04..c2800f44a67 100644 --- a/services/site/script/check-worker-start.ts +++ b/services/site/script/check-worker-start.ts @@ -15,14 +15,15 @@ import { readBuildOutput } from "@cloudflare/build-output-utils" import { Miniflare, Response } from "miniflare" const root = path.resolve(import.meta.dir, "..") -// Pages with a status must answer exactly that; /v2/docs/ (the docs, when this build has them, else -// a 404) anything but a 5xx. -const pages = [ +// Pages with a status must answer exactly that, others anything but a 5xx. The docs page is asked for +// as a browser asks and, at a second URL, as an agent asks (its Markdown twin, src/docs/middleware.ts). +const pages: { path: string; status?: number; accept?: string }[] = [ { path: "/", status: 200 }, { path: "/ja", status: 200 }, { path: "/desktop", status: 200 }, { path: "/check-worker-start-404", status: 404 }, - { path: "/v2/docs/" }, + { path: "/v2/docs/", status: 200 }, + { path: "/v2/docs/config/", status: 200, accept: "text/markdown" }, ] const started = performance.now() @@ -82,7 +83,10 @@ const results = await Promise.resolve() .then(async (url) => { const answers = [] for (const page of pages) { - const actual = await fetch(new URL(page.path, url), { headers: { accept: "text/html" }, redirect: "manual" }) + const actual = await fetch(new URL(page.path, url), { + headers: { accept: page.accept ?? "text/html" }, + redirect: "manual", + }) .then(async (response) => (await response.arrayBuffer(), response.status)) .catch(() => 0) answers.push({ ...page, actual }) diff --git a/services/site/script/docs-file-icons.ts b/services/site/script/docs-file-icons.ts index d74f614673d..297eb0e6760 100644 --- a/services/site/script/docs-file-icons.ts +++ b/services/site/script/docs-file-icons.ts @@ -8,7 +8,7 @@ import { readdirSync } from "node:fs" import path from "node:path" const app = path.resolve(import.meta.dir, "..") -const content = path.resolve(app, "../../../services/www/src/docs/content") +const content = path.resolve(app, "src/docs/content") const sprite = await Bun.file(path.resolve(app, "../../ui/src/components/file-icons/sprite.svg")).text() const walk = (dir: string): string[] => diff --git a/services/site/script/opentui-mono.py b/services/site/script/opentui-mono.py index 92c8e9929c0..435894d0a63 100644 --- a/services/site/script/opentui-mono.py +++ b/services/site/script/opentui-mono.py @@ -1,4 +1,4 @@ -# Builds the site's OpenTUI Mono files from the docs' copies (services/www/src/docs/assets/fonts, +# Builds the site's OpenTUI Mono files from the docs' copies (src/docs/assets/fonts, # the same files as anomalyco/opentui packages/web/public/fonts). Two files per style, split by # unicode-range like Inter: "latin" (Latin, punctuation, arrows, shapes and box drawing, what the # site sets in mono) and "rest" (every other character the font has, only fetched when a page uses @@ -10,7 +10,7 @@ # are kept. # # OpenTUI Mono is licensed under the SIL Open Font License 1.1 (src/asset/fonts/OpenTUIMono-OFL.txt). -# Run from packages/console/app: +# Run from services/site: # uvx --from 'fonttools[woff]==4.60.1' python script/opentui-mono.py import json from pathlib import Path @@ -18,7 +18,7 @@ from pathlib import Path from fontTools import subset from fontTools.ttLib import TTFont -source = Path("../../../services/www/src/docs/assets/fonts") +source = Path("src/docs/assets/fonts") out = Path("src/asset/fonts") styles = { "regular": "OpenTUIMono-Regular", diff --git a/services/site/src/component/site/docs-code-gallery.tsx b/services/site/src/component/site/docs-code-gallery.tsx index dd84b488b69..e2e02b4dec1 100644 --- a/services/site/src/component/site/docs-code-gallery.tsx +++ b/services/site/src/component/site/docs-code-gallery.tsx @@ -6,7 +6,7 @@ import { docsInstalls } from "~/lib/docs-content" /** * Every docs code block type at once, in Vercel square (the chosen code style), whatever the * kitchen sink's switchers say: docs-variants.css styles [data-slot="code-gallery"] as option 6. - * Content is from the docs (services/www/src/docs/content), with models reading Claude Opus 5.5. + * Content is from the docs (src/docs/content), with models reading Claude Opus 5.5. */ export function DocsCodeGallery(props: { reference?: boolean }) { return ( diff --git a/services/site/src/docs/AGENTS.md b/services/site/src/docs/AGENTS.md new file mode 100644 index 00000000000..52ab87dbd61 --- /dev/null +++ b/services/site/src/docs/AGENTS.md @@ -0,0 +1,42 @@ +# Documentation feature guide + +## Structure + +- This folder is the V2 docs at `/v2` on the site: content, components, styles, build and serving. Keep docs code here; the site's shared files only plug it in (`vite.config.ts`, `src/middleware.ts`, `script/build.ts`, `cloudflare.config.ts`). +- Content lives in `content/` as MDX. A file's path is its URL: `content/cli/keybinds.mdx` is `/v2/docs/cli/keybinds/`. Other repositories read some of these files by path, so don't move them without checking. +- `lib/navigation.ts` is the source of truth for header sections and sidebar ordering. +- `frontmatter.ts` is the frontmatter schema; a page that doesn't match it fails the build. +- `config.ts` holds the mount path (`/v2/`). Build URLs from it (`docsHref`), never from the site's base. MDX reads it as `import.meta.env.BASE_URL`. +- Keep internal Markdown links docs-root-relative, for example `/config`; `remark-links.ts` adds `/v2/docs`. +- Components used in MDX are registered in `document.tsx`. Named slots work as in Astro: `
` inside a component becomes that slot's content (`remark-slots.ts`, `components/Slot.tsx`). + +## Rendering + +- Pages are plain HTML. They never hydrate and never load the site's styles, fonts or scripts; the site's never load the docs'. A page links `styles/global.css` and the one browser script, `client.ts`. +- Everything a page loads lives under `/v2` (the stylesheet, script and fonts are built to `/v2/_build`), because production can route `/v2` to this Worker before the rest of the domain. The build fails otherwise. +- Interactive pieces (search, copy buttons, tabs, the status link) are delegated listeners in `client.ts`, keyed on `data-*` attributes the components render. Don't add Solid state or effects to docs components; they only run on the server. +- The theme script is inline in `layouts/DocsLayout.tsx` so pages never flash. Navigation is ordinary page loads, made instant by Speculation Rules and cross-document view transitions. +- `vite.ts` compiles MDX the way Astro's MDX integration did (GitHub-flavoured Markdown, smart punctuation, Shiki with the github-light and github-dark themes, GitHub-style heading ids), so the markup and `global.css` stay unchanged. + +## Build and serving + +- `script/build.ts` runs after the site's `vite build` (from `script/build.ts` at the site root): it builds the docs' stylesheet and script, prerenders every page, every content page's Markdown twin (`/v2/docs/config.md`), `llms.txt` and the sitemap into `.output/public`, builds the Pagefind index, and checks every link and that nothing loads from outside `/v2`. `--mode dev` makes `llms.txt` and the sitemap list dev.opencode.ai. +- Pull requests that touch the site run the same build on its own (`.github/workflows/check-docs.yml`): `bun run generate`, then `bun src/docs/script/build.ts --out `. +- The Worker never renders docs. `middleware.ts` serves the prerendered files: docs URLs reach it first (`runWorkerFirst` in `cloudflare.config.ts`), so a page's URL answers `Accept: text/markdown` with its twin. `vite dev` has no prerendered files, so the development server renders on request. + +## Writing Style + +- Keep prose sections brief and focused on one idea. Prefer one to three sentences over large paragraphs. +- Interleave explanations with concrete code, configuration, command, or output examples so pages do not become walls of text. +- Put the relevant example immediately after the text that introduces it, following `content/build/plugins/cli.mdx` as the reference pattern. +- Every subsection that explains syntax, fields, or an API concept must include its own minimal example. A larger example earlier on the page does not count. +- Split long explanations with meaningful headings and examples rather than accumulating caveats in one paragraph. +- Lead with the common task and working example; place edge cases and supporting details afterward. +- Do not stack several prose paragraphs without a visual break. After introducing a concept, use an example, list, table, or task-oriented subheading before covering the next concern. +- Use bullets or tables for independent rules, options, and constraints. Do not hide reference material in narrative paragraphs. +- Organize workflow documentation in the order readers perform it, with a working example at each major step. + +## Validation + +- Run `bun typecheck` and `bun run build` from `services/site` after changes. The build fails on invalid frontmatter, MDX errors and broken links. +- Check desktop and mobile layouts, light and dark, when changing navigation or shared styles. diff --git a/services/www/src/docs/assets/fonts/OpenTUIMono-Bold.woff2 b/services/site/src/docs/assets/fonts/OpenTUIMono-Bold.woff2 similarity index 100% rename from services/www/src/docs/assets/fonts/OpenTUIMono-Bold.woff2 rename to services/site/src/docs/assets/fonts/OpenTUIMono-Bold.woff2 diff --git a/services/www/src/docs/assets/fonts/OpenTUIMono-BoldItalic.woff2 b/services/site/src/docs/assets/fonts/OpenTUIMono-BoldItalic.woff2 similarity index 100% rename from services/www/src/docs/assets/fonts/OpenTUIMono-BoldItalic.woff2 rename to services/site/src/docs/assets/fonts/OpenTUIMono-BoldItalic.woff2 diff --git a/services/www/src/docs/assets/fonts/OpenTUIMono-Italic.woff2 b/services/site/src/docs/assets/fonts/OpenTUIMono-Italic.woff2 similarity index 100% rename from services/www/src/docs/assets/fonts/OpenTUIMono-Italic.woff2 rename to services/site/src/docs/assets/fonts/OpenTUIMono-Italic.woff2 diff --git a/services/www/src/docs/assets/fonts/OpenTUIMono-Regular.woff2 b/services/site/src/docs/assets/fonts/OpenTUIMono-Regular.woff2 similarity index 100% rename from services/www/src/docs/assets/fonts/OpenTUIMono-Regular.woff2 rename to services/site/src/docs/assets/fonts/OpenTUIMono-Regular.woff2 diff --git a/services/site/src/docs/client.ts b/services/site/src/docs/client.ts new file mode 100644 index 00000000000..d253daf6604 --- /dev/null +++ b/services/site/src/docs/client.ts @@ -0,0 +1,180 @@ +// The docs pages' only script: search, copy buttons, tabs and the status link. Pages are plain HTML +// (no hydration); everything here is delegated from the document, so it needs no per-element setup. +// The theme toggle is in the layout's head script, so it works before this loads. +type PagefindResult = { url: string; excerpt: string; meta: { title: string } } +type Pagefind = { search(query: string): Promise<{ results: { data(): Promise }[] }> } + +let pagefind: Promise | undefined +// Pagefind is loaded from its built file on first search. Through `Function` because Vite would wrap +// a plain import() in its preload helper, a second script on every page for a file it can't preload. +const load = new Function("url", "return import(url)") as (url: string) => Promise +const dialog = document.querySelector("[data-search-dialog]") +const input = document.querySelector("[data-search-input]") +const results = document.querySelector("[data-search-results]") + +function openSearch() { + dialog?.showModal() + input?.focus() +} + +document.addEventListener("keydown", (event) => { + if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") { + event.preventDefault() + openSearch() + } +}) + +input?.addEventListener("input", async () => { + if (!dialog || !results) return + const query = input.value.trim() + if (!query) { + results.innerHTML = "

Start typing to search the documentation.

" + return + } + const path = dialog.dataset.pagefindPath + if (!path) return + pagefind ??= load(path) + const search = await (await pagefind).search(query) + const pages = await Promise.all(search.results.slice(0, 8).map((result) => result.data())) + const base = dialog.dataset.baseUrl ?? "/" + results.replaceChildren( + ...pages.map((page) => { + const link = document.createElement("a") + link.href = page.url.startsWith(base) ? page.url : `${base.replace(/\/$/, "")}${page.url}` + const title = document.createElement("strong") + title.textContent = page.meta.title + const excerpt = document.createElement("span") + excerpt.innerHTML = page.excerpt + link.append(title, excerpt) + return link + }), + ) + if (!pages.length) results.innerHTML = "

No results found.

" +}) + +document.addEventListener("click", async (event) => { + if (!(event.target instanceof Element)) return + if (event.target.closest("[data-search-open]")) return openSearch() + + const code = event.target.closest("[data-copy-code]") + if (code) { + await navigator.clipboard.writeText(code.dataset.code ?? "") + code.textContent = "Copied" + window.setTimeout(() => (code.textContent = "Copy"), 1500) + return + } + + const tab = event.target.closest("[data-code-tab]") + if (tab) return selectCodeTab(tab) + + const plan = event.target.closest("[data-plan-tab]") + const plans = plan?.closest("[data-plan-tabs]") + if (plan?.dataset.planTab && plans) return selectSyncedPlan(plans, plan.dataset.planTab) + + const markdown = event.target.closest("[data-copy-markdown]") + if (markdown) { + markdown.dataset.copied = "true" + markdown.setAttribute("aria-label", "Copied") + window.setTimeout(() => { + delete markdown.dataset.copied + markdown.setAttribute("aria-label", "Copy page as Markdown") + }, 1500) + const response = await fetch(window.location.href, { headers: { Accept: "text/markdown" } }) + if (!response.ok) return + await navigator.clipboard.writeText(await response.text()) + } +}) + +// Arrow keys, Home and End move between tabs. +document.addEventListener("keydown", (event) => { + if (!(event.target instanceof HTMLButtonElement)) return + const tabs = event.target.closest("[data-code-tabs], [data-plan-tabs]") + if (!tabs || !event.target.matches("[data-code-tab], [data-plan-tab]")) return + const buttons = [...tabs.querySelectorAll("[data-code-tab], [data-plan-tab]")] + const selected = buttons.indexOf(event.target) + const next = + event.key === "Home" + ? buttons[0] + : event.key === "End" + ? buttons.at(-1) + : event.key === "ArrowRight" + ? buttons[(selected + 1) % buttons.length] + : event.key === "ArrowLeft" + ? buttons[(selected - 1 + buttons.length) % buttons.length] + : undefined + if (!next) return + event.preventDefault() + if (next.dataset.planTab) selectSyncedPlan(tabs, next.dataset.planTab) + else selectCodeTab(next) + next.focus() +}) + +function selectCodeTab(button: HTMLButtonElement) { + const tabs = button.closest("[data-code-tabs]") + if (!tabs) return + const buttons = [...tabs.querySelectorAll("[data-code-tab]")] + const selected = buttons.indexOf(button) + buttons.forEach((item, index) => { + item.setAttribute("aria-selected", String(index === selected)) + item.tabIndex = index === selected ? 0 : -1 + }) + tabs.querySelectorAll("[data-code-panel]").forEach((panel, index) => (panel.hidden = index !== selected)) +} + +function selectPlan(tabs: HTMLElement, plan: string) { + tabs.querySelectorAll("[data-plan-tab]").forEach((button) => { + const selected = button.dataset.planTab === plan + button.setAttribute("aria-selected", String(selected)) + button.tabIndex = selected ? 0 : -1 + }) + tabs.querySelectorAll("[data-plan-panel]").forEach((panel) => { + panel.hidden = panel.dataset.planPanel !== plan + }) +} + +// Plan tabs with the same sync key switch together, and the choice is remembered. +function selectSyncedPlan(source: HTMLElement, plan: string) { + const syncKey = source.dataset.syncKey + if (!syncKey) return + document.querySelectorAll("[data-plan-tabs]").forEach((tabs) => { + if (tabs.dataset.syncKey === syncKey) selectPlan(tabs, plan) + }) + localStorage.setItem(`docs-plan-tabs:${syncKey}`, plan) +} + +document.querySelectorAll("[data-plan-tabs]").forEach((tabs) => { + const plan = tabs.dataset.syncKey && localStorage.getItem(`docs-plan-tabs:${tabs.dataset.syncKey}`) + if (plan) selectPlan(tabs, plan) +}) + +// The status page's summary. Each page is a full load, so the last answer is kept for a minute in +// sessionStorage: the next page shows it at once instead of fetching again. +const labels = { + operational: "All systems operational", + degraded: "Degraded performance", + outage: "Service disruption", +} +type Status = keyof typeof labels + +function showStatus(status: Status) { + const link = document.querySelector(".system-status") + if (!link) return + link.dataset.status = status + link.textContent = labels[status] +} + +const cached = (() => { + const value = JSON.parse(sessionStorage.getItem("docs-status") ?? "null") as { status: Status; at: number } | null + return value && value.status in labels ? value : undefined +})() +if (cached) showStatus(cached.status) +if (!cached || Date.now() - cached.at > 60_000) + fetch("/status/api/summary", { signal: AbortSignal.timeout(10_000) }) + .then((response) => (response.ok ? response.json() : undefined)) + .then((body: unknown) => { + const status = body && typeof body === "object" && "status" in body ? body.status : undefined + if (status !== "operational" && status !== "degraded" && status !== "outage") return + sessionStorage.setItem("docs-status", JSON.stringify({ status, at: Date.now() })) + showStatus(status) + }) + .catch(() => undefined) diff --git a/services/site/src/docs/components/Callout.tsx b/services/site/src/docs/components/Callout.tsx new file mode 100644 index 00000000000..ee1186e80f1 --- /dev/null +++ b/services/site/src/docs/components/Callout.tsx @@ -0,0 +1,12 @@ +import type { JSX } from "solid-js" + +export function Callout(props: { type?: "note" | "tip" | "warning"; title?: string; children?: JSX.Element }) { + const type = props.type ?? "note" + const title = props.title ?? (type === "tip" ? "Tip" : type === "warning" ? "Warning" : "Note") + return ( + + ) +} diff --git a/services/site/src/docs/components/Card.tsx b/services/site/src/docs/components/Card.tsx new file mode 100644 index 00000000000..2f335043db7 --- /dev/null +++ b/services/site/src/docs/components/Card.tsx @@ -0,0 +1,12 @@ +import type { JSX } from "solid-js" +import { base } from "../config" + +export function Card(props: { title: string; href: string; children?: JSX.Element }) { + const href = props.href.startsWith("/") ? `${base}docs${props.href}` : props.href + return ( + + {props.title} +
{props.children}
+
+ ) +} diff --git a/services/site/src/docs/components/CardGroup.tsx b/services/site/src/docs/components/CardGroup.tsx new file mode 100644 index 00000000000..509e6b59b7a --- /dev/null +++ b/services/site/src/docs/components/CardGroup.tsx @@ -0,0 +1,9 @@ +import type { JSX } from "solid-js" + +export function CardGroup(props: { cols?: number; children?: JSX.Element }) { + return ( +
+ {props.children} +
+ ) +} diff --git a/services/site/src/docs/components/CodeBlock.tsx b/services/site/src/docs/components/CodeBlock.tsx new file mode 100644 index 00000000000..ef1a0a88818 --- /dev/null +++ b/services/site/src/docs/components/CodeBlock.tsx @@ -0,0 +1,13 @@ +// The copy button works through client.ts. +export function CodeBlock(props: { code: string }) { + return ( +
+
+        {props.code}
+      
+ +
+ ) +} diff --git a/services/site/src/docs/components/CodeTabs.tsx b/services/site/src/docs/components/CodeTabs.tsx new file mode 100644 index 00000000000..c4809b08559 --- /dev/null +++ b/services/site/src/docs/components/CodeTabs.tsx @@ -0,0 +1,40 @@ +import { For } from "solid-js" +import { CodeBlock } from "./CodeBlock" + +// The tabs switch through client.ts. +export function CodeTabs(props: { id: string; label: string; tabs: { label: string; code: string }[] }) { + return ( +
+
+ + {(tab, index) => ( + + )} + +
+ + {(tab, index) => ( + + )} + +
+ ) +} diff --git a/services/site/src/docs/components/DocsFooter.tsx b/services/site/src/docs/components/DocsFooter.tsx new file mode 100644 index 00000000000..5792f7789e4 --- /dev/null +++ b/services/site/src/docs/components/DocsFooter.tsx @@ -0,0 +1,12 @@ +// The status comes in through client.ts. +export function DocsFooter() { + return ( + + ) +} diff --git a/services/site/src/docs/components/DocsHeader.tsx b/services/site/src/docs/components/DocsHeader.tsx new file mode 100644 index 00000000000..6dda1e579a0 --- /dev/null +++ b/services/site/src/docs/components/DocsHeader.tsx @@ -0,0 +1,97 @@ +import { For, Show } from "solid-js" +import { base } from "../config" +import { docsHref, docsSections, getDocsSection } from "../lib/navigation" + +// Search, the theme toggle and the ⌘K shortcut work through the layout's head script and client.ts. +export function DocsHeader(props: { currentSlug: string }) { + const currentSection = getDocsSection(props.currentSlug) + const searchEnabled = import.meta.env.PROD + return ( + <> + + + + +
+

Start typing to search the documentation.

+
+
+
+ + ) +} diff --git a/services/site/src/docs/components/DocsSidebar.tsx b/services/site/src/docs/components/DocsSidebar.tsx new file mode 100644 index 00000000000..0de95bcd5a3 --- /dev/null +++ b/services/site/src/docs/components/DocsSidebar.tsx @@ -0,0 +1,29 @@ +import { For, Show } from "solid-js" +import { docsHref, type DocsNavGroup } from "../lib/navigation" + +export function DocsSidebar(props: { groups: DocsNavGroup[]; currentSlug: string }) { + return ( + + ) +} diff --git a/services/site/src/docs/components/DocsTableOfContents.tsx b/services/site/src/docs/components/DocsTableOfContents.tsx new file mode 100644 index 00000000000..f9edd9f0bb4 --- /dev/null +++ b/services/site/src/docs/components/DocsTableOfContents.tsx @@ -0,0 +1,22 @@ +import { For, Show } from "solid-js" +import type { DocsHeading } from "../rehype-headings" + +export function DocsTableOfContents(props: { headings: DocsHeading[] }) { + const headings = props.headings.filter((heading) => heading.depth === 2 || heading.depth === 3) + return ( + 0}> + + + ) +} diff --git a/services/site/src/docs/components/OpenApiReference.tsx b/services/site/src/docs/components/OpenApiReference.tsx new file mode 100644 index 00000000000..df1b3dfa5d8 --- /dev/null +++ b/services/site/src/docs/components/OpenApiReference.tsx @@ -0,0 +1,224 @@ +import { For, Show } from "solid-js" +import openapi from "../../../../../packages/protocol/openapi.json" +import { base } from "../config" +import { openApiSchemaLabel, type OpenApiOperation, type OpenApiSchema } from "../lib/openapi" +import { OpenApiSchemaView } from "./OpenApiSchema" + +const methods = ["get", "post", "put", "patch", "delete", "options", "head"] as const +type Method = (typeof methods)[number] + +const paths = openapi.paths as unknown as Record>> +const operations = Object.entries(paths).flatMap(([path, item]) => + methods.flatMap((method) => { + const operation = item[method] + if (!operation) return [] + return [ + { ...operation, path, method, id: operation.operationId ?? `${method}-${path}`.replace(/[^a-z0-9]+/gi, "-") }, + ] + }), +) +const groups = [...new Set(operations.map((operation) => operation.tags?.[0] ?? "other"))].map((tag) => ({ + tag, + id: `tag-${tag.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`, + description: openapi.tags.find((item) => item.name === tag)?.description, + operations: operations.filter((operation) => (operation.tags?.[0] ?? "other") === tag), +})) +const schemas = Object.entries(openapi.components.schemas) as [string, OpenApiSchema][] + +export function OpenApiReference() { + return ( +
+ + +
+
+

OpenAPI {openapi.openapi}

+

HTTP API

+

{openapi.info.description}

+
+ {operations.length} operations + {schemas.length} schemas + OpenAPI JSON +
+
+ + + {(group) => ( +
+
+

Resource

+

{group.tag}

+ +

{group.description}

+
+
+
+ {(operation) => } +
+
+ )} +
+ +
+
+

Components

+

Schemas

+

Reusable data types referenced by API operations.

+
+
+ + {([name, schema]) => ( +
+ + {name} + {openApiSchemaLabel(schema)} + +
+ +
+
+ )} +
+
+
+
+
+ ) +} + +function Operation(props: { operation: (typeof operations)[number] }) { + const operation = props.operation + return ( +
+ + {operation.method} + {operation.path} + {operation.summary} + +
+ +

{operation.description}

+
+

+ Operation ID + {operation.operationId} +

+ +
+

Parameters

+
+ + + + + + + + + + + + {(parameter) => ( + + + + + + + )} + + +
NameLocationTypeDescription
+ {parameter.name} + + required + + {parameter.in} + + {parameter.description ?? No description}
+
+
+
+ + {(body) => ( +
+

+ Request body{" "} + + required + +

+ +

{body().description}

+
+
+ + {([mediaType, content]) => ( +
+ {mediaType} + +
+ )} +
+
+
+ )} +
+
+

Responses

+
+ + {([status, response]) => ( +
+
+ + {status} + + {response.description ?? "Response"} +
+ + {([mediaType, content]) => ( +
+ {mediaType} + +
+ )} +
+
+ )} +
+
+
+
+
+ ) +} diff --git a/services/site/src/docs/components/OpenApiSchema.tsx b/services/site/src/docs/components/OpenApiSchema.tsx new file mode 100644 index 00000000000..7b1daa3dd6a --- /dev/null +++ b/services/site/src/docs/components/OpenApiSchema.tsx @@ -0,0 +1,114 @@ +import { For, Show } from "solid-js" +import { formatOpenApiValue, openApiSchemaLabel, type OpenApiSchema } from "../lib/openapi" + +export function OpenApiSchemaView(props: { schema?: OpenApiSchema; required?: boolean }) { + const schema = props.schema + const reference = schema?.$ref?.split("/").at(-1) + const variants = schema?.anyOf ?? schema?.oneOf ?? schema?.allOf + const variantOperator = schema?.allOf ? "and" : "or" + const constraints = schema + ? [ + schema.pattern && `pattern ${schema.pattern}`, + schema.minimum !== undefined && `min ${schema.minimum}`, + schema.maximum !== undefined && `max ${schema.maximum}`, + schema.exclusiveMinimum !== undefined && `> ${schema.exclusiveMinimum}`, + schema.exclusiveMaximum !== undefined && `< ${schema.exclusiveMaximum}`, + schema.minLength !== undefined && `min length ${schema.minLength}`, + schema.maxLength !== undefined && `max length ${schema.maxLength}`, + schema.minItems !== undefined && `min items ${schema.minItems}`, + schema.maxItems !== undefined && `max items ${schema.maxItems}`, + ].filter((value): value is string => typeof value === "string") + : [] + + if (reference) + return ( + + {reference} + + ) + return ( +
+
+ {openApiSchemaLabel(schema)} + + required + + + deprecated + +
+ +

{schema?.description}

+
+ + {(values) => ( +
+ Values + {values().map(formatOpenApiValue).join(" | ")} +
+ )} +
+ +
+ Constant + {formatOpenApiValue(schema?.const)} +
+
+ +
+ Default + {formatOpenApiValue(schema?.default)} +
+
+ 0}> +
{constraints.join(", ")}
+
+ + {(items) => ( +
+ + {(variant, index) => ( +
+ 0}> + {variantOperator} + + +
+ )} +
+
+ )} +
+ + {(items) => ( +
+ Items + +
+ )} +
+ + {(properties) => ( +
+ + {([name, property]) => ( +
+ {name} + +
+ )} +
+
+ )} +
+ + {(additional) => ( +
+ Additional properties + +
+ )} +
+
+ ) +} diff --git a/services/site/src/docs/components/PlanTabs.tsx b/services/site/src/docs/components/PlanTabs.tsx new file mode 100644 index 00000000000..93208e90224 --- /dev/null +++ b/services/site/src/docs/components/PlanTabs.tsx @@ -0,0 +1,47 @@ +import { For, type JSX } from "solid-js" +import { SlotScope } from "./Slot" + +const plans = [ + { id: "go", label: "Go" }, + { id: "go-plus", label: "Go Plus" }, + { id: "go-teams", label: "Go Teams" }, +] as const + +// One panel per plan, from the `slot="go"`, `slot="go-plus"` and `slot="go-teams"` children. The tabs +// switch, and stay in step across every PlanTabs with the same syncKey, through client.ts. +export function PlanTabs(props: { id: string; label: string; syncKey: string; children?: JSX.Element }) { + return ( +
+
+ + {(plan, index) => ( + + )} + +
+ + {(plan, index) => ( + + )} + +
+ ) +} diff --git a/services/site/src/docs/components/Slot.tsx b/services/site/src/docs/components/Slot.tsx new file mode 100644 index 00000000000..008ef1b831c --- /dev/null +++ b/services/site/src/docs/components/Slot.tsx @@ -0,0 +1,15 @@ +import { createContext, Show, useContext, type JSX } from "solid-js" + +// Named slots for MDX components (remark-slots.ts turns `
` into ``). +// A component renders its children inside `` to show only that slot's content. +// Outside any scope, a slot shows its content. +const SlotName = createContext() + +export function SlotScope(props: { name: string; children: JSX.Element }) { + return {props.children} +} + +export function Slot(props: { name: string; children: JSX.Element }) { + const scope = useContext(SlotName) + return {props.children} +} diff --git a/services/site/src/docs/config.ts b/services/site/src/docs/config.ts new file mode 100644 index 00000000000..19e3885149a --- /dev/null +++ b/services/site/src/docs/config.ts @@ -0,0 +1,3 @@ +// Where the docs are mounted on the site. Docs code builds its URLs from this, and MDX content reads it +// as `import.meta.env.BASE_URL` (vite.ts), as it did when the docs were their own app. +export const base = "/v2/" diff --git a/services/site/src/docs/content.ts b/services/site/src/docs/content.ts new file mode 100644 index 00000000000..7f098fdd602 --- /dev/null +++ b/services/site/src/docs/content.ts @@ -0,0 +1,20 @@ +import type { Component } from "solid-js" +import type { Frontmatter } from "./frontmatter" +import { docsSlug } from "./lib/navigation" +import type { DocsHeading } from "./rehype-headings" + +/** A compiled content page (vite.ts): its content component, frontmatter, headings and Markdown twin. */ +export type DocsModule = { + default: Component<{ components?: Record> }> + frontmatter: Frontmatter + headings: DocsHeading[] + markdown: string +} + +// Loaded on demand: the Worker serves the prerendered pages, so it never evaluates these at start-up. +const modules = import.meta.glob("./content/**/*.mdx") + +/** Every content page's loader, by slug (`index`, `config`, `cli`, `build/plugins`, …). */ +export const docsContent = new Map( + Object.entries(modules).map(([file, load]) => [docsSlug(file.slice("./content/".length)), load]), +) diff --git a/services/www/src/docs/content/agents.mdx b/services/site/src/docs/content/agents.mdx similarity index 100% rename from services/www/src/docs/content/agents.mdx rename to services/site/src/docs/content/agents.mdx diff --git a/services/www/src/docs/content/attachments.mdx b/services/site/src/docs/content/attachments.mdx similarity index 100% rename from services/www/src/docs/content/attachments.mdx rename to services/site/src/docs/content/attachments.mdx diff --git a/services/www/src/docs/content/build/client/effect.mdx b/services/site/src/docs/content/build/client/effect.mdx similarity index 100% rename from services/www/src/docs/content/build/client/effect.mdx rename to services/site/src/docs/content/build/client/effect.mdx diff --git a/services/www/src/docs/content/build/client/index.mdx b/services/site/src/docs/content/build/client/index.mdx similarity index 100% rename from services/www/src/docs/content/build/client/index.mdx rename to services/site/src/docs/content/build/client/index.mdx diff --git a/services/www/src/docs/content/build/index.mdx b/services/site/src/docs/content/build/index.mdx similarity index 100% rename from services/www/src/docs/content/build/index.mdx rename to services/site/src/docs/content/build/index.mdx diff --git a/services/www/src/docs/content/build/plugins/cli.mdx b/services/site/src/docs/content/build/plugins/cli.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/cli.mdx rename to services/site/src/docs/content/build/plugins/cli.mdx diff --git a/services/www/src/docs/content/build/plugins/effect.mdx b/services/site/src/docs/content/build/plugins/effect.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/effect.mdx rename to services/site/src/docs/content/build/plugins/effect.mdx diff --git a/services/www/src/docs/content/build/plugins/effect/rpc.mdx b/services/site/src/docs/content/build/plugins/effect/rpc.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/effect/rpc.mdx rename to services/site/src/docs/content/build/plugins/effect/rpc.mdx diff --git a/services/www/src/docs/content/build/plugins/gui/index.mdx b/services/site/src/docs/content/build/plugins/gui/index.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/gui/index.mdx rename to services/site/src/docs/content/build/plugins/gui/index.mdx diff --git a/services/www/src/docs/content/build/plugins/index.mdx b/services/site/src/docs/content/build/plugins/index.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/index.mdx rename to services/site/src/docs/content/build/plugins/index.mdx diff --git a/services/www/src/docs/content/build/plugins/migrate-v1.mdx b/services/site/src/docs/content/build/plugins/migrate-v1.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/migrate-v1.mdx rename to services/site/src/docs/content/build/plugins/migrate-v1.mdx diff --git a/services/www/src/docs/content/build/plugins/rpc.mdx b/services/site/src/docs/content/build/plugins/rpc.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/rpc.mdx rename to services/site/src/docs/content/build/plugins/rpc.mdx diff --git a/services/www/src/docs/content/build/plugins/server.mdx b/services/site/src/docs/content/build/plugins/server.mdx similarity index 100% rename from services/www/src/docs/content/build/plugins/server.mdx rename to services/site/src/docs/content/build/plugins/server.mdx diff --git a/services/www/src/docs/content/build/sdk/cloudflare.mdx b/services/site/src/docs/content/build/sdk/cloudflare.mdx similarity index 100% rename from services/www/src/docs/content/build/sdk/cloudflare.mdx rename to services/site/src/docs/content/build/sdk/cloudflare.mdx diff --git a/services/www/src/docs/content/build/sdk/effect.mdx b/services/site/src/docs/content/build/sdk/effect.mdx similarity index 100% rename from services/www/src/docs/content/build/sdk/effect.mdx rename to services/site/src/docs/content/build/sdk/effect.mdx diff --git a/services/www/src/docs/content/build/sdk/index.mdx b/services/site/src/docs/content/build/sdk/index.mdx similarity index 100% rename from services/www/src/docs/content/build/sdk/index.mdx rename to services/site/src/docs/content/build/sdk/index.mdx diff --git a/services/www/src/docs/content/claude-agent.mdx b/services/site/src/docs/content/claude-agent.mdx similarity index 100% rename from services/www/src/docs/content/claude-agent.mdx rename to services/site/src/docs/content/claude-agent.mdx diff --git a/services/www/src/docs/content/cli/acp.mdx b/services/site/src/docs/content/cli/acp.mdx similarity index 100% rename from services/www/src/docs/content/cli/acp.mdx rename to services/site/src/docs/content/cli/acp.mdx diff --git a/services/www/src/docs/content/cli/commands.mdx b/services/site/src/docs/content/cli/commands.mdx similarity index 100% rename from services/www/src/docs/content/cli/commands.mdx rename to services/site/src/docs/content/cli/commands.mdx diff --git a/services/www/src/docs/content/cli/config.mdx b/services/site/src/docs/content/cli/config.mdx similarity index 100% rename from services/www/src/docs/content/cli/config.mdx rename to services/site/src/docs/content/cli/config.mdx diff --git a/services/www/src/docs/content/cli/index.mdx b/services/site/src/docs/content/cli/index.mdx similarity index 100% rename from services/www/src/docs/content/cli/index.mdx rename to services/site/src/docs/content/cli/index.mdx diff --git a/services/www/src/docs/content/cli/keybinds.mdx b/services/site/src/docs/content/cli/keybinds.mdx similarity index 100% rename from services/www/src/docs/content/cli/keybinds.mdx rename to services/site/src/docs/content/cli/keybinds.mdx diff --git a/services/www/src/docs/content/cli/plugins.mdx b/services/site/src/docs/content/cli/plugins.mdx similarity index 100% rename from services/www/src/docs/content/cli/plugins.mdx rename to services/site/src/docs/content/cli/plugins.mdx diff --git a/services/www/src/docs/content/cli/providers.mdx b/services/site/src/docs/content/cli/providers.mdx similarity index 100% rename from services/www/src/docs/content/cli/providers.mdx rename to services/site/src/docs/content/cli/providers.mdx diff --git a/services/www/src/docs/content/cli/theme.mdx b/services/site/src/docs/content/cli/theme.mdx similarity index 100% rename from services/www/src/docs/content/cli/theme.mdx rename to services/site/src/docs/content/cli/theme.mdx diff --git a/services/www/src/docs/content/cli/tui.mdx b/services/site/src/docs/content/cli/tui.mdx similarity index 100% rename from services/www/src/docs/content/cli/tui.mdx rename to services/site/src/docs/content/cli/tui.mdx diff --git a/services/www/src/docs/content/cli/web.mdx b/services/site/src/docs/content/cli/web.mdx similarity index 100% rename from services/www/src/docs/content/cli/web.mdx rename to services/site/src/docs/content/cli/web.mdx diff --git a/services/www/src/docs/content/commands.mdx b/services/site/src/docs/content/commands.mdx similarity index 100% rename from services/www/src/docs/content/commands.mdx rename to services/site/src/docs/content/commands.mdx diff --git a/services/www/src/docs/content/compaction.mdx b/services/site/src/docs/content/compaction.mdx similarity index 100% rename from services/www/src/docs/content/compaction.mdx rename to services/site/src/docs/content/compaction.mdx diff --git a/services/www/src/docs/content/config.mdx b/services/site/src/docs/content/config.mdx similarity index 100% rename from services/www/src/docs/content/config.mdx rename to services/site/src/docs/content/config.mdx diff --git a/services/www/src/docs/content/console/api/index.mdx b/services/site/src/docs/content/console/api/index.mdx similarity index 100% rename from services/www/src/docs/content/console/api/index.mdx rename to services/site/src/docs/content/console/api/index.mdx diff --git a/services/www/src/docs/content/console/api/inference.mdx b/services/site/src/docs/content/console/api/inference.mdx similarity index 100% rename from services/www/src/docs/content/console/api/inference.mdx rename to services/site/src/docs/content/console/api/inference.mdx diff --git a/services/www/src/docs/content/console/api/providers.mdx b/services/site/src/docs/content/console/api/providers.mdx similarity index 100% rename from services/www/src/docs/content/console/api/providers.mdx rename to services/site/src/docs/content/console/api/providers.mdx diff --git a/services/www/src/docs/content/console/billing.mdx b/services/site/src/docs/content/console/billing.mdx similarity index 100% rename from services/www/src/docs/content/console/billing.mdx rename to services/site/src/docs/content/console/billing.mdx diff --git a/services/www/src/docs/content/console/budgets.mdx b/services/site/src/docs/content/console/budgets.mdx similarity index 100% rename from services/www/src/docs/content/console/budgets.mdx rename to services/site/src/docs/content/console/budgets.mdx diff --git a/services/www/src/docs/content/console/go.mdx b/services/site/src/docs/content/console/go.mdx similarity index 100% rename from services/www/src/docs/content/console/go.mdx rename to services/site/src/docs/content/console/go.mdx diff --git a/services/www/src/docs/content/console/harnesses/claude-code.mdx b/services/site/src/docs/content/console/harnesses/claude-code.mdx similarity index 100% rename from services/www/src/docs/content/console/harnesses/claude-code.mdx rename to services/site/src/docs/content/console/harnesses/claude-code.mdx diff --git a/services/www/src/docs/content/console/harnesses/codex.mdx b/services/site/src/docs/content/console/harnesses/codex.mdx similarity index 100% rename from services/www/src/docs/content/console/harnesses/codex.mdx rename to services/site/src/docs/content/console/harnesses/codex.mdx diff --git a/services/www/src/docs/content/console/harnesses/hermes.mdx b/services/site/src/docs/content/console/harnesses/hermes.mdx similarity index 100% rename from services/www/src/docs/content/console/harnesses/hermes.mdx rename to services/site/src/docs/content/console/harnesses/hermes.mdx diff --git a/services/www/src/docs/content/console/harnesses/index.mdx b/services/site/src/docs/content/console/harnesses/index.mdx similarity index 100% rename from services/www/src/docs/content/console/harnesses/index.mdx rename to services/site/src/docs/content/console/harnesses/index.mdx diff --git a/services/www/src/docs/content/console/harnesses/pi.mdx b/services/site/src/docs/content/console/harnesses/pi.mdx similarity index 100% rename from services/www/src/docs/content/console/harnesses/pi.mdx rename to services/site/src/docs/content/console/harnesses/pi.mdx diff --git a/services/www/src/docs/content/console/harnesses/raycast.mdx b/services/site/src/docs/content/console/harnesses/raycast.mdx similarity index 100% rename from services/www/src/docs/content/console/harnesses/raycast.mdx rename to services/site/src/docs/content/console/harnesses/raycast.mdx diff --git a/services/www/src/docs/content/console/index.mdx b/services/site/src/docs/content/console/index.mdx similarity index 100% rename from services/www/src/docs/content/console/index.mdx rename to services/site/src/docs/content/console/index.mdx diff --git a/services/www/src/docs/content/console/members.mdx b/services/site/src/docs/content/console/members.mdx similarity index 100% rename from services/www/src/docs/content/console/members.mdx rename to services/site/src/docs/content/console/members.mdx diff --git a/services/www/src/docs/content/console/models.mdx b/services/site/src/docs/content/console/models.mdx similarity index 100% rename from services/www/src/docs/content/console/models.mdx rename to services/site/src/docs/content/console/models.mdx diff --git a/services/www/src/docs/content/console/providers.mdx b/services/site/src/docs/content/console/providers.mdx similarity index 100% rename from services/www/src/docs/content/console/providers.mdx rename to services/site/src/docs/content/console/providers.mdx diff --git a/services/www/src/docs/content/console/scim.mdx b/services/site/src/docs/content/console/scim.mdx similarity index 100% rename from services/www/src/docs/content/console/scim.mdx rename to services/site/src/docs/content/console/scim.mdx diff --git a/services/www/src/docs/content/console/sso.mdx b/services/site/src/docs/content/console/sso.mdx similarity index 100% rename from services/www/src/docs/content/console/sso.mdx rename to services/site/src/docs/content/console/sso.mdx diff --git a/services/www/src/docs/content/console/websearch.mdx b/services/site/src/docs/content/console/websearch.mdx similarity index 100% rename from services/www/src/docs/content/console/websearch.mdx rename to services/site/src/docs/content/console/websearch.mdx diff --git a/services/www/src/docs/content/console/workspaces.mdx b/services/site/src/docs/content/console/workspaces.mdx similarity index 100% rename from services/www/src/docs/content/console/workspaces.mdx rename to services/site/src/docs/content/console/workspaces.mdx diff --git a/services/www/src/docs/content/formatters.mdx b/services/site/src/docs/content/formatters.mdx similarity index 100% rename from services/www/src/docs/content/formatters.mdx rename to services/site/src/docs/content/formatters.mdx diff --git a/services/www/src/docs/content/index.mdx b/services/site/src/docs/content/index.mdx similarity index 100% rename from services/www/src/docs/content/index.mdx rename to services/site/src/docs/content/index.mdx diff --git a/services/www/src/docs/content/instructions.mdx b/services/site/src/docs/content/instructions.mdx similarity index 100% rename from services/www/src/docs/content/instructions.mdx rename to services/site/src/docs/content/instructions.mdx diff --git a/services/www/src/docs/content/mcp-servers.mdx b/services/site/src/docs/content/mcp-servers.mdx similarity index 100% rename from services/www/src/docs/content/mcp-servers.mdx rename to services/site/src/docs/content/mcp-servers.mdx diff --git a/services/www/src/docs/content/migrate-v1.mdx b/services/site/src/docs/content/migrate-v1.mdx similarity index 100% rename from services/www/src/docs/content/migrate-v1.mdx rename to services/site/src/docs/content/migrate-v1.mdx diff --git a/services/www/src/docs/content/models.mdx b/services/site/src/docs/content/models.mdx similarity index 100% rename from services/www/src/docs/content/models.mdx rename to services/site/src/docs/content/models.mdx diff --git a/services/www/src/docs/content/network.mdx b/services/site/src/docs/content/network.mdx similarity index 100% rename from services/www/src/docs/content/network.mdx rename to services/site/src/docs/content/network.mdx diff --git a/services/www/src/docs/content/permissions.mdx b/services/site/src/docs/content/permissions.mdx similarity index 100% rename from services/www/src/docs/content/permissions.mdx rename to services/site/src/docs/content/permissions.mdx diff --git a/services/www/src/docs/content/plugins.mdx b/services/site/src/docs/content/plugins.mdx similarity index 100% rename from services/www/src/docs/content/plugins.mdx rename to services/site/src/docs/content/plugins.mdx diff --git a/services/www/src/docs/content/policies.mdx b/services/site/src/docs/content/policies.mdx similarity index 100% rename from services/www/src/docs/content/policies.mdx rename to services/site/src/docs/content/policies.mdx diff --git a/services/www/src/docs/content/providers.mdx b/services/site/src/docs/content/providers.mdx similarity index 100% rename from services/www/src/docs/content/providers.mdx rename to services/site/src/docs/content/providers.mdx diff --git a/services/www/src/docs/content/references.mdx b/services/site/src/docs/content/references.mdx similarity index 100% rename from services/www/src/docs/content/references.mdx rename to services/site/src/docs/content/references.mdx diff --git a/services/www/src/docs/content/skills.mdx b/services/site/src/docs/content/skills.mdx similarity index 100% rename from services/www/src/docs/content/skills.mdx rename to services/site/src/docs/content/skills.mdx diff --git a/services/www/src/docs/content/snapshots.mdx b/services/site/src/docs/content/snapshots.mdx similarity index 100% rename from services/www/src/docs/content/snapshots.mdx rename to services/site/src/docs/content/snapshots.mdx diff --git a/services/www/src/docs/content/themes.mdx b/services/site/src/docs/content/themes.mdx similarity index 100% rename from services/www/src/docs/content/themes.mdx rename to services/site/src/docs/content/themes.mdx diff --git a/services/www/src/docs/content/tools.mdx b/services/site/src/docs/content/tools.mdx similarity index 100% rename from services/www/src/docs/content/tools.mdx rename to services/site/src/docs/content/tools.mdx diff --git a/services/www/src/docs/content/troubleshooting.mdx b/services/site/src/docs/content/troubleshooting.mdx similarity index 100% rename from services/www/src/docs/content/troubleshooting.mdx rename to services/site/src/docs/content/troubleshooting.mdx diff --git a/services/www/src/docs/content/warming.mdx b/services/site/src/docs/content/warming.mdx similarity index 100% rename from services/www/src/docs/content/warming.mdx rename to services/site/src/docs/content/warming.mdx diff --git a/services/www/src/docs/content/websearch.mdx b/services/site/src/docs/content/websearch.mdx similarity index 100% rename from services/www/src/docs/content/websearch.mdx rename to services/site/src/docs/content/websearch.mdx diff --git a/services/site/src/docs/document.tsx b/services/site/src/docs/document.tsx new file mode 100644 index 00000000000..145a207d9f3 --- /dev/null +++ b/services/site/src/docs/document.tsx @@ -0,0 +1,54 @@ +import { NoHydration, ssr } from "solid-js/web" +import { Callout } from "./components/Callout" +import { Card } from "./components/Card" +import { CardGroup } from "./components/CardGroup" +import { CodeBlock } from "./components/CodeBlock" +import { CodeTabs } from "./components/CodeTabs" +import { OpenApiReference } from "./components/OpenApiReference" +import { PlanTabs } from "./components/PlanTabs" +import { Slot } from "./components/Slot" +import type { DocsModule } from "./content" +import { DocsLayout, type DocsAssets } from "./layouts/DocsLayout" + +/** A docs page to render: a content page and its module, or the API reference (no module). */ +export type DocsRender = { slug: string; module?: DocsModule; assets: DocsAssets } + +const components = { Callout, Card, CardGroup, CodeBlock, CodeTabs, PlanTabs, Slot } + +/** + * A whole docs page: plain HTML with the docs' own stylesheet and script, and no hydration + * (NoHydration), so it ships no framework code and none of the site's styles or scripts. + */ +export function DocsDocument(props: DocsRender) { + const module = props.module + return ( + + {ssr("") as unknown as Element} + {module ? ( + + + + ) : ( + + + + )} + + ) +} diff --git a/services/site/src/docs/frontmatter.ts b/services/site/src/docs/frontmatter.ts new file mode 100644 index 00000000000..cc2d705fe71 --- /dev/null +++ b/services/site/src/docs/frontmatter.ts @@ -0,0 +1,19 @@ +import { z } from "zod" + +// Every page's frontmatter, checked when its MDX compiles (vite.ts), so a bad page fails the build. +export const frontmatterSchema = z.object({ + title: z.string(), + description: z.string().optional(), + noindex: z.boolean().optional(), + tableOfContents: z + .union([ + z.boolean(), + z.object({ + minHeadingLevel: z.number().int().min(1).max(6).optional(), + maxHeadingLevel: z.number().int().min(1).max(6).optional(), + }), + ]) + .optional(), +}) + +export type Frontmatter = z.infer diff --git a/services/site/src/docs/layouts/DocsLayout.tsx b/services/site/src/docs/layouts/DocsLayout.tsx new file mode 100644 index 00000000000..b6386e9a310 --- /dev/null +++ b/services/site/src/docs/layouts/DocsLayout.tsx @@ -0,0 +1,139 @@ +import { Show, type JSX } from "solid-js" +import { base } from "../config" +import { DocsFooter } from "../components/DocsFooter" +import { DocsHeader } from "../components/DocsHeader" +import { DocsSidebar } from "../components/DocsSidebar" +import { DocsTableOfContents } from "../components/DocsTableOfContents" +import { docsHref, getDocsSection } from "../lib/navigation" +import type { DocsHeading } from "../rehype-headings" + +/** The URLs of the docs' stylesheet (styles/global.css) and browser script (client.ts). */ +export type DocsAssets = { stylesheet: string; script: string } + +// Before first paint: the saved theme, else the system's, and the toggle. Inline so pages never flash. +const themeScript = ` +const applyTheme = () => { + const theme = + localStorage.getItem("docs-theme") ?? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark") + document.documentElement.dataset.theme = theme + document.querySelector('meta[name="theme-color"]')?.setAttribute("content", theme === "light" ? "#ffffff" : "#000000") +} + +applyTheme() +matchMedia("(prefers-color-scheme: light)").addEventListener("change", applyTheme) +document.addEventListener("click", (event) => { + if (!(event.target instanceof Element) || !event.target.closest("[data-theme-toggle]")) return + localStorage.setItem("docs-theme", document.documentElement.dataset.theme === "light" ? "dark" : "light") + applyTheme() +}) +` + +// Pages load as plain documents (no client router). The browser prerenders a docs link once the +// pointer rests on it, and global.css turns on cross-document view transitions, so a click shows the +// next page at once, without a blank frame. +const speculationRules = JSON.stringify({ + prerender: [{ where: { href_matches: `${base}docs/*` }, eagerness: "moderate" }], +}) + +export function DocsLayout(props: { + title: string + description?: string + currentSlug: string + headings: DocsHeading[] + showTableOfContents?: boolean + fullWidth?: boolean + noindex?: boolean + assets: DocsAssets + children: JSX.Element +}) { + const section = getDocsSection(props.currentSlug) + const description = props.description ?? `${props.title} documentation for OpenCode.` + const canonical = new URL(docsHref(props.currentSlug), "https://opencode.ai").href + return ( + + + + + + + + + + diff --git a/services/www/src/docs/components/CodeTabs.astro b/services/www/src/docs/components/CodeTabs.astro deleted file mode 100644 index f3e1367a0cc..00000000000 --- a/services/www/src/docs/components/CodeTabs.astro +++ /dev/null @@ -1,89 +0,0 @@ ---- -import CodeBlock from "./CodeBlock.astro" - -interface Props { - id: string - label: string - tabs: Array<{ - label: string - code: string - }> -} ---- - -
-
- { - Astro.props.tabs.map((tab, index) => ( - - )) - } -
- { - Astro.props.tabs.map((tab, index) => ( - - )) - } -
- - diff --git a/services/www/src/docs/components/DocsFooter.astro b/services/www/src/docs/components/DocsFooter.astro deleted file mode 100644 index 49dd5e99a46..00000000000 --- a/services/www/src/docs/components/DocsFooter.astro +++ /dev/null @@ -1,31 +0,0 @@ -
- -
- - diff --git a/services/www/src/docs/components/DocsHeader.astro b/services/www/src/docs/components/DocsHeader.astro deleted file mode 100644 index a22c70fd33c..00000000000 --- a/services/www/src/docs/components/DocsHeader.astro +++ /dev/null @@ -1,130 +0,0 @@ ---- -import { docsHref, docsSections, getDocsSection } from "../lib/navigation" - -interface Props { - currentSlug: string -} - -const currentSlug = Astro.props.currentSlug -const currentSection = getDocsSection(currentSlug) -const searchEnabled = import.meta.env.PROD ---- - - - -{ - searchEnabled && ( - - -
-

Start typing to search the documentation.

-
-
- ) -} - - diff --git a/services/www/src/docs/components/DocsPage.astro b/services/www/src/docs/components/DocsPage.astro deleted file mode 100644 index eb405a064bd..00000000000 --- a/services/www/src/docs/components/DocsPage.astro +++ /dev/null @@ -1,28 +0,0 @@ ---- -import { render, type CollectionEntry } from "astro:content" -import Callout from "./Callout.astro" -import Card from "./Card.astro" -import CardGroup from "./CardGroup.astro" -import CodeBlock from "./CodeBlock.astro" -import CodeTabs from "./CodeTabs.astro" -import PlanTabs from "./PlanTabs.astro" -import DocsLayout from "../layouts/DocsLayout.astro" - -interface Props { - entry: CollectionEntry<"docs"> -} - -const entry = Astro.props.entry -const rendered = await render(entry) ---- - - - - diff --git a/services/www/src/docs/components/DocsSidebar.astro b/services/www/src/docs/components/DocsSidebar.astro deleted file mode 100644 index e167347af91..00000000000 --- a/services/www/src/docs/components/DocsSidebar.astro +++ /dev/null @@ -1,27 +0,0 @@ ---- -import { docsHref, type DocsNavGroup } from "../lib/navigation" - -interface Props { - groups: DocsNavGroup[] - currentSlug: string -} ---- - - diff --git a/services/www/src/docs/components/DocsTableOfContents.astro b/services/www/src/docs/components/DocsTableOfContents.astro deleted file mode 100644 index 4b59399ad47..00000000000 --- a/services/www/src/docs/components/DocsTableOfContents.astro +++ /dev/null @@ -1,24 +0,0 @@ ---- -import type { MarkdownHeading } from "astro" - -interface Props { - headings: MarkdownHeading[] -} - -const headings = Astro.props.headings.filter((heading) => heading.depth === 2 || heading.depth === 3) ---- - -{ - headings.length > 0 && ( - - ) -} diff --git a/services/www/src/docs/components/OpenApiReference.astro b/services/www/src/docs/components/OpenApiReference.astro deleted file mode 100644 index a5dce83d150..00000000000 --- a/services/www/src/docs/components/OpenApiReference.astro +++ /dev/null @@ -1,151 +0,0 @@ ---- -import openapi from "../../../../../packages/protocol/openapi.json" -import type { OpenApiOperation, OpenApiSchema } from "../lib/openapi" -import { openApiSchemaLabel } from "../lib/openapi" -import OpenApiSchemaView from "./OpenApiSchema.astro" - -const methods = ["get", "post", "put", "patch", "delete", "options", "head"] as const -type Method = (typeof methods)[number] - -const paths = openapi.paths as unknown as Record>> -const operations = Object.entries(paths).flatMap(([path, item]) => - methods.flatMap((method) => { - const operation = item[method] - if (!operation) return [] - return [{ ...operation, path, method, id: operation.operationId ?? `${method}-${path}`.replace(/[^a-z0-9]+/gi, "-") }] - }), -) -const groups = [...new Set(operations.map((operation) => operation.tags?.[0] ?? "other"))].map((tag) => ({ - tag, - id: `tag-${tag.replace(/[^a-z0-9]+/gi, "-").toLowerCase()}`, - description: openapi.tags.find((item) => item.name === tag)?.description, - operations: operations.filter((operation) => (operation.tags?.[0] ?? "other") === tag), -})) -const schemas = Object.entries(openapi.components.schemas) as [string, OpenApiSchema][] ---- - -
- - -
-
-

OpenAPI {openapi.openapi}

-

HTTP API

-

{openapi.info.description}

-
- {operations.length} operations - {schemas.length} schemas - OpenAPI JSON -
-
- - { - groups.map((group) => ( -
-
-

Resource

-

{group.tag}

- {group.description &&

{group.description}

} -
-
- {group.operations.map((operation) => ( -
- - {operation.method} - {operation.path} - {operation.summary} - -
- {operation.description &&

{operation.description}

} -

Operation ID{operation.operationId}

- {!!operation.parameters?.length && ( -
-

Parameters

-
- - - - {operation.parameters.map((parameter) => ( - - - - - - - ))} - -
NameLocationTypeDescription
{parameter.name}{parameter.required && required}{parameter.in}{parameter.description ?? No description}
-
-
- )} - {operation.requestBody && ( -
-

Request body {operation.requestBody.required && required}

- {operation.requestBody.description &&

{operation.requestBody.description}

} -
- {Object.entries(operation.requestBody.content ?? {}).map(([mediaType, content]) => ( -
{mediaType}
- ))} -
-
- )} -
-

Responses

-
- {Object.entries(operation.responses ?? {}).map(([status, response]) => ( -
-
- {status} - {response.description ?? "Response"} -
- {Object.entries(response.content ?? {}).map(([mediaType, content]) => ( -
{mediaType}
- ))} -
- ))} -
-
-
-
- ))} -
-
- )) - } - -
-
-

Components

-

Schemas

-

Reusable data types referenced by API operations.

-
-
- { - schemas.map(([name, schema]) => ( -
- {name}{openApiSchemaLabel(schema)} -
-
- )) - } -
-
-
-
diff --git a/services/www/src/docs/components/OpenApiSchema.astro b/services/www/src/docs/components/OpenApiSchema.astro deleted file mode 100644 index ae4d63f03aa..00000000000 --- a/services/www/src/docs/components/OpenApiSchema.astro +++ /dev/null @@ -1,69 +0,0 @@ ---- -import { formatOpenApiValue, openApiSchemaLabel, type OpenApiSchema } from "../lib/openapi" - -interface Props { - schema?: OpenApiSchema - required?: boolean -} - -const schema = Astro.props.schema -const reference = schema?.$ref?.split("/").at(-1) -const variants = schema?.anyOf ?? schema?.oneOf ?? schema?.allOf -const variantOperator = schema?.allOf ? "and" : "or" -const constraints = schema - ? [ - schema.pattern && `pattern ${schema.pattern}`, - schema.minimum !== undefined && `min ${schema.minimum}`, - schema.maximum !== undefined && `max ${schema.maximum}`, - schema.exclusiveMinimum !== undefined && `> ${schema.exclusiveMinimum}`, - schema.exclusiveMaximum !== undefined && `< ${schema.exclusiveMaximum}`, - schema.minLength !== undefined && `min length ${schema.minLength}`, - schema.maxLength !== undefined && `max length ${schema.maxLength}`, - schema.minItems !== undefined && `min items ${schema.minItems}`, - schema.maxItems !== undefined && `max items ${schema.maxItems}`, - ].filter((value): value is string => typeof value === "string") - : [] ---- - -{ - reference ? ( - {reference} - ) : ( -
-
- {openApiSchemaLabel(schema)} - {Astro.props.required && required} - {schema?.deprecated && deprecated} -
- {schema?.description &&

{schema.description}

} - {schema?.enum &&
Values{schema.enum.map(formatOpenApiValue).join(" | ")}
} - {schema?.const !== undefined &&
Constant{formatOpenApiValue(schema.const)}
} - {schema?.default !== undefined &&
Default{formatOpenApiValue(schema.default)}
} - {constraints.length > 0 &&
{constraints.join(", ")}
} - {variants && ( -
- {variants.map((variant, index) => ( -
- {index > 0 && {variantOperator}} - -
- ))} -
- )} - {schema?.type === "array" && schema.items &&
Items
} - {schema?.properties && ( -
- {Object.entries(schema.properties).map(([name, property]) => ( -
- {name} - -
- ))} -
- )} - {typeof schema?.additionalProperties === "object" && ( -
Additional properties
- )} -
- ) -} diff --git a/services/www/src/docs/components/PlanTabs.astro b/services/www/src/docs/components/PlanTabs.astro deleted file mode 100644 index 021a22cf882..00000000000 --- a/services/www/src/docs/components/PlanTabs.astro +++ /dev/null @@ -1,104 +0,0 @@ ---- -interface Props { - id: string - label: string - syncKey: string -} - -const plans = [ - { id: "go", label: "Go" }, - { id: "go-plus", label: "Go Plus" }, - { id: "go-teams", label: "Go Teams" }, -] as const ---- - -
-
- { - plans.map((plan, index) => ( - - )) - } -
- { - plans.map((plan, index) => ( - - )) - } -
- - diff --git a/services/www/src/docs/layouts/DocsLayout.astro b/services/www/src/docs/layouts/DocsLayout.astro deleted file mode 100644 index e536187773b..00000000000 --- a/services/www/src/docs/layouts/DocsLayout.astro +++ /dev/null @@ -1,137 +0,0 @@ ---- -import type { MarkdownHeading } from "astro" -import { ClientRouter } from "astro:transitions" -import DocsFooter from "../components/DocsFooter.astro" -import DocsHeader from "../components/DocsHeader.astro" -import DocsSidebar from "../components/DocsSidebar.astro" -import DocsTableOfContents from "../components/DocsTableOfContents.astro" -import { docsHref, getDocsSection } from "../lib/navigation" -import "../styles/global.css" - -interface Props { - title: string - description?: string - currentSlug: string - headings: MarkdownHeading[] - showTableOfContents?: boolean - fullWidth?: boolean - noindex?: boolean -} - -const section = getDocsSection(Astro.props.currentSlug) -const fullWidth = Astro.props.fullWidth ?? false -const description = Astro.props.description ?? `${Astro.props.title} documentation for OpenCode.` -const canonical = new URL(docsHref(Astro.props.currentSlug), "https://opencode.ai").href ---- - - - - - - - - {Astro.props.noindex && } - - - - - - {Astro.props.title} | OpenCode - - - - - { - fullWidth ? ( -
- -
- ) : ( - <> -
- {section.title} navigation - -
-
- -
-
-

- {Astro.props.title} - -

- -
-
- -
- - ) - } - - - - - diff --git a/services/www/src/pages/docs-index/[...slug].astro b/services/www/src/pages/docs-index/[...slug].astro deleted file mode 100644 index 75e76648ce0..00000000000 --- a/services/www/src/pages/docs-index/[...slug].astro +++ /dev/null @@ -1,21 +0,0 @@ ---- -import { getCollection, type CollectionEntry } from "astro:content" -import DocsPage from "../../docs/components/DocsPage.astro" - -interface Props { - entry: CollectionEntry<"docs"> -} - -export async function getStaticPaths() { - return (await getCollection("docs")).map((entry) => ({ - params: { - slug: entry.id === "index" ? undefined : entry.id.replace(/\/index$/, ""), - }, - props: { entry }, - })) -} - -export const prerender = true ---- - - diff --git a/services/www/src/pages/docs/[...slug].astro b/services/www/src/pages/docs/[...slug].astro deleted file mode 100644 index fb59d343835..00000000000 --- a/services/www/src/pages/docs/[...slug].astro +++ /dev/null @@ -1,26 +0,0 @@ ---- -import { getEntry } from "astro:content" -import DocsPage from "../../docs/components/DocsPage.astro" - -export const prerender = false - -const slug = Astro.params.slug || "index" -const entry = (await getEntry("docs", slug)) ?? (await getEntry("docs", `${slug}/index`)) -if (!entry) return new Response("Not found", { status: 404, headers: { "Cache-Control": "no-store" } }) - -const headers = { - "Cache-Control": "public, max-age=300, stale-while-revalidate=3600", - Vary: "Accept", -} - -const accept = Astro.request.headers.get("accept") ?? "" -if (accept.includes("text/markdown") || accept.includes("text/x-markdown")) { - return new Response(`# ${entry.data.title}\n\n${entry.body ?? ""}`, { - headers: { ...headers, "Content-Type": "text/markdown; charset=utf-8" }, - }) -} - -Object.entries(headers).forEach(([name, value]) => Astro.response.headers.set(name, value)) ---- - - diff --git a/services/www/src/pages/docs/api/index.astro b/services/www/src/pages/docs/api/index.astro deleted file mode 100644 index 6caa95b4893..00000000000 --- a/services/www/src/pages/docs/api/index.astro +++ /dev/null @@ -1,17 +0,0 @@ ---- -import OpenApiReference from "../../../docs/components/OpenApiReference.astro" -import DocsLayout from "../../../docs/layouts/DocsLayout.astro" - -export const prerender = true ---- - - - - diff --git a/services/www/src/pages/docs/console/inference.astro b/services/www/src/pages/docs/console/inference.astro deleted file mode 100644 index 658cfdf1e90..00000000000 --- a/services/www/src/pages/docs/console/inference.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -export const prerender = false - -return Astro.redirect(`${import.meta.env.BASE_URL}docs/console/api/inference/`, 301) ---- diff --git a/services/www/src/pages/index.astro b/services/www/src/pages/index.astro deleted file mode 100644 index b3db27662b0..00000000000 --- a/services/www/src/pages/index.astro +++ /dev/null @@ -1,5 +0,0 @@ ---- -export const prerender = false - -return Astro.redirect(`${import.meta.env.BASE_URL}docs/`, 301) ---- diff --git a/services/www/src/pages/install.astro b/services/www/src/pages/install.astro deleted file mode 100644 index 2f98a0d41bc..00000000000 --- a/services/www/src/pages/install.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -export const prerender = false - -const response = await fetch("https://raw.githubusercontent.com/anomalyco/opencode/dev/install") -const headers = new Headers(response.headers) -headers.set("Cache-Control", "no-store") - -return new Response(response.body, { - status: response.status, - statusText: response.statusText, - headers, -}) ---- diff --git a/services/www/src/pages/llms.txt.ts b/services/www/src/pages/llms.txt.ts deleted file mode 100644 index b16bc5c77a2..00000000000 --- a/services/www/src/pages/llms.txt.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { APIRoute } from "astro" -import { renderLlmsTxt } from "../docs/lib/llms" - -export const prerender = true - -export const GET: APIRoute = ({ site }) => - new Response(renderLlmsTxt(site ?? new URL("https://opencode.ai")), { - headers: { "Content-Type": "text/plain; charset=utf-8" }, - }) diff --git a/services/www/src/pages/sitemap.xml.ts b/services/www/src/pages/sitemap.xml.ts deleted file mode 100644 index 01323fb1c74..00000000000 --- a/services/www/src/pages/sitemap.xml.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { APIRoute } from "astro" -import { renderSitemap } from "../docs/lib/sitemap" - -export const prerender = true - -export const GET: APIRoute = ({ site }) => - new Response(renderSitemap(site ?? new URL("https://opencode.ai")), { - headers: { "Content-Type": "application/xml; charset=utf-8" }, - }) diff --git a/services/www/tsconfig.json b/services/www/tsconfig.json deleted file mode 100644 index 295edc76cfb..00000000000 --- a/services/www/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "astro/tsconfigs/strict", - "compilerOptions": { - "noUnusedLocals": true, - "types": ["bun"] - } -} diff --git a/services/www/wrangler.jsonc b/services/www/wrangler.jsonc deleted file mode 100644 index 5c3da434fb2..00000000000 --- a/services/www/wrangler.jsonc +++ /dev/null @@ -1,36 +0,0 @@ -{ - "$schema": "node_modules/wrangler/config-schema.json", - "name": "opencode-www", - "compatibility_date": "2026-07-25", - "workers_dev": false, - "preview_urls": false, - "cache": { - "enabled": true, - }, - "assets": { - "binding": "ASSETS", - "directory": "./dist/client", - "html_handling": "drop-trailing-slash", - "not_found_handling": "404-page", - }, - "env": { - "dev": { - "name": "opencode-www-dev", - "routes": [ - { - "pattern": "dev.opencode.ai/v2*", - "zone_name": "opencode.ai", - }, - ], - }, - "production": { - "name": "opencode-www-production", - "routes": [ - { - "pattern": "opencode.ai/v2*", - "zone_name": "opencode.ai", - }, - ], - }, - }, -}