diff --git a/automation/source-repo-templates/api-docs.dotnet.yml b/automation/source-repo-templates/api-docs.dotnet.yml index 4ff18d4c..4b052e8a 100644 --- a/automation/source-repo-templates/api-docs.dotnet.yml +++ b/automation/source-repo-templates/api-docs.dotnet.yml @@ -158,7 +158,7 @@ jobs: - name: Write top-level index # DefaultDocumentation emits one tree per assembly. Assemble # a small README.mdx at the top of the api/ folder listing - # the available packages. + # the available packages with their pinned versions. # # Emit .mdx (not .md) because Mintlify's docs.json nav # resolver only matches .mdx for page ids; without this, @@ -171,21 +171,51 @@ jobs: # parents fails broken-links validation when the parent is # .mdx). Users navigate to per-package pages via the URL bar # or future programmatic _pages.json nav splice. + # + # Version next to each project is read from / + # in the .csproj. Falls back to "unknown" so + # missing tags don't break the doc build. run: | - { - echo "# ResQ .NET SDK" - echo "" - echo "Auto-generated reference for the public packages in" - echo "[resq-software/dotnet-sdk](https://github.com/resq-software/dotnet-sdk)." - echo "" - echo "## Packages" - echo "" - for proj in $PUBLIC_PROJECTS; do - if [ -d "$OUTPUT_DIR/$proj" ]; then - echo "- \`$proj\`" - fi - done - } > "$OUTPUT_DIR/README.mdx" + python3 - <<'PY' > "$OUTPUT_DIR/README.mdx" + import os + import pathlib + import re + + ref_name = os.environ.get("DOCS_REF_NAME", "main") + repo = os.environ.get("GITHUB_REPOSITORY", "") + output_dir = pathlib.Path(os.environ["OUTPUT_DIR"]) + projects = (os.environ.get("PUBLIC_PROJECTS") or "").split() + + def read_version(proj: str) -> str: + # .csproj files are usually /.csproj + # but DefaultDocumentation expects the assembly name as the + # output dir, so search for a .csproj matching the project. + candidates = list(pathlib.Path(".").rglob(f"{proj}.csproj")) + if not candidates: + return "unknown" + text = candidates[0].read_text(encoding="utf-8") + for tag in ("Version", "VersionPrefix"): + m = re.search(rf"<{tag}>([^<]+)", text) + if m: + return m.group(1).strip() + return "unknown" + + print("# ResQ .NET SDK") + print() + print( + f"Auto-generated reference for " + f"[`{repo}`](https://github.com/{repo}) " + f"at ref `{ref_name}`." + ) + print() + print("## Packages") + print() + for proj in projects: + if not (output_dir / proj).is_dir(): + continue + version = read_version(proj) + print(f"- `{proj}` — `v{version}`") + PY - name: Drop hash from constructor filenames + references # DefaultDocumentation names constructor pages after the IL diff --git a/automation/source-repo-templates/api-docs.python.yml b/automation/source-repo-templates/api-docs.python.yml index d2f1ee98..72735ec4 100644 --- a/automation/source-repo-templates/api-docs.python.yml +++ b/automation/source-repo-templates/api-docs.python.yml @@ -226,7 +226,7 @@ jobs: - name: Write top-level index # pydoc-markdown emits an overview.md per package. Stitch # a small README.mdx at the top of the api/ folder listing - # the available packages. + # the available packages with their pinned versions. # # Emit .mdx (not .md) because Mintlify's docs.json nav # resolver only matches .mdx for page ids; without this, @@ -242,24 +242,49 @@ jobs: # # Package list is sorted alphabetically by directory name # so it stays in sync with `_pages.json` (which - # `find ... | sort` produces below). + # `find ... | sort` produces below). The version next to + # each package is read from its pyproject.toml so the + # rendered docs can be cross-referenced against a specific + # PyPI release; the workflow ref (`DOCS_REF_NAME`) is shown + # at the top so tag-triggered runs are clearly identified. run: | - { - echo "# ResQ Python SDK" - echo "" - echo "You can use this auto-generated reference for the public packages in" - echo "[\`${{ github.repository }}\`](https://github.com/${{ github.repository }})." - echo "" - echo "## Packages" - echo "" - for entry in $PUBLIC_PACKAGES; do - echo "${entry%%:*}" - done | sort | while read -r pkg_dir; do - if [ -d "$OUTPUT_DIR/$pkg_dir" ]; then - echo "- \`$pkg_dir\`" - fi - done - } > "$OUTPUT_DIR/README.mdx" + python3 - <<'PY' > "$OUTPUT_DIR/README.mdx" + import os + import pathlib + import re + + ref_name = os.environ.get("DOCS_REF_NAME", "main") + repo = os.environ.get("GITHUB_REPOSITORY", "") + output_dir = pathlib.Path(os.environ["OUTPUT_DIR"]) + public = (os.environ.get("PUBLIC_PACKAGES") or "").split() + + def read_version(pkg_dir: str) -> str: + # Best-effort version lookup so a parse failure doesn't + # break the whole doc build — fall back to "unknown". + pyproject = pathlib.Path("packages") / pkg_dir / "pyproject.toml" + if not pyproject.exists(): + return "unknown" + text = pyproject.read_text(encoding="utf-8") + m = re.search(r'^\s*version\s*=\s*"([^"]+)"', text, re.M) + return m.group(1) if m else "unknown" + + print("# ResQ Python SDK") + print() + print( + f"Auto-generated API reference for " + f"[`{repo}`](https://github.com/{repo}) " + f"at ref `{ref_name}`." + ) + print() + print("## Packages") + print() + entries = sorted(e.split(":", 1)[0] for e in public) + for pkg_dir in entries: + if not (output_dir / pkg_dir).is_dir(): + continue + version = read_version(pkg_dir) + print(f"- `{pkg_dir}` — `v{version}`") + PY - name: Prefix bare-filename intra-page links with ./ # Mintlify rejects bare-filename .md links as broken; diff --git a/automation/source-repo-templates/api-docs.typescript.yml b/automation/source-repo-templates/api-docs.typescript.yml index 4ab91770..d680e588 100644 --- a/automation/source-repo-templates/api-docs.typescript.yml +++ b/automation/source-repo-templates/api-docs.typescript.yml @@ -130,11 +130,21 @@ jobs: # `[components/X](X/README.md)` module list down to plain # backticked text. Users navigate to specific pages via # the URL bar. + # + # Prepend a version banner so users browsing the rendered + # docs can identify which release the reference describes. + # Version is read from package.json at the package root; + # ref is the workflow's input ref (tag for tag-triggered + # runs). working-directory: ${{ env.PKG_DIR }}/generated-docs + env: + PKG_REF: ${{ inputs.ref || github.ref_name }} + GH_REPO: ${{ github.repository }} run: | if [ -f README.md ]; then python3 - <<'PY' - import pathlib, re + import json, os, pathlib, re + INTERNAL_LINK = re.compile(r'\[([^\]]+)\]\((?!https?://|mailto:|#)[^)]+\)') p = pathlib.Path("README.md") text = p.read_text(encoding="utf-8") @@ -142,6 +152,30 @@ jobs: # Collapse accidental double-backticks from already- # backticked link text. text = text.replace("``", "`") + + ref_name = os.environ.get("PKG_REF", "main") + repo = os.environ.get("GH_REPO", "") + # package.json is one level up from generated-docs. + pkg_json = pathlib.Path("..") / "package.json" + version = "unknown" + if pkg_json.exists(): + try: + version = json.loads(pkg_json.read_text(encoding="utf-8")).get("version", "unknown") + except json.JSONDecodeError: + pass + + banner = ( + f"> **Version:** `v{version}` · **Ref:** `{ref_name}` · " + f"**Source:** [`{repo}`](https://github.com/{repo})\n\n" + ) + # Insert banner after the first H1 (typedoc emits `# ui` + # or similar). If there's no leading H1, prepend. + h1_match = re.match(r'(# [^\n]+\n+)', text) + if h1_match: + text = text[:h1_match.end()] + banner + text[h1_match.end():] + else: + text = banner + text + pathlib.Path("README.mdx").write_text(text, encoding="utf-8") p.unlink() PY