alt-docs-registry
Single source of truth for the registry of ALT documentation products, sections and versions. One YAML schema products.yaml, a Vite plugin with the virtual module virtual:alt-docs-registry, the runtime composable useRegistry() and the CLI alt-docs.
The package replaces the previous PRODUCT_NAMES, ALIASES, ALL_DISTROS hardcodes scattered across the theme and 22 distribution config branches. The same registry is read at build time by the theme, by the publican-docbook-to-vitepress-markdown converter and by the runtime navigation components.
Installation
pnpm add @ampernic/alt-docs-registryThe @ampernic scope from Forgejo is wired via .npmrc at the project root:
@ampernic:registry=https://altlinux.space/api/packages/ampernic/npm/The alt-docs CLI is installed through the package bin, run pnpm dlx alt-docs ... or, locally, pnpm exec alt-docs ....
Source: products.yaml
The format is described in schema.ts. A minimal example:
schema: 1
locales: [ru, en]
site:
hostname: https://docs.altlinux.space
organization:
name: Базальт СПО
url: https://www.basealt.ru
sections:
- id: server
name: { ru: "Серверные", en: "Server" }
products: [alt-server, alt-domain]
- id: workstation
name: { ru: "Рабочие станции", en: "Workstation" }
products: [alt-workstation, alt-kworkstation]
products:
alt-workstation:
name: { ru: "Альт Рабочая станция" }
tagline: { ru: "Универсальная система для офиса" }
product_link: https://www.basealt.ru/alt-workstation
versions: ["11.2", "11.1", "11.0", "10.4"]
branding:
logo:
col: branding/alt-workstation/logo-col.svg
dark: branding/alt-workstation/logo-dark.svg
accent: "#fa814c"
meta:
isPublic: trueThe head of versions is treated as the current one. The family field merges a legacy branch (alt-domain-p10) with the main one under a single card. site.hostname propagates into ResolvedRegistry.hostname and then into vitepress-plugin-meta for canonical URLs; site.organization becomes the schema.org Organization node on the landing.
Wiring in the VitePress config
// .vitepress/config.mts
import { defineConfig } from 'vitepress'
import { AltDocsRegistryPlugin, createRegistry } from '@ampernic/alt-docs-registry'
import { createSharedConfig } from '@ampernic/vitepress-theme-alt-docs/config'
const { plugin: registryPlugin, resolved } = await createRegistry({
source: 'products.yaml',
fallback: 'https://raw.altlinux.space/alt-docs/docs-vitepress/index/products.yaml',
distro: 'alt-workstation',
locale: 'ru',
emitProductsJson: true,
emitSprite: true,
})
const shared = createSharedConfig({
distroName: 'alt-workstation',
allDistros: resolved.allDistros,
sections: resolved.sections,
productNames: resolved.productNames,
productLinks: resolved.productLinks,
})
export default defineConfig({
...shared,
vite: {
plugins: [registryPlugin, ...(shared.vite?.plugins ?? [])],
},
})createRegistry warms the snapshot once and returns both the plugin (for vite.plugins) and a ready-resolved ResolvedRegistry object. AltDocsRegistryPlugin can be used on its own when the snapshot is only needed at runtime.
Usage in components
import { useRegistry } from '@ampernic/alt-docs-registry/composables'
const { registry, merged, refresh } = useRegistry()
// registry.value: build-time snapshot from virtual:alt-docs-registry
// merged.value: runtime cache | build; runtime wins
// refresh(): pulls a fresh /products.json from the site rootThe composable returns shallow refs so refreshing the registry does not re-render half the theme. One module-scoped cache per page: several components calling useRegistry() see the same fetched snapshot.
CLI alt-docs
File operations only, no auto-commit; the git wrapper stays with gear-* or CI:
alt-docs product add alt-orchestra \
--section orchestra \
--logoCol ./logo.svg \
--logoDark ./logo-mono.svg
alt-docs version add alt-workstation 11.3 --from 11.2
alt-docs product publish --repo alt-docs/docs-vitepress --token "$ALT_DOCS_TOKEN"
alt-docs check --strictproduct addadds an entry toproducts.yaml, copies the logos intodocs/public/branding/<slug>/, attaches the product to a section.product removedrops the entry from the registry (leaves the branch alone).product publishtriggers thefull-rebuild.ymlworkflow via the Forgejo API. Requires--tokenorALT_DOCS_TOKEN.version addwrites the new version intoproducts.yaml(first in the list = current) and scaffoldsdocs/ru/<X.Y>/from a template or from the previous version.checkvalidates the schema and the on-disk presence of branding files.--strictpromotes warnings to exit-code 1.
Plugin options
| Option | Type | Default | Description |
|---|---|---|---|
source | string | -- | Required. Path to products.yaml or a https://... URL |
fallback | string | -- | Fallback source, tried when source is unreadable |
distro | string | null | null | Slug of the current distribution. null = index build showing every product |
locale | Locale | 'ru' | Locale into which LocalizedString fields are resolved |
emitProductsJson | boolean | true | Write <outDir>/products.json, consumed by useRegistry().refresh() at runtime |
productsJsonName | string | 'products.json' | Name of the emitted JSON |
brandingRoots | string[] | ['docs/public', 'public'] | Roots where branding.logo.col/dark is looked up. Order = priority |
emitSprite | boolean | false | Assemble an SVG sprite of every logo as <outDir>/products-sprite.svg |
spriteFileName | string | 'products-sprite.svg' | Name of the emitted sprite |
What ends up in ResolvedRegistry
The virtual module virtual:alt-docs-registry and products.json carry a locale-resolved snapshot:
| Field | Description |
|---|---|
hostname | Copy of site.hostname from the YAML |
organization | Copy of site.organization: the publisher, emitted by vitepress-plugin-meta as a schema.org Organization node on the landing |
allDistros | Sorted list of every product slug |
sections | { id, name, products }, names already resolved into the current locale |
productNames | slug -> localized name |
productBrandingKeys | slug -> sprite key (branding.sharedWith is honored) |
productTaglines | slug -> localized tagline |
productLinks | slug -> product_link (external product page) |
productFamilyPrimary | slug -> primary slug of the family (self when not in a family) |
productFamilyMembers | primary slug -> [primary, ...secondaries] |
productVersions | slug -> versions[] (lets the primary landing show every branch without a cross-site fetch) |
currentDistro | Copy of the distro option |
locale | Copy of the locale option |
Notes
- The parser requires
schema: 1in the YAML. Extra fields do not break it, only a major version mismatch does. parseRegistrymay return anissues[]list withseverity: 'error'; in that case the plugin fails duringconfigResolvedinstead of silently building a broken snapshot.- A distribution with an empty
versions[]is not treated as an error, it renders as a placeholder in the switcher without versions (useful as a spacer for a future product). - Enable
emitSpriteonly when the theme actually usesproducts-sprite.svg, otherwise you pay kilobytes in the bundle for nothing. parseRegistryhandles unknown fields softly: warns, but validates the rest.- The
sitefield is restored manually when merging from YAML, older parser versions used to drop it. Currently fixed upstream; look attest/site-field.test.tsbefore patching the loader.