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
35 changes: 35 additions & 0 deletions .changeset/fix-api-reference-rendering.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
"@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'`.
- `/status` (and the new `/scale`) endpoints are rendered based on the version's declared `spec.versions[].subresources`, not on whether the schema happens to contain a `status` property — fixing both fabricated `/status` endpoints and missing ones.
- `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.
- 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.
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" />
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']} />
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.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# FIXTURE for DOOM-4 —— 多版本,且 versions[0] 是 served:false
# doom 今天渲染 v1alpha1 ← API server 明确拒绝服务这个版本
# 修复后(preferred-served)应渲染 v1beta1
# 同时覆盖 DOOM-10 的正向:只有 v1beta1 声明了 subresources.status
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: multiversionthings.doomtest.alauda.io
spec:
group: doomtest.alauda.io
scope: Namespaced
names:
kind: MultiVersionThing
listKind: MultiVersionThingList
plural: multiversionthings
singular: multiversionthing
versions:
- name: v1alpha1
served: false
storage: false
schema:
openAPIV3Schema:
type: object
description: Retired alpha version, no longer served by the API server.
properties:
spec:
type: object
description: Desired state.
- name: v1beta1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
description: A multi-version fixture resource for doom API rendering tests.
properties:
spec:
type: object
description: Desired state.
properties:
mode:
type: string
description: Operating mode.
status:
type: object
description: Observed state.
properties:
phase:
type: string
description: Current phase.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# FIXTURE for DOOM-10 —— schema 里有 status 属性,但 CRD 没有声明 subresources.status
# doom 今天渲染 /nostatussubresources/{name}/status ← 该端点在真实集群上 404
# 修复后不应渲染任何 /status 端点
# (注:doom 现有的 8 个 tekton fixture 已经是这个形状,本文件只是把它显式化)
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: nostatussubresources.doomtest.alauda.io
spec:
group: doomtest.alauda.io
scope: Namespaced
names:
kind: NoStatusSubresource
listKind: NoStatusSubresourceList
plural: nostatussubresources
singular: nostatussubresource
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
description: Has a status property but no status subresource.
properties:
spec:
type: object
description: Desired state.
status:
type: object
description: Observed state, reconciled via the main resource (no subresource).
31 changes: 31 additions & 0 deletions fixture-docs/shared/crds/doomtest.alauda.io_testloadbalancer2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# FIXTURE for DOOM-3(第二形态)—— 不规则复数(ALB2 同款,作者连 apiKind 变通都用不了)
# pluralize('TEST2') => 'test2s' ← doom 今天渲染这个,错
# spec.names.plural => 'testloadbalancer2' ← 修复后应渲染这个
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: testloadbalancer2.doomtest.alauda.io
spec:
group: doomtest.alauda.io
scope: Namespaced
names:
kind: TEST2
listKind: TEST2List
plural: testloadbalancer2
singular: testloadbalancer2
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
description: An irregular-plural fixture resource for doom API rendering tests.
properties:
spec:
type: object
description: Desired state.
properties:
address:
type: string
description: Load balancer address.
64 changes: 64 additions & 0 deletions fixture-docs/shared/openapis/testapi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"openapi": "3.0.0",
"info": { "title": "Doom API Fixture", "version": "1.0.0" },
"paths": {
"/test/foo": {
"get": {
"responses": { "200": { "description": "OK" } }
},
"post": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/WithReq" }
}
}
},
"responses": { "200": { "description": "OK" } }
}
},
"/test/bar": {
"put": {
"requestBody": {
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/WithReq" }
}
}
},
"responses": { "200": { "description": "OK" } }
}
},
"/test/baz": {
"post": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/NoReq" }
}
}
},
"responses": { "200": { "description": "OK" } }
}
}
},
"components": {
"schemas": {
"WithReq": {
"type": "object",
"required": ["bar"],
"properties": {
"bar": { "type": "string", "description": "A required property." }
}
},
"NoReq": {
"type": "object",
"properties": {
"baz": { "type": "string", "description": "An optional property." }
}
}
}
}
}
Loading
Loading