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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/scripts/sync_code_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@
from pathlib import Path


# Generated from OpenHands/enterprise-cookbook, whose code-block paths are
# relative to each example, not to software-agent-sdk.
EXCLUDED_DIRS = {"cookbook"}


def find_mdx_files(docs_path: Path) -> list[Path]:
"""Find all MDX files in the docs directory."""
mdx_files: list[Path] = []
for root, _, files in os.walk(docs_path):
for root, dirs, files in os.walk(docs_path):
if Path(root) == docs_path:
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
for file in files:
if file.endswith(".mdx"):
mdx_files.append(Path(root) / file)
Expand Down
30 changes: 30 additions & 0 deletions .github/workflows/cookbook-generated.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Cookbook is generated

# Everything under cookbook/ is generated from OpenHands/enterprise-cookbook by its
# docs-preview and docs-publish workflows, which push only cookbook-preview/* and
# cookbook-sync branches. Any other change would be overwritten by the next sync.
on:
pull_request:
paths:
- 'cookbook/**'

permissions:
contents: read

jobs:
guard:
name: Cookbook pages come from enterprise-cookbook
runs-on: ubuntu-latest
steps:
- name: Allow only the enterprise-cookbook sync branches
env:
HEAD_REF: ${{ github.head_ref }}
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
run: |
if [ "$HEAD_REPO" = "$GITHUB_REPOSITORY" ]; then
case "$HEAD_REF" in
cookbook-sync|cookbook-preview/*) echo "Generated update from OpenHands/enterprise-cookbook."; exit 0 ;;
esac
fi
echo "::error::Pages under cookbook/ are generated from OpenHands/enterprise-cookbook. Change the example's README.md or example.yaml there instead: https://github.com/OpenHands/enterprise-cookbook/tree/main/tools/docs-render"
exit 1
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ Workflow: `.github/workflows/sync-agent-sdk-openapi.yml`
- Runs the agent-server OpenAPI generator
- Updates `openapi/agent-sdk.json` via an automated PR

### 4) Cookbook tab generated from `OpenHands/enterprise-cookbook`

Every page under `cookbook/` and the `Cookbook` tab in `docs.json` are generated from example READMEs in
`OpenHands/enterprise-cookbook` by its `tools/docs-render` converter. **Do not edit them here**; change the
example's `README.md` or `example.yaml` in that repository.

- Each enterprise-cookbook PR gets a draft `cookbook-preview/pr-<N>` PR here for its Mintlify preview. These
are never merged and close with the source PR.
- Merged changes arrive in a single `cookbook-sync` PR, opened by `openhands-release-bot` and refreshed nightly.
- `.github/workflows/cookbook-generated.yml` fails PRs from any other branch that touch `cookbook/`, and
`sync_code_blocks.py` skips `cookbook/` because its code-block paths are relative to each example.

## Docs writing conventions

- Most pages are `.mdx` with frontmatter:
Expand Down
20 changes: 19 additions & 1 deletion tests/test_sync_code_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
# Add the script directory to path for imports
sys.path.insert(0, str(Path(__file__).parent.parent / ".github" / "scripts"))

from sync_code_blocks import escape_embedded_backticks, extract_code_blocks, normalize_content
from sync_code_blocks import (
escape_embedded_backticks,
extract_code_blocks,
find_mdx_files,
normalize_content,
)


class TestEscapeEmbeddedBackticks:
Expand Down Expand Up @@ -171,3 +176,16 @@ def test_normalizes_line_endings(self):
# splitlines() handles all line ending types
lines = result.split('\n')
assert len(lines) == 3


def test_find_mdx_files_skips_generated_cookbook(tmp_path):
"""cookbook/ is generated from enterprise-cookbook and must not be rewritten."""
(tmp_path / "sdk" / "cookbook").mkdir(parents=True)
(tmp_path / "sdk" / "page.mdx").write_text("x")
(tmp_path / "sdk" / "cookbook" / "nested.mdx").write_text("x")
(tmp_path / "cookbook").mkdir()
(tmp_path / "cookbook" / "example.mdx").write_text("x")

found = {p.relative_to(tmp_path).as_posix() for p in find_mdx_files(tmp_path)}

assert found == {"sdk/page.mdx", "sdk/cookbook/nested.mdx"}
Loading