Skip to content
Open
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
38 changes: 38 additions & 0 deletions .changeset/fix-api-reference-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
"@alauda/doom": minor
---

Fix a family of API-reference rendering defects where the components ignored facts already present in the CRD / OpenAPI sources, plus add offline integrity tooling.

**Correctness — endpoints and schema now read the source instead of guessing:**

- `<K8sAPI>` derives `namespaced` from the CRD's `spec.scope` instead of always defaulting to `true`, so `Cluster`-scoped resources no longer render an unreachable `/namespaces/{namespace}/` path. The `namespaced` prop still overrides.
- Endpoint paths use the resource's real plural from `spec.names.plural` instead of guessing with `pluralize(kind)`, fixing hyphenated (`vpc-egress-gateways`) and irregular (`alaudaloadbalancer2`) plurals. A new `plural` prop is an escape hatch for OpenAPI-sourced resources. (`toLocaleLowerCase` → `toLowerCase`.)
- When a page does not pass `apiVersion`, a multi-version CRD now renders the version `kubectl` resolves to — the highest-priority `served` version (GA > beta > alpha, apimachinery ordering) — instead of `spec.versions[0]`. This never publishes a `served: false` version. Both the schema and the endpoint-path version now come from this single resolved version. Configurable via `api.crdVersion: 'preferred' | 'storage' | 'first'`.
- `<K8sAPI>` no longer renders endpoint paths it cannot derive. When an OpenAPI schema carries no `x-kubernetes-group-version-kind` — aggregation-layer documents routinely omit it — and no CRD backs the name, the group, version and kind used to fall back to empty strings and concatenate into `/api//` and `/api///{name}`, shipping a broken path on a green build. The endpoints section is now omitted, with a `console.error` naming the props to declare; the schema still renders. `apiVersion` + `apiKind` (plus `apiGroup` outside the core group) make the page render endpoints again.
- `/status` (and the new `/scale`) endpoints follow what the source declares, not whether the schema happens to contain a `status` property — fixing both fabricated `/status` endpoints and missing ones. A CRD declares the subresource in `spec.versions[].subresources`; an OpenAPI document declares it by routing it, so its `paths` decide, and a document that routes the resource without a `/status` route renders none. A document that does not route the resource at all says nothing either way, so the schema property stays the fallback there. The new `hasStatus` prop overrides both.
- `x-kubernetes-int-or-string` fields (carried under `anyOf`) now render their type (`integer|string`) instead of an empty cell.

**Anchors and badges:**

- Array-item schema sections (`.spec.foo[]`) no longer collide with their parent (`.spec.foo`) on the same HTML id; schema headings use a page-level stateful slugger, so every property section is uniquely addressable (HTML id uniqueness / WCAG 4.1.1).
- OpenAPI operations without a `summary` no longer produce `id="undefined"` / `href="#undefined"` or bare numeric anchors; the heading id is derived from the method (and summary when present).
- The `<OpenAPIPath>` Request Body **required** badge reads `requestBody.required` (the boolean on the request body) instead of the body schema's list of required _properties_.

**New configuration and tooling:**

- `api.references` accepts an object form `{ href, routePath?: string | false }`, separating the link href from the page-identity key used to decide inline expansion. Plain string values are unchanged. `routePath: false` explicitly means "always link, never expand".
- `translate.copyOnlyDirectories` overrides which directories are copied instead of translated (default unchanged).
- New `doom api check` command: an offline validation of the local CRD / OpenAPI sources — every file parses, CRDs have the right kind and a unique name, filenames follow the `<group>_<plural>.yaml` convention, and OpenAPI definitions do not conflict across files.
- New `no-unresolved-api-ref` lint rule flags `<K8sAPI>` / `<K8sCrd>` / `<OpenAPIRef>` / `<OpenAPIPath>` / `<K8sPermissionTable>` references that cannot be resolved, before they ship as blank pages. It also flags a `<K8sAPI>` whose group, version and kind can be derived from neither the schema nor a CRD nor explicit props, so that failure surfaces at lint time instead of as a missing endpoints section.
- Deterministic source ordering: schema files are sorted, and the `filepath` / `openapiPath` map key no longer depends on `process.cwd()`, so pinning a source is stable across working directories and `<OpenAPIPath>` uses a consistent first-match.

**Other:**

- `<K8sPermissionTable>` renders a visible "not found" row instead of silently dropping an unresolved function.
- API component chrome (`Property`, `Type`, `Description`, `Required`, `Specification`, `API Endpoints`, `HTTP method`, `Common Parameters`, `Request Body`, `Response`, …) is now translated via `useTranslation` (en/zh/ru) instead of hardcoded English.
- API reference pages surface their top-level properties in the page outline (previously a two-line TOC), while deeper nested properties stay excluded.

> **Downstream impact:** the scope / plural / version / status fixes change the rendered endpoints on already-published pages (measured: immutable-infra-docs 8, asm-docs 4, aml-docs 4, acp-docs 3, plus fabricated `/status` across ~19 pages). `namespaced` (`docs/*/usage/api.md`) and the CRD default-version behavior were documented public defaults; downstream docs should re-review their API pages after upgrading.
>
> The OpenAPI-branch `/status` change was measured against the full consumer corpus (222 `<K8sAPI>` / `<K8sCrd>` tags across 11 repositories; 26 resolve to OpenAPI schemas, all of them in acp-docs): **no page changes**, because every document that routes one of those resources also routes its `/status`. The two pages whose documents do not are covered by the fallback rather than losing endpoints.
8 changes: 6 additions & 2 deletions docs/en/usage/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ Refer to [ArtifactCleanupRun](../apis/crds/ArtifactCleanupRun).
### `props`

- `name`: Reference name under OpenAPI schema `definitions` (v2) or `components/schemas` (v3), or CRD `metadata.name`
- `namespaced`: Indicates whether the resource is namespace-scoped; defaults to `true`, meaning the API Endpoints include the namespace path parameter `namespaces/{namespace}`
- `namespaced`: Indicates whether the resource is namespace-scoped, i.e. whether the API Endpoints include the namespace path parameter `namespaces/{namespace}`. A CRD's `spec.scope` decides it when this is unset; OpenAPI sources do not carry a scope and default to `true`
- `pathPrefix`: Can be used to override the global configuration `api.pathPrefix`
- `filepath`: Similar to [specifying openapi path](#specified-openapi-path), used to specify a particular openapi or CRD file
- `apiGroup`: Optional, specifies the API group; openapi will try to read the referenced `x-kubernetes-group-version-kind`, same below
- `apiVersion`: Optional, specifies the API version; CRD defaults to using the first version in `spec.versions`
- `apiVersion`: Optional, specifies the API version; a CRD defaults to the version `kubectl` resolves to — the highest-priority `served` version — which [`api.crdVersion`](./configuration#api) can change
- `apiKind`: Optional, specifies the API resource kind
- `plural`: Optional, the resource's plural name used in the endpoint paths. A CRD's `spec.names.plural` is read automatically; set this for OpenAPI sources whose plural is irregular, otherwise it is derived from the kind
- `hasStatus`: Optional, whether the API server exposes a `status` subresource. A CRD's `subresources.status` decides it; for an OpenAPI source the routes listed in the document decide it. This prop overrides both

An OpenAPI document that carries no `x-kubernetes-group-version-kind` — aggregation-layer documents routinely omit it — must have `apiVersion` and `apiKind` declared here, plus `apiGroup` outside the core group. Otherwise the group, version and kind cannot be derived: the API Endpoints section is omitted rather than built from empty path segments, and `doom lint` reports the page.

## Advanced API

Expand Down
5 changes: 5 additions & 0 deletions docs/en/usage/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ api:
v1alpha1.CodeQualityBranch: /apis/references/CodeQualityBranch#v1alpha1.CodeQualityBranch
# Optional, API documentation path prefix. If the current business uses gateway or other proxy services, this can be configured
pathPrefix: /apis
# Optional, which version of a multi-version CRD a page renders when it passes no `apiVersion` prop
# `preferred` (default): the version `kubectl` resolves to — the highest-priority `served` version
# `storage`: the etcd storage version (`storage: true`)
# `first`: legacy behavior — `spec.versions[0]`, even when it is not served
crdVersion: preferred
```

Refer to [API Documentation](./api) for writing documentation.
Expand Down
8 changes: 6 additions & 2 deletions docs/zh/usage/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ weight: 5
### `props`

- `name`: OpenAPI schema `definitions`(v2) or `components/schemas`(v3) 下的引用名称或CRD `metadata.name`
- `namespaced`: 指示资源是否为命名空间级别,默认为 `true`,即 API Endpoints 是否包含命名空间路径参数 `namespaces/{namespace}`
- `namespaced`: 指示资源是否为命名空间级别,即 API Endpoints 是否包含命名空间路径参数 `namespaces/{namespace}`。未指定时由 CRD 的 `spec.scope` 决定;OpenAPI 源不携带 scope,默认为 `true`
- `pathPrefix`: 可以用于覆盖全局配置中的 `api.pathPrefix`
- `filepath`: 类似[指定 openapi 路径](#specified-openapi-path),用于指定特定的 openapi 或 CRD 文件
- `apiGroup`: 可选,指定 API 组,openapi 会尝试读取引用的 `x-kubernetes-group-version-kind`,下同
- `apiVersion`: 可选,指定 API 版本CRD 会默认使用 `spec.versions` 中第一个版本
- `apiVersion`: 可选,指定 API 版本CRD 默认使用 `kubectl` 会解析到的版本,即 `served` 版本中优先级最高的那个,可通过 [`api.crdVersion`](./configuration#api) 更改
- `apiKind`: 可选,指定 API 资源类型
- `plural`: 可选,端点路径中使用的资源复数名。CRD 会自动读取 `spec.names.plural`;OpenAPI 源的复数形式不规则时需要指定,否则由 kind 推导
- `hasStatus`: 可选,API server 是否暴露 `status` 子资源。CRD 由 `subresources.status` 决定,OpenAPI 源由文档中登记的路由决定,本 prop 可覆盖两者

如果 OpenAPI 文档不带 `x-kubernetes-group-version-kind`(聚合层 apiserver 的文档普遍不带),必须在此显式声明 `apiVersion` 与 `apiKind`,非 core 组还需 `apiGroup`。否则无法推导出 group/version/kind:API Endpoints 段会整段省略,而不是用空路径段拼出来,同时 `doom lint` 会报告该页面。

## 高级 API

Expand Down
5 changes: 5 additions & 0 deletions docs/zh/usage/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ api:
v1alpha1.CodeQualityBranch: /apis/references/CodeQualityBranch#v1alpha1.CodeQualityBranch
# 可选,API 文档路径前缀,如果当前业务使用 gateway 等代理服务,可以配置此项
pathPrefix: /apis
# 可选,页面未传 `apiVersion` prop 时,多版本 CRD 默认渲染哪个版本
# `preferred`(默认):`kubectl` 会解析到的版本,即 `served` 版本中优先级最高的那个
# `storage`:etcd 存储版本(`storage: true`)
# `first`:旧行为——`spec.versions[0]`,即使它没有 served
crdVersion: preferred
```

文档编写参考 [API 文档](./api)
Expand Down
4 changes: 4 additions & 0 deletions fixture-docs/doom.config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ lint:
debugMode: true
cspell:
words:
- gvkless
- gvklessviews
- openshiftpipelinesascodes
- statusful
- statusfulviews
- tekton
- tektonchains
- tektonconfigs
Expand Down
3 changes: 3 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/anchortest.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# AnchorTest [doomtest.alauda.io/v1]

<K8sAPI name="anchortests.doomtest.alauda.io" />
3 changes: 3 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/clusterwidget.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ClusterWidget [doomtest.alauda.io/v1]

<K8sAPI name="clusterwidgets.doomtest.alauda.io" />
15 changes: 15 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/gvklessview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# GvklessView [doomtest.alauda.io/v1]

The OpenAPI document behind this page carries no group-version-kind extension
and no CRD defines the resource, so the group, version and kind cannot be
derived — and this page declares none of them. The endpoints section is
therefore expected to be absent rather than built out of empty path segments.
Nothing below the schema should render.

The `no-unresolved-api-ref` rule detects this (see its unit tests), but it is
not reported when the component is the last node of the file, as it is here and
on most real API pages: the eslint-mdx layer `doom lint` runs on drops messages
anchored there. Keep the component last so this page keeps documenting the real
shape.

<K8sAPI name="v1.GvklessView" namespaced={false} />
19 changes: 19 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/gvklessviewdeclared.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# GvklessView, declared [doomtest.alauda.io/v1]

The same schema, with the group, version and kind supplied by the page. The
endpoints render from those props, and the props win over the document: the
document routes the resource under a namespace, while this page declares the
resource cluster-scoped.

The status subresource is expected to be absent, because the document routes
this resource but no status route for it — the schema's status property alone
does not put one there.

<K8sAPI
name="v1.GvklessView"
apiGroup="doomtest.alauda.io"
apiVersion="v1"
apiKind="GvklessView"
plural="gvklessviews"
namespaced={false}
/>
3 changes: 3 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/iptablestestrule.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# IptablesTestRule [doomtest.alauda.io/v1]

<K8sAPI name="iptables-test-rules.doomtest.alauda.io" />
3 changes: 3 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/multiversionthing.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# MultiVersionThing [doomtest.alauda.io/v1beta1]

<K8sAPI name="multiversionthings.doomtest.alauda.io" />
3 changes: 3 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/nostatussubresource.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# NoStatusSubresource [doomtest.alauda.io/v1]

<K8sAPI name="nostatussubresources.doomtest.alauda.io" />
7 changes: 7 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/openapipathtest.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# OpenAPIPath Fixture

Exercises DOOM-18 (operations without `summary` must not produce `#undefined`
or bare numeric anchors) and DOOM-12 (the `required` badge must read
`requestBody.required`, not the body schema's list of required properties).

<OpenAPIPath path={['/test/foo', '/test/bar', '/test/baz']} />
13 changes: 13 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/statusfulview.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# StatusfulView [doomtest.alauda.io/v1]

The document routes a status subresource for this resource, so the endpoints are
expected to include it. This page's schema and `GvklessView`'s both carry a
status property; only the routes tell the two cases apart.

<K8sAPI
name="v1.StatusfulView"
apiGroup="doomtest.alauda.io"
apiVersion="v1"
apiKind="StatusfulView"
plural="statusfulviews"
/>
3 changes: 3 additions & 0 deletions fixture-docs/en/apis/kubernetes_apis/testloadbalancer2.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# TEST2 [doomtest.alauda.io/v1]

<K8sAPI name="testloadbalancer2.doomtest.alauda.io" />
70 changes: 70 additions & 0 deletions fixture-docs/shared/crds/doomtest.alauda.io_anchortests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# FIXTURE for DOOM-11 (array `[]` anchor collides with parent property),
# DOOM-21 (x-kubernetes-int-or-string Type column is empty),
# DOOM-23 (`/scale` subresource endpoint never rendered).
# Namespaced + pluralize-compatible + single served/storage version, so it does
# NOT also trip DOOM-2/3/4 — it isolates the anchor / schema / scale defects.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: anchortests.doomtest.alauda.io
spec:
group: doomtest.alauda.io
scope: Namespaced
names:
kind: AnchorTest
listKind: AnchorTestList
plural: anchortests
singular: anchortest
versions:
- name: v1
served: true
storage: true
subresources:
status: {}
scale:
specReplicasPath: .spec.replicas
statusReplicasPath: .status.replicas
schema:
openAPIV3Schema:
type: object
description: A fixture resource exercising anchors, IntOrString and scale.
properties:
spec:
type: object
description: Desired state.
properties:
replicas:
type: integer
description: Number of replicas.
port:
x-kubernetes-int-or-string: true
anyOf:
- type: integer
- type: string
description: An IntOrString port (DOOM-21 — Type column must not be empty).
selector:
type: object
description: Label selector.
properties:
matchExpressions:
type: array
description: A list of selector requirements.
items:
type: object
description: A single selector requirement.
properties:
key:
type: string
description: The label key.
values:
type: array
description: An array of string values.
items:
type: string
status:
type: object
description: Observed state.
properties:
replicas:
type: integer
description: Observed replica count.
40 changes: 40 additions & 0 deletions fixture-docs/shared/crds/doomtest.alauda.io_clusterwidgets.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# FIXTURE for DOOM-2 —— Cluster-scoped CRD
# doom 今天渲染 /apis/doomtest.alauda.io/v1/namespaces/{namespace}/clusterwidgets ← 错,会 404
# 修复后应渲染 /apis/doomtest.alauda.io/v1/clusterwidgets
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: clusterwidgets.doomtest.alauda.io
spec:
group: doomtest.alauda.io
scope: Cluster
names:
kind: ClusterWidget
listKind: ClusterWidgetList
plural: clusterwidgets
singular: clusterwidget
versions:
- name: v1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
description: A cluster-scoped fixture resource for doom API rendering tests.
properties:
spec:
type: object
description: Desired state.
properties:
size:
type: integer
description: Widget size.
status:
type: object
description: Observed state.
properties:
ready:
type: boolean
description: Whether the widget is ready.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# FIXTURE for DOOM-3 —— plural 无法由 pluralize(kind) 推导(连字符型,kube-ovn 同款)
# pluralize('IptablesTestRule') => 'iptablestestrules' ← doom 今天渲染这个,错
# spec.names.plural => 'iptables-test-rules' ← 修复后应渲染这个
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: iptables-test-rules.doomtest.alauda.io
spec:
group: doomtest.alauda.io
scope: Namespaced
names:
kind: IptablesTestRule
listKind: IptablesTestRuleList
plural: iptables-test-rules
singular: iptables-test-rule
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
description: A hyphenated-plural fixture resource for doom API rendering tests.
properties:
spec:
type: object
description: Desired state.
properties:
rule:
type: string
description: The rule expression.
Loading
Loading