Files
docs/packages/content-loader/src/products.ts
T
2026-10-19 04:58:10 +00:00

331 lines
9.4 KiB
TypeScript

/**
* Single source of truth for the product x version x language matrix.
*
* Everything else (content loading, sidebars, the version and language
* pickers, search facets and the sitemap) is derived from this file, so
* adding a version or a language is a one line change here.
*/
export type LocaleId = 'en-us' | 'zh-cn' | 'zh-tw';
export type ProductId = 'docs' | 'api' | 'runner' | 'enterprise';
/** Values substituted for `@name@` placeholders in the markdown sources. */
export interface VersionVariables {
goVersion: string;
minGoVersion: string;
minNodeVersion: string;
version: string;
sourceVersion: string;
sourceBranch: string;
dockerVersion: string;
displayVersion: string;
}
export interface VersionDef {
/** Stable identifier, e.g. `1.27`, `next`, `3`, `develop`. */
id: string;
/** Label shown in the version picker. */
label: string;
/**
* Route segment inserted after the product base. Empty for the version
* served at the product root (docs 1.27 at `/`, runner 3 at `/runner/`).
*/
path: string;
/** Marks the version served at the product root. */
latest?: boolean;
/** Starlight banner shown on every page of the version. */
banner?: 'unreleased' | 'unmaintained';
/** Content directories, relative to the repository root, per language. */
sources: Partial<Record<LocaleId, string>>;
/**
* Swagger 2.0 document, relative to the repository root (api product only).
* Every documented gitea version ships one.
*/
schema?: string;
/**
* OpenAPI 3.0 document, relative to the repository root (api product only).
* Gitea generates it since 1.27; it is what gets rendered and what the code
* generators that rejected the swagger 2.0 document can read.
*/
openapi3?: string;
variables?: VersionVariables;
}
export interface ProductDef {
id: ProductId;
label: string;
/** Route segment shared by every version, empty for the docs product. */
base: string;
/** Languages the product is published in, in display order. */
locales: LocaleId[];
versions: VersionDef[];
/** Recreate the `_category_.json` index pages for the product. */
categoryPages?: boolean;
/** Set for products served by another deployment (enterprise). */
externalBaseUrl?: string;
}
export const locales: Record<LocaleId, { label: string; lang: string }> = {
'en-us': { label: 'English', lang: 'en-US' },
'zh-cn': { label: '简体中文', lang: 'zh-CN' },
'zh-tw': { label: '繁體中文', lang: 'zh-TW' },
};
export const defaultLocale: LocaleId = 'en-us';
const variables: Record<string, VersionVariables> = {
next: {
goVersion: '1.27',
minGoVersion: '1.27',
minNodeVersion: '22',
version: 'main-nightly',
sourceVersion: 'main',
sourceBranch: 'main',
dockerVersion: 'main-nightly',
displayVersion: '29-dev',
},
'28': {
goVersion: '1.27',
minGoVersion: '1.27',
minNodeVersion: '22',
version: '28.2.0',
sourceVersion: 'v28.2.0',
sourceBranch: 'release/v28',
dockerVersion: '28.2.0',
displayVersion: '28.2.0',
},
'1.27': {
goVersion: '1.26',
minGoVersion: '1.26',
minNodeVersion: '24',
version: '1.27.3',
sourceVersion: 'v1.27.3',
sourceBranch: 'release/v1.27',
dockerVersion: '1.27.3',
displayVersion: '1.27.3',
},
'1.26': {
goVersion: '1.26',
minGoVersion: '1.26',
minNodeVersion: '22',
version: '1.26.4',
sourceVersion: 'v1.26.4',
sourceBranch: 'release/v1.26',
dockerVersion: '1.26.4',
displayVersion: '1.26.4',
},
'1.25': {
goVersion: '1.25',
minGoVersion: '1.25',
minNodeVersion: '22',
version: '1.25.5',
sourceVersion: 'v1.25.0',
sourceBranch: 'release/v1.25',
dockerVersion: '1.25.5',
displayVersion: '1.25.5',
},
'1.24': {
goVersion: '1.24',
minGoVersion: '1.24',
minNodeVersion: '22',
version: '1.24.7',
sourceVersion: 'v1.24.0',
sourceBranch: 'release/v1.24',
dockerVersion: '1.24.7',
displayVersion: '1.24.7',
},
'1.23': {
goVersion: '1.23',
minGoVersion: '1.22',
minNodeVersion: '18',
version: '1.23.8',
sourceVersion: 'v1.23.8',
sourceBranch: 'release/v1.23',
dockerVersion: '1.23.8',
displayVersion: '1.23.8',
},
'1.22': {
goVersion: '1.22',
minGoVersion: '1.22',
minNodeVersion: '18',
version: '1.22.6',
sourceVersion: 'v1.22.6',
sourceBranch: 'release/v1.22',
dockerVersion: '1.22.6',
displayVersion: '1.22.6',
},
};
/** Localized content of the gitea docs, as laid out under `i18n/<locale>/docs`. */
function docsSources(version: 'current' | string): VersionDef['sources'] {
const dir = version === 'current' ? 'docs' : `versioned_docs/version-${version}`;
const i18nDir = (locale: LocaleId) =>
`i18n/${locale}/docs/${
version === 'current' ? 'current' : `version-${version}`
}`;
return {
'en-us': dir,
'zh-cn': i18nDir('zh-cn'),
'zh-tw': i18nDir('zh-tw'),
};
}
const docsProduct: ProductDef = {
id: 'docs',
label: 'Docs',
base: '',
locales: ['en-us', 'zh-cn', 'zh-tw'],
categoryPages: true,
versions: [
{
id: 'next',
label: variables.next.displayVersion,
path: 'next',
banner: 'unreleased',
sources: docsSources('current'),
variables: variables.next,
},
{
id: '28',
label: variables['28'].displayVersion,
path: '',
latest: true,
sources: docsSources('28'),
variables: variables['28'],
},
...['1.27', '1.26', '1.25', '1.24', '1.23', '1.22'].map(
(id): VersionDef => ({
id,
label: variables[id]!.displayVersion,
path: id,
sources: docsSources(id),
variables: variables[id],
}),
),
],
};
/**
* Runner docs: one directory per release series, english only for now.
* The label is the newest release of the series, which is also the tag the
* generated reference pages of that directory come from, so it has to be
* bumped here when a new runner release is documented.
*/
const runnerSeries: { id: string; latestRelease: string }[] = [
{ id: '3', latestRelease: '3.3.0' },
{ id: '2', latestRelease: '2.3.0' },
{ id: '1', latestRelease: '1.0.8' },
{ id: '0', latestRelease: '0.6.1' },
];
const runnerProduct: ProductDef = {
id: 'runner',
label: 'Runner',
base: 'runner',
locales: ['en-us'],
versions: [
{
id: 'develop',
label: 'develop',
path: 'develop',
banner: 'unreleased',
sources: { 'en-us': 'runner-docs' },
},
...runnerSeries.map(
({ id, latestRelease }, index): VersionDef => ({
id,
label: latestRelease,
path: index === 0 ? '' : id,
latest: index === 0,
sources: { 'en-us': `runner-docs_versioned_docs/version-${id}` },
}),
),
],
};
/** API reference, rendered from the swagger documents by starlight-openapi. */
const apiProduct: ProductDef = {
id: 'api',
label: 'API',
base: 'api',
locales: ['en-us'],
versions: [
{
id: 'next',
label: variables.next.displayVersion,
path: 'next',
banner: 'unreleased',
sources: {},
schema: 'static/swagger-latest.json',
openapi3: 'static/openapi3-latest.json',
},
{
id: '28',
label: variables['28'].displayVersion,
path: '',
latest: true,
sources: {},
schema: 'static/swagger-28.json',
openapi3: 'static/openapi3-28.json',
},
{
id: '1.27',
label: variables['1.27'].displayVersion,
path: '1.27',
sources: {},
schema: 'static/swagger-27.json',
openapi3: 'static/openapi3-27.json',
},
...['1.26', '1.25', '1.24', '1.23', '1.22'].map(
(id): VersionDef => ({
id,
label: variables[id]!.displayVersion,
path: id,
sources: {},
schema: `static/swagger-${id.split('.')[1]}.json`,
}),
),
],
};
/** Served by a separate cloudflare pages project, linked from the header. */
const enterpriseProduct: ProductDef = {
id: 'enterprise',
label: 'Enterprise',
base: 'enterprise',
locales: ['en-us', 'zh-cn'],
versions: [],
externalBaseUrl: 'https://docs.gitea.com/enterprise',
};
export const products: ProductDef[] = [docsProduct, apiProduct, runnerProduct, enterpriseProduct];
export function getProduct(id: ProductId): ProductDef {
const product = products.find((candidate) => candidate.id === id);
if (!product) throw new Error(`unknown product: ${id}`);
return product;
}
export function getVersion(productId: ProductId, versionId: string): VersionDef {
const version = getProduct(productId).versions.find((candidate) => candidate.id === versionId);
if (!version) throw new Error(`unknown version: ${productId}@${versionId}`);
return version;
}
/**
* Route prefix of a (product, version, locale) triple, without leading or
* trailing slash: `""`, `next`, `1.26`, `zh-cn/next`, `runner/2`.
*
* The docs are the site itself and keep the language in front, as starlight
* expects. The other products are self contained, so their language goes inside
* the product: `/runner/zh-cn/3/`, not `/zh-cn/runner/3/`. Only english is
* published for them today, the layout is reserved for when that changes.
*/
export function routePrefix(product: ProductDef, version: VersionDef, locale: LocaleId): string {
const language = locale === defaultLocale ? '' : locale;
return product.base
? [product.base, language, version.path].filter(Boolean).join('/')
: [language, version.path].filter(Boolean).join('/');
}