Skip to content
Closed
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
64 changes: 62 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ on:
tags:
- "v*.*.*"

permissions:
contents: write
permissions: {}

jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand All @@ -27,3 +28,62 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}

sync-docs:
runs-on: ubuntu-latest
needs: release
steps:
- name: Checkout qcloud-cli
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: qcloud-cli
persist-credentials: false

- name: Checkout landing_page
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
repository: qdrant/landing_page
path: landing_page
token: ${{ secrets.LANDING_PAGE_PAT }}
persist-credentials: true

- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0
with:
version: 2026.3.8

- name: Generate command reference
working-directory: qcloud-cli
run: make docs

- name: Sync docs into landing_page
run: |
python3 qcloud-cli/scripts/sync_landing_docs.py \
qcloud-cli/docs/reference \
landing_page/qdrant-landing/content/documentation/cloud-cli/reference

- name: Open PR against landing_page
working-directory: landing_page
env:
GH_TOKEN: ${{ secrets.LANDING_PAGE_PAT }}
REF_NAME: ${{ github.ref_name }}
run: |
if git diff --quiet -- qdrant-landing/content/documentation/cloud-cli/reference; then
echo "no changes to sync"
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

branch="qcloud-cli-docs-sync-${REF_NAME}"
git checkout -b "$branch"
git add qdrant-landing/content/documentation/cloud-cli/reference
git commit -m "docs: sync qcloud CLI command reference for ${REF_NAME}"
git push -u origin "$branch" --force

gh pr create \
--title "docs: sync qcloud CLI command reference for ${REF_NAME}" \
--body "Auto-generated from [qdrant/qcloud-cli@${REF_NAME}](https://github.com/qdrant/qcloud-cli/releases/tag/${REF_NAME}) by the release workflow." \
--head "$branch" \
--base main \
|| echo "PR already exists for $branch"
135 changes: 135 additions & 0 deletions scripts/sync_landing_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3

"""Convert cobra-generated docs/reference/*.md into Hugo pages for the
landing_page repo's Qdrant Cloud CLI reference section.

Usage:
python3 scripts/sync_landing_docs.py <src_dir> <dest_dir>

<src_dir> is docs/reference (output of `make docs`)
<dest_dir> is the landing_page repo's
qdrant-landing/content/documentation/cloud-cli/reference directory

The destination directory is fully regenerated on every run (existing
generated files are replaced, stale ones removed) so it always mirrors the
current command tree.
"""

import re
import sys
from pathlib import Path

LINK_RE = re.compile(r"\[([^\]]+)\]\((qcloud[\w.-]*)\.md\)")


def slug_for(filename: str) -> str:
"""qcloud_cluster_create.md -> qcloud_cluster_create"""
return filename[:-3]


def rewrite_links(text: str) -> str:
def repl(match: re.Match[str]):
label, target = match.group(1), match.group(2)
if target == "qcloud":
return f"[{label}](/documentation/cloud-cli/reference/)"
return f"[{label}](/documentation/cloud-cli/reference/{target}/)"

return LINK_RE.sub(repl, text)


def demote_headings(text: str) -> str:
"""## -> #, ### -> ##, etc. so the page owns a single H1 title."""
out_lines: list[str] = []
for line in text.splitlines():
if line.startswith("#"):
hashes = len(line) - len(line.lstrip("#"))
out_lines.append(line[1:] if hashes > 1 else line)
else:
out_lines.append(line)
return "\n".join(out_lines)


def annotate_code_fences(text: str) -> str:
"""Bare ``` fences don't render as code blocks on the landing page;
tag every opening fence as bash (cobra only emits usage/example/flag
blocks, which are all shell-ish)."""
out_lines: list[str] = []
in_fence = False
for line in text.splitlines():
if line.strip() == "```":
out_lines.append("```bash" if not in_fence else "```")
in_fence = not in_fence
else:
out_lines.append(line)
return "\n".join(out_lines)


def convert(src_path: Path) -> tuple[str, str, str]:
"""Return (title, description, body) for one generated doc file."""
lines = src_path.read_text().splitlines()

title_line = lines[0]
assert title_line.startswith("## "), f"unexpected heading in {src_path}"
title = title_line[3:].strip()

description = ""
for line in lines[1:]:
stripped = line.strip()
if stripped:
description = stripped
break

body = "\n".join(lines)
body = demote_headings(body)
body = rewrite_links(body)
body = annotate_code_fences(body)
return title, description, body


def frontmatter(title: str, description: str, weight: int, extra: str = "") -> str:
short = description if len(description) <= 120 else description[:117].rstrip() + "..."
return (
"---\n"
f"title: {title}\n"
f"short_description: \"{short}\"\n"
f"description: \"{description}\"\n"
f"weight: {weight}\n"
f"{extra}"
"---\n\n"
)


def main():
if len(sys.argv) != 3:
print(__doc__)
sys.exit(1)

src_dir = Path(sys.argv[1])
dest_dir = Path(sys.argv[2])
dest_dir.mkdir(parents=True, exist_ok=True)

for existing in dest_dir.glob("*.md"):
existing.unlink()

files = sorted(p for p in src_dir.glob("*.md"))
if not files:
print(f"no markdown files found in {src_dir}", file=sys.stderr)
sys.exit(1)

for i, src in enumerate(files):
title, description, body = convert(src)

if src.name == "qcloud.md":
dest = dest_dir / "_index.md"
fm = frontmatter("Command Reference", description, 0)
else:
dest = dest_dir / src.name
fm = frontmatter(title, description, i + 1)

_ = dest.write_text(fm + body + "\n")

print(f"wrote {len(files)} reference pages to {dest_dir}")


if __name__ == "__main__":
main()