Files
docs/sites/docs
Lunny Xiaoandbircni 3258717d62 fix(api): show the name of a type in the reference, document the gmail app password (#541)
Two unrelated but small fixes, closes #258 and closes #32.

**The reference does not name its types (#258)**

Since the api reference is rendered from the openapi 3.0 document, a field of type `ReviewStateType` is rendered as the bare string enum the `$ref` points at: starlight-openapi dereferences everything and the schema has no `title`, so the name of the type is nowhere on the page. Searching the current reference for `ReviewStateType` returns nothing, which is what the issue reports (the plugin also has no route for `components/schemas`, so there is no page per type either).

`normalizeSchema()` now titles every named schema of an openapi 3.0 document with its own name, which the plugin renders next to the type of a field:

- before: `state` · `string` · Allowed values: APPROVED …
- after: `state` · `ReviewStateType` · `string` · Allowed values: APPROVED …

The go doc comments start with the name of the type, so the description would repeat it ("ChangeFilesOptions — ChangeFilesOptions options for creating …"); a leading `<name> ` is stripped from the description.

For the record, how the three generations of documents describe that type:

| Versions | Type name | Allowed values |
| --- | --- | --- |
| 1.22 – 1.25 (swagger, `ReviewStateType` definition without `enum`) | shown | missing |
| 1.26 – 1.27 (swagger, enum inlined upstream) | missing | shown |
| 1.27, next (openapi 3.0, with this change) | shown | shown |

The released swagger documents are frozen, so 1.22 – 1.26 keep what they have; from 1.27 on the reference answers the question the issue asks.

**Gmail needs an app password (#32)**

The configuration in the guide is still correct after Google turned off password-only access, because it already asks for an app password. What was missing is why there is no alternative: Gitea's mailer authenticates with `CRAM-MD5`, `PLAIN` or `LOGIN` and has no XOAUTH2, so "Sign in with Google" is not an option. Added that, together with the two things people run into — an app password needs 2-step verification and a Workspace administrator can disable app passwords for the domain — and the `587` + `smtp+starttls` variant.

Verified by building the api and the next docs: the type names show up on the operation pages, the descriptions are not duplicated, and 1.26 and older are unchanged. `pnpm check` passes.

<!-- cloudflare-preview --> Preview: https://pr-541.docs-gitea-com.pages.dev

---------

Co-authored-by: bircni <[email protected]>
Reviewed-on: https://gitea.com/gitea/docs/pulls/541
Reviewed-by: bircni <[email protected]>
Co-authored-by: Lunny Xiao <[email protected]>
2026-09-12 11:27:21 +00:00
..

Astro + Starlight site

Renders the gitea documentation with Starlight: the docs (6 versions x 3 languages), the runner docs (4 series) and the api reference (7 swagger documents). The enterprise docs stay in their own deployment and are linked from the header.

The content directories are the ones the site has always used — docs/, versioned_docs/, i18n/, runner-docs/, runner-docs_versioned_docs/ and static/swagger-*.json (plus static/openapi3-*.json since gitea 1.27) — so the release scripts and the translation workflow stay unchanged.

Running it

Everything is driven from the repository root, see the README.md there:

make serve-fast    # english, the version served at the root, starts in seconds
make serve         # whole matrix, api included
make serve-built   # build and serve it, the only way to try the search locally
make build
make check

GITEA_DOCS_PRODUCTS, GITEA_DOCS_VERSIONS and GITEA_DOCS_LOCALES (comma separated) restrict the matrix, which is what make serve-fast uses.

How it is put together

packages/content-loader holds the product matrix and the astro content loader. src/products.ts is the single source of truth for product x version x language and drives everything else: content loading, sidebars, the version and language pickers, the version banner, the search facets and the api schemas.

Source Route
versioned_docs/version-1.27/ /
docs/ /next/
versioned_docs/version-1.26/ /1.26/
i18n/zh-cn/docusaurus-plugin-content-docs/version-1.27/ /zh-cn/
runner-docs_versioned_docs/version-3/ /runner/
runner-docs/ /runner/develop/
static/openapi3-27.json /api/
static/openapi3-latest.json /api/next/
static/swagger-26.json /api/1.26/

The loader reads the markdown in two passes. The first one works out the route of every file, the second normalizes and renders it:

  • substitutes the version variables (@version@, @dockerVersion@, ...)
  • lifts the leading # heading into the starlight title
  • maps sidebar_position to sidebar.order and honours the docusaurus slug, including the translations that moved a page
  • rewrites the relative *.md links to urls, resolving them against the file and against the version root, and reports the ones that do not resolve
  • turns the docusaurus admonitions into starlight asides (:::warning to :::caution, :::info to :::note, :::note Title to :::note[Title])
  • recreates the index pages of _category_.json (/category/installation/, /usage/actions/)

Sidebars are built per (product, version, language) from _category_.json and from the docusaurus sidebar files (sidebars.js, versioned_sidebars/, runner-sidebars.js, runner-docs_versioned_sidebars/), and selected in src/routeData.ts, since starlight only supports a single static sidebar.

Set GITEA_DOCS_STRICT_LINKS=true to fail the build on a broken internal link instead of warning.

Products and languages

The docs are the site itself, so their language comes first: /zh-cn/1.26/. The runner and the api are separate products and keep their language inside the product, /runner/zh-cn/3/, which is reserved: both are published in english only today. Starlight builds a fallback page in every configured language for every english page, so /zh-cn/runner/ would exist; the post build integration removes those directories and cloudflare/_redirects sends them to the english page.

The version served at the root of a product has no number in its url: docs 1.27 is /, runner 3 is /runner/ and api 1.27 is /api/. /1.27/, /runner/3/ and /api/1.27/ are redirected onto them in cloudflare/_redirects.

Api reference

starlight-openapi generates a page per operation from the documents update_api_docs.sh maintains: the openapi 3.0 document for the versions that have one (gitea >= 1.27), the swagger 2.0 one for the older versions. Both describe the same api and use the same operation ids, so the urls of the operation pages do not depend on which one is rendered. Three adjustments happen at build time:

  • the swagger 2.0 documents set basePath to the full https://gitea.com/api/v1 url, which redoc accepted but is not valid swagger 2.0. A normalized copy is written to .cache/openapi/ instead of touching the sources. The openapi 3.0 documents keep that url in servers, where it is valid, and only get the operation ids and the tag descriptions adjusted.
  • both documents of a version are linked for download from its overview page, see specDownloads() in src/config/api.ts and src/components/ApiOverviewTags.astro.
  • the plugin slugifies the base path, so /api/1.26/ is generated as /api/126/. src/integrations/postbuild.ts renames the directories and rewrites the links once the build is done.

Algolia docsearch, enabled when PUBLIC_DOCSEARCH_APP_ID and PUBLIC_DOCSEARCH_API_KEY are set, pagefind otherwise, so a fork without credentials still gets search. Every page carries docsearch:product, docsearch:version and docsearch:language meta tags; the crawler turns them into facets and src/config/docsearch.ts filters the search on the product, version and language being read. cloudflare/docsearch-crawler.json holds the crawler configuration and only indexes the versions people read.

Pagefind builds one index for the whole site, so the same scope has to be applied on its side as well, otherwise a search started in the 1.23 docs answers with the pages of the latest release:

  • src/components/MarkdownContent.astro wraps the content in the data-pagefind-filter elements that put product and version into the index. One filter per element, pagefind reads the whole attribute as a single name:value pair.
  • the same scope is written to the gitea:search-filters meta tag by src/lib/search.ts.
  • starlight builds the search ui with a build time configuration and has no option for per page filters, so its @pagefind/default-ui import is redirected to the subclass in src/lib/pagefind-ui.ts by the gitea-pagefind-filters plugin in astro.config.mjs. It selects the filters of the page the modal was opened on and adds the "Search all versions" checkbox, which drops them again and labels the results with their version. A starlight upgrade that moves the import fails the build, the gitea-pagefind-filters-check integration verifies that the redirect ran.

Deployment

cloudflare/_headers and cloudflare/_redirects are copied next to the build output by the publish workflow. /enterprise/* is routed to the enterprise pages project by a Cloudflare worker maintained outside this repository.

Differences between pnpm dev and the deployed site

  • /api/1.26/ is served at /api/126/, the renaming happens after the build.
  • /zh-cn/runner/ still answers, the fallback pages are removed after the build.
  • /1.27/, /runner/3/ and /api/1.27/ are cloudflare redirects, so they only work on the deployed site.
  • search is built by pagefind at build time and only answers on the built site, unless the algolia docsearch credentials are set; make serve-built builds and serves it locally.
  • a scoped run only builds part of the matrix, so the products and versions left out answer with the 404 page; pnpm dev serves everything.