Files
datastore/ci/jobs/docs_job_mintlify.py

176 lines
7.2 KiB
Python

import argparse
import shlex
from ci.jobs.scripts.docs.check_autogenerated_regions import (
check_autogenerated_regions,
)
from ci.jobs.scripts.docs.check_readonly_copies import check_readonly_copies
from ci.jobs.scripts.docs.mintlify_docs_check import (
DEFAULT_CHECKS,
LOCALE_CHECKS,
QUICKSTARTS_CHECK,
)
from ci.praktika.info import Info
from ci.praktika.result import Result
from ci.praktika.utils import Shell, Utils
# The translated trees that docs.json ships (via `languages` + `$ref`s): both the
# top-level locale pages and the localized snippet components they render. The
# locale checks run only when a PR touches one of these folders -- or the
# checkers themselves -- so ordinary English-only edits don't pay for them.
LOCALE_DIRS = ["ar", "es", "fr", "ja", "ko", "pt-BR", "ru", "zh"]
LOCALE_CHECK_TRIGGERS = tuple(f"docs/{d}/" for d in LOCALE_DIRS) + (
"ci/jobs/scripts/docs/lychee_check.py",
"ci/jobs/scripts/docs/locale_components_check.py",
"docs/lychee.toml",
# Shared inputs the locale resolvers depend on, so a PR that touches them can
# break localized navigation and must re-run the locale checks even when no
# docs/<locale>/ page changed:
# - all of snippets/ (locale pages import/link both localized snippets and
# shared English snippets, e.g. /snippets/delete);
# - redirect sources, which count as valid locale link targets (e.g.
# /<locale>/integrations resolves only via a redirect).
"docs/snippets/",
"docs/_site/redirects.json",
)
# The quickstarts check (frontmatter tags + generated explorer data freshness)
# runs only when a PR touches a quickstarts tree, the generated data modules,
# the generator, or the checker itself.
QUICKSTARTS_CHECK_TRIGGERS = (
("docs/get-started/quickstarts/",)
+ tuple(f"docs/{d}/get-started/quickstarts/" for d in LOCALE_DIRS)
+ ("docs/snippets/components/QuickStartsGrid/",)
+ tuple(f"docs/snippets/{d}/components/QuickStartsGrid/" for d in LOCALE_DIRS)
+ (
"docs/_site/scripts/update_quickstarts.py",
"ci/jobs/scripts/docs/quickstarts_check.py",
)
)
def parse_args():
parser = argparse.ArgumentParser(description="Mintlify Docs Check Job")
parser.add_argument("--test", help="Sub check name", default="")
return parser.parse_args()
def _locale_check_should_run():
# Run the locale link-check only when the PR changes a locale tree or the
# checker. If the changed-file list is unavailable, run it (fail open toward
# coverage: a missed run would let broken locale links merge).
changed_files = Info().get_changed_files()
if changed_files is None:
return True
return any(f.startswith(LOCALE_CHECK_TRIGGERS) for f in changed_files)
def _quickstarts_check_should_run():
# Same gating idea as the locale checks: run only when the PR touches the
# quickstarts content or its tooling; fail open toward coverage when the
# changed-file list is unavailable.
changed_files = Info().get_changed_files()
if changed_files is None:
return True
return any(f.startswith(QUICKSTARTS_CHECK_TRIGGERS) for f in changed_files)
def _protected_docs_guard():
# Fail direct edits both to generated regions and to docs folders whose
# canonical source lives in another repo. This is aggregator-only, so it is
# deliberately not part of the shared DEFAULT_CHECKS.
info = Info()
changed_files = info.get_changed_files()
if changed_files is None:
# Fail close: without the changed-file list we cannot prove the PR does
# not touch read-only copies, so do not report success.
print("Error: the changed-file list is unavailable, cannot run the check.")
return False
# Generated output may intentionally update protected marker regions, but
# it must never modify one-way copies owned by another repository.
readonly_copies_ok = check_readonly_copies(changed_files)
if "pr-autogenerated-docs" in info.pr_labels:
print("OK: skipping the generated-region guard for autogenerated docs.")
return readonly_copies_ok
merge_base = info.get_kv_data("merge_base_commit_sha")
if not merge_base or info.pr_number <= 0 or not info.sha:
print("Error: the PR revisions are unavailable, cannot run the check.")
return False
# The checkout can be shallow and contain neither the merge base nor the
# actual PR head when GitHub checked out its synthetic merge commit. Fetch
# both revisions explicitly so the comparison does not depend on checkout
# depth or on whether merge-commit checkout is enabled.
pull_ref = f"refs/pull/{info.pr_number}/head"
if not Shell.check(
"git fetch --no-tags --filter=blob:none origin "
f"{shlex.quote(merge_base)} {shlex.quote(pull_ref)}",
verbose=True,
):
print("Error: could not fetch the PR revisions, cannot run the check.")
return False
autogenerated_regions_ok = check_autogenerated_regions(merge_base, info.sha)
return readonly_copies_ok and autogenerated_regions_ok
if __name__ == "__main__":
args = parse_args()
testpattern = args.test
docs_dir = f"{Utils.cwd()}/docs"
def selected(name):
# Case-insensitive substring match; an empty pattern runs every check.
return testpattern.lower() in name.lower()
# The mint check definitions are shared with the standalone driver
# (ci/jobs/scripts/docs/mintlify_docs_check.py). This job already runs inside
# the docs-builder image with the docs present natively, so it runs them
# directly; add new checks to DEFAULT_CHECKS, not here. --test selects a
# subset by sub-check name.
results = [
Result.from_commands_run(name=name, command=command, workdir=docs_dir)
for name, command in DEFAULT_CHECKS
if selected(name)
]
# Locale checks: blocking, but only when the PR touches a locale tree,
# a localized snippet, or a checker. This is how a GT translation PR that
# reintroduces broken/unlocalized locale links gets caught before merge.
if _locale_check_should_run():
for locale_name, locale_command in LOCALE_CHECKS:
if selected(locale_name):
results.append(
Result.from_commands_run(
name=locale_name, command=locale_command, workdir=docs_dir
)
)
# Quickstarts check: blocking, but only when the PR touches the quickstarts
# trees or their tooling. Validates frontmatter tags and fails when the
# generated explorer data was not regenerated and committed.
if _quickstarts_check_should_run():
qs_name, qs_command = QUICKSTARTS_CHECK
if selected(qs_name):
results.append(
Result.from_commands_run(
name=qs_name, command=qs_command, workdir=docs_dir
)
)
# These guards run from the repo root (not the docs root), so keep them out
# of the docs_dir loop above.
if selected("No direct edits to generated or read-only docs"):
results.append(
Result.from_commands_run(
name="No direct edits to generated or read-only docs",
command=_protected_docs_guard,
)
)
Result.create_from(results=results).complete_job()