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
20 changes: 10 additions & 10 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ jobs:
with:
persist-credentials: false

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: "3.13"

# The generators use Jinja for raw Markdown and PyYAML for structured frontmatter.
- name: Install linter dependencies
run: pip install pyyaml==6.0.3 jinja2==3.1.6
- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1

- name: Script tests
run: python -m unittest discover -s tests -p "test_*.py"
run: uv run --frozen python -m unittest discover -s tests -p "test_*.py"

- name: Lint documentation
run: python scripts/lint_docs.py --baseline
run: uv run --frozen python scripts/lint_docs.py --baseline

- name: Build documentation
env:
GA_MEASUREMENT_ID: ""
run: uv run --frozen python scripts/build_docs.py

# Advisory: shows the full Phase 0 backlog without affecting the job result.
- name: Backlog (informational)
if: always()
run: python scripts/lint_docs.py || true
run: uv run --frozen python scripts/lint_docs.py || true
170 changes: 170 additions & 0 deletions .github/workflows/pr-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
name: PR Preview

on:
pull_request:
types: [opened, synchronize, reopened, closed]

permissions:
contents: read

jobs:
preview:
if: >-
github.event.repository.private == true &&
vars.ENABLE_PR_PREVIEWS == 'true' &&
vars.PAGES_PREVIEW_ORIGIN != '' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
concurrency:
group: "pages"
cancel-in-progress: false
permissions:
contents: write # Update gh-pages-previews branch.
pages: write # Deploy GitHub Pages bundle.
id-token: write # Mint OIDC token for actions/deploy-pages.
pull-requests: write # Post sticky preview link on PR.
environment:
name: github-pages
url: ${{ steps.comment.outputs.preview_url }}
steps:
- if: github.event.action != 'closed'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false

- if: github.event.action != 'closed'
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1

- name: Build preview
if: github.event.action != 'closed'
env:
SITE_URL: "${{ vars.PAGES_PREVIEW_ORIGIN }}/pr-${{ github.event.pull_request.number }}"
COLAB_REPO: ${{ github.repository }}
COLAB_BRANCH: ${{ github.event.pull_request.head.ref }}
run: uv run --frozen python scripts/build_docs.py

- name: Update preview branch
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
EVENT_ACTION: ${{ github.event.action }}
run: |
set -euo pipefail
[[ "${PR_NUMBER}" =~ ^[1-9][0-9]*$ ]]
[[ "${GH_REPO}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]

OPEN_PRS="$(gh api --paginate "repos/${GH_REPO}/pulls?state=open&per_page=100" --jq '.[].number')"
AUTH_B64="$(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 -w0)"
export GIT_TERMINAL_PROMPT=0
export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0="http.https://github.com/.extraheader"
export GIT_CONFIG_VALUE_0="AUTHORIZATION: basic ${AUTH_B64}"
REMOTE="https://github.com/${GH_REPO}.git"

rm -rf _pages
if ! git clone --depth=1 --single-branch --branch gh-pages-previews "${REMOTE}" _pages 2>/dev/null; then
rm -rf _pages
git init _pages
git -C _pages remote add origin "${REMOTE}"
git -C _pages checkout --orphan gh-pages-previews
fi

for dir in _pages/pr-*; do
[[ -d "${dir}" ]] || continue
num="${dir#_pages/pr-}"
if ! [[ "${num}" =~ ^[1-9][0-9]*$ ]] || [[ "${num}" == "${PR_NUMBER}" ]] || ! grep -Fxq -- "${num}" <<<"${OPEN_PRS}"; then
rm -rf -- "${dir}"
fi
done
if [[ "${EVENT_ACTION}" != "closed" ]]; then
mv site "_pages/pr-${PR_NUMBER}"
fi

{
printf '<!doctype html><meta charset="utf-8"><meta name="robots" content="noindex,nofollow">'
printf '<title>PR Previews</title><h1>PR Previews (%s)</h1><ul>' "${GH_REPO}"
found=0
for dir in _pages/pr-*; do
[[ -d "${dir}" ]] || continue
num="${dir#_pages/pr-}"
[[ "${num}" =~ ^[1-9][0-9]*$ ]] || continue
printf '<li><a href="./pr-%s/">PR #%s</a></li>' "${num}" "${num}"
found=1
done
[[ "${found}" -eq 1 ]] || printf '<li>None</li>'
printf '</ul>\n'
} > _pages/index.html
: > _pages/.nojekyll
printf 'User-agent: *\nDisallow: /\n' > _pages/robots.txt

git -C _pages config user.name "github-actions[bot]"
git -C _pages config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git -C _pages add -A
if [[ -n "$(git -C _pages status --porcelain)" ]]; then
git -C _pages commit -m "PR #${PR_NUMBER} (${EVENT_ACTION})"
git -C _pages push origin HEAD:refs/heads/gh-pages-previews
fi
rm -rf _pages/.git

- uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
with:
path: _pages

- id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5

- name: Update PR comment
id: comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
EVENT_ACTION: ${{ github.event.action }}
PAGE_URL: ${{ steps.deployment.outputs.page_url }}
run: |
python3 - <<'PY'
import json, os, subprocess

pr_num = int(os.environ["PR_NUMBER"])
action = os.environ["EVENT_ACTION"].strip()
gh_repo = os.environ["GH_REPO"].strip()
base_url = os.environ["PAGE_URL"].strip().rstrip("/")
preview_url = f"{base_url}/pr-{pr_num}/"
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"preview_url={preview_url if action != 'closed' else base_url + '/'}\n")

marker = "<!-- docs-pr-preview -->"
if action == "closed":
body = f"{marker}\nPreview `/pr-{pr_num}/` removed (PR closed)."
else:
sha = os.environ["HEAD_SHA"].strip()[:7]
pages_raw = subprocess.check_output(
["gh", "api", "--paginate", "--slurp", f"repos/{gh_repo}/pulls/{pr_num}/files?per_page=100"],
text=True,
)
routes = []
for f in (item for p in (json.loads(pages_raw) if pages_raw.strip() else []) for item in p):
name = str(f.get("filename", ""))
if f.get("status") == "removed" or not name.startswith("docs/") or not name.endswith(".md") or name.startswith("docs/contributing/"):
continue
rel = name[5:-3]
route = "" if rel == "index" else (f"{rel[:-6]}/" if rel.endswith("/index") else f"{rel}/")
routes.append(f"- `{name}` → {preview_url}{route}")
lines = [marker, f"**PR Preview:** {preview_url} (`{sha}`)"]
if routes:
lines.extend(["", "**Modified pages:**", *routes[:20]])
if len(routes) > 20:
lines.append(f"- *…and {len(routes) - 20} more*")
body = "\n".join(lines)

comments_raw = subprocess.check_output(
["gh", "api", "--paginate", "--slurp", f"repos/{gh_repo}/issues/{pr_num}/comments?per_page=100"],
text=True,
)
comments = [c for p in (json.loads(comments_raw) if comments_raw.strip() else []) for c in p]
cid = next((c["id"] for c in comments if isinstance(c, dict) and marker in str(c.get("body", ""))), None)
endpoint = f"repos/{gh_repo}/issues/comments/{cid}" if cid else f"repos/{gh_repo}/issues/{pr_num}/comments"
subprocess.run(["gh", "api", "--method", "PATCH" if cid else "POST", endpoint, "--input", "-"], input=json.dumps({"body": body}), text=True, check=True)
PY
89 changes: 89 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: Publish

on:
workflow_dispatch:
inputs:
source_branch:
description: "Source branch name"
required: true
type: string
commit_message:
description: "Commit message"
required: true
type: string

permissions:
contents: read

concurrency:
group: publish
cancel-in-progress: false

jobs:
publish:
if: github.event.repository.private == true
runs-on: ubuntu-latest
environment: publish
steps:
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
id: app-token
with:
client-id: ${{ vars.PUBLISHER_APP_CLIENT_ID }}
private-key: ${{ secrets.PUBLISHER_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: "docs,${{ github.event.repository.name }}"
permission-contents: write
permission-pull-requests: write

- name: Verify approval and push squashed commit
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
SOURCE_REPO: ${{ github.repository }}
REPO_OWNER: ${{ github.repository_owner }}
SOURCE_BRANCH: ${{ inputs.source_branch }}
COMMIT_MESSAGE: ${{ inputs.commit_message }}
ACTOR_LOGIN: ${{ github.actor }}
ACTOR_ID: ${{ github.actor_id }}
run: |
set -euo pipefail
[[ "${SOURCE_REPO}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ && "${SOURCE_REPO}" != "google-gemma/docs" ]]
[[ "${SOURCE_BRANCH}" != "main" && "${SOURCE_BRANCH}" =~ ^[A-Za-z0-9_./-]+$ ]]
git check-ref-format --branch "${SOURCE_BRANCH}" >/dev/null
[[ -n "${COMMIT_MESSAGE// /}" ]]

git init repo && cd repo
AUTH_B64="$(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 -w0)"
git config "http.https://github.com/.extraheader" "AUTHORIZATION: basic ${AUTH_B64}"
git remote add upstream https://github.com/google-gemma/docs.git
git remote add origin "https://github.com/${SOURCE_REPO}.git"
git fetch --no-tags upstream main
git fetch --no-tags origin "refs/heads/${SOURCE_BRANCH}:refs/remotes/origin/target"
UPSTREAM_SHA="$(git rev-parse refs/remotes/upstream/main)"
TARGET_SHA="$(git rev-parse refs/remotes/origin/target)"
git merge-base --is-ancestor "${UPSTREAM_SHA}" "${TARGET_SHA}"
[[ "$(git rev-parse "${TARGET_SHA}^{tree}")" != "$(git rev-parse "${UPSTREAM_SHA}^{tree}")" ]]

PR_JSON="$(gh api -X GET "repos/${SOURCE_REPO}/pulls" -f state=open -f base=main -f "head=${REPO_OWNER}:${SOURCE_BRANCH}")"
PR_NUMBER="$(printf '%s' "${PR_JSON}" | jq -r '.[0].number // empty')"
PR_AUTHOR="$(printf '%s' "${PR_JSON}" | jq -r '.[0].user.login // empty')"
PR_HEAD="$(printf '%s' "${PR_JSON}" | jq -r '.[0].head.sha // empty')"
[[ -n "${PR_NUMBER}" && "${PR_HEAD}" == "${TARGET_SHA}" ]]

REVIEWS_JSON="$(gh api --paginate --slurp "repos/${SOURCE_REPO}/pulls/${PR_NUMBER}/reviews?per_page=100")"
APPROVERS="$(printf '%s' "${REVIEWS_JSON}" | jq -r --arg a "${PR_AUTHOR}" \
'flatten | map(select(.user.login != null and .user.login != $a and (.state == "APPROVED" or .state == "CHANGES_REQUESTED" or .state == "DISMISSED"))) | group_by(.user.login) | map(sort_by(.id) | last) | .[] | select(.state == "APPROVED") | .user.login')"
VALID_LGTM=0
for reviewer in ${APPROVERS}; do
perm="$(gh api "repos/${SOURCE_REPO}/collaborators/${reviewer}/permission" --jq '.permission')"
case "${perm}" in admin|maintain|write) VALID_LGTM=1; break ;; esac
done
[[ "${VALID_LGTM}" -ge 1 ]]

export GIT_AUTHOR_NAME="${ACTOR_LOGIN}"
export GIT_AUTHOR_EMAIL="${ACTOR_ID}+${ACTOR_LOGIN}@users.noreply.github.com"
export GIT_COMMITTER_NAME="${GIT_AUTHOR_NAME}"
export GIT_COMMITTER_EMAIL="${GIT_AUTHOR_EMAIL}"
SQUASH_SHA="$(git commit-tree "${TARGET_SHA}^{tree}" -p "${UPSTREAM_SHA}" -m "${COMMIT_MESSAGE}")"
git push upstream "${SQUASH_SHA}:refs/heads/main" --ff-only
git push origin "${SQUASH_SHA}:refs/heads/main" --ff-only
gh api -X PATCH "repos/${SOURCE_REPO}/pulls/${PR_NUMBER}" -f state=closed
42 changes: 42 additions & 0 deletions .github/workflows/sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Sync

on:
schedule:
- cron: "0 6 * * *"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: publish
cancel-in-progress: false

jobs:
sync:
if: github.event.repository.private == true
runs-on: ubuntu-latest
environment: publish
steps:
- uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
id: app-token
with:
client-id: ${{ vars.PUBLISHER_APP_CLIENT_ID }}
private-key: ${{ secrets.PUBLISHER_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: "docs,${{ github.event.repository.name }}"
permission-contents: write
permission-workflows: write

- name: Fast-forward main
env:
GH_APP_TOKEN: ${{ steps.app-token.outputs.token }}
TARGET_REPO: ${{ github.repository }}
run: |
set -euo pipefail
[[ "${TARGET_REPO}" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ && "${TARGET_REPO}" != "google-gemma/docs" ]]
git init repo && cd repo
AUTH_B64="$(printf 'x-access-token:%s' "${GH_APP_TOKEN}" | base64 -w0)"
git config "http.https://github.com/.extraheader" "AUTHORIZATION: basic ${AUTH_B64}"
git fetch --no-tags https://github.com/google-gemma/docs.git main:refs/remotes/upstream/main
git push "https://github.com/${TARGET_REPO}.git" refs/remotes/upstream/main:refs/heads/main --ff-only
2 changes: 1 addition & 1 deletion overrides/main.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
{% endif %}
<meta name="description" content="{{ meta_description | e }}">
{% endif %}
{% if page and page.meta and page.meta.search and page.meta.search.exclude %}
{% if (page and page.meta and page.meta.search and page.meta.search.exclude) or (config.site_url and (config.site_url | trim("/")) != "https://gemma.ai") %}
<meta name="robots" content="noindex, nofollow">
{% else %}
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1">
Expand Down
5 changes: 3 additions & 2 deletions overrides/partials/page-context.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
{%- set requires = page.meta.requires if page.meta and page.meta.requires else "" -%}
{%- set frameworks = page.meta.frameworks if page.meta and page.meta.frameworks else [] -%}
{%- set colab = page.meta.colab if page.meta and page.meta.colab else "" -%}
{%- set markdown_url = ("/" ~ (page.url | trim("/")) ~ ".md.txt") if page and page.url else "" -%}
{%- set markdown_url = (((page.url | trim("/")) ~ ".md.txt") | url) if page and page.url else "" -%}
{%- set ns = namespace(families=[]) -%}
{%- for id in models -%}
{%- set family = id | split("/") | first -%}
Expand All @@ -37,7 +37,8 @@
{%- for family in ns.families %}
{%- set url = gemma_model_urls[family] | default("") %}
{%- if url %}
<a class="page-context__value page-context__value--link" href="{{ url }}">{{ gemma_model_names[family] | default(family) }}</a>
{%- set resolved_url = (((url | trim("/")) ~ "/") | url) if url[:1] == "/" else url %}
<a class="page-context__value page-context__value--link" href="{{ resolved_url }}">{{ gemma_model_names[family] | default(family) }}</a>
{%- else %}
<span class="page-context__value">{{ gemma_model_names[family] | default(family) }}</span>
{%- endif %}
Expand Down
Loading
Loading