Skip to content
This repository was archived by the owner on Aug 7, 2026. It is now read-only.

Commit 7382ec0

Browse files
catalog: index valentus-theme ui-modules after antora-dark-mode split.
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 220229f commit 7382ec0

4 files changed

Lines changed: 546 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
= GitHub Topics for Antora Extensions
2+
:navtitle: GitHub Topics
3+
:description: Prescribed GitHub topic strings for registry discovery
4+
5+
Topics describe *kind* and *capability* — not repo names. Do not tag `valentus-theme` on the Valentus repository; use `antora-theme` plus appearance topics.
6+
7+
== Primary topics (exactly one)
8+
9+
[cols="2,3"]
10+
|===
11+
| Topic | Use for
12+
13+
| `antora-extension`
14+
| Extends Antora — UI modules, generator packages, chrome. Indexer scans for `ui-modules/registry.json5`.
15+
16+
| `antora-theme`
17+
| Full publishable UI theme (bundle, `preview.png`, demo). Themes gallery.
18+
|===
19+
20+
Do not require the vague `antora` topic — projects that merely *use* Antora tag it already.
21+
22+
== Appearance topics (optional)
23+
24+
[cols="2,3"]
25+
|===
26+
| Topic | Meaning
27+
28+
| `antora-light-theme`
29+
| Full theme includes light appearance
30+
31+
| `antora-dark-theme`
32+
| Full dark theme (not slipstreamable)
33+
34+
| `antora-dark-mode`
35+
| Dark mode *overlay* installable into an existing theme
36+
|===
37+
38+
== Category topics (optional)
39+
40+
[cols="2,3"]
41+
|===
42+
| Topic | Meaning
43+
44+
| `antora-wildcard-theme`
45+
| Full themes that do *not* fit the light/dark model (sepia, high-contrast palettes, etc.). Pair with `antora-theme`. Do *not* combine with `antora-light-theme` or `antora-dark-theme`.
46+
|===
47+
48+
`antora-dark-theme` and `antora-dark-mode` are different:
49+
50+
* *dark-theme* — complete dark theme product
51+
* *dark-mode* — extension layer (e.g. `antora-dark-mode` repo)
52+
53+
== Examples
54+
55+
=== antora-dark-mode (extension)
56+
57+
[source]
58+
----
59+
antora-extension
60+
antora-dark-theme
61+
antora-dark-mode
62+
----
63+
64+
Categorized as extension; appears under dark / dark-mode filters; hidden from default theme browse.
65+
66+
=== Valentus (full theme, `valentus-theme` repo)
67+
68+
Valentus is an Antora theme (from Latin *valere* / *valens*). Topics:
69+
70+
[source]
71+
----
72+
antora-theme
73+
antora-light-theme
74+
antora-dark-theme
75+
----
76+
77+
No repo-name topic. Default theme browse shows dual light+dark themes.
78+
79+
=== Architexture (light theme)
80+
81+
[source]
82+
----
83+
antora-theme
84+
antora-light-theme
85+
----
86+
87+
Stone and Graphite is a light palette — browse under *Light*, not *Wildcard*.
88+
89+
=== Hypothetical wildcard theme
90+
91+
For a theme whose palette is neither light nor dark (e.g. sepia or solarized-only):
92+
93+
[source]
94+
----
95+
antora-theme
96+
antora-wildcard-theme
97+
----
98+
99+
Shows in the *Wildcard* browse filter only — no `antora-light-theme` or `antora-dark-theme`.
100+
101+
== Discovery
102+
103+
[source]
104+
----
105+
topic:antora-extension # extensions catalog
106+
topic:antora-theme # themes gallery
107+
----
108+
109+
Manifest catalog is optional for `antora-extension` repos — indexer looks for `ui-modules/registry.json5` when present.
110+
111+
== Repository renames
112+
113+
The registry stores GitHub `repo.id` (numeric). It persists across renames within GitHub.
114+
115+
If a repo is *deleted and recreated*, it gets a new id — contact a curator to re-link (automated claim flow planned). Same-owner renames need no action.
116+
117+
Machine-readable schema: `/api/extensions/topics`

scripts/sync-extension-catalog.mjs

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Fetches ui-modules manifests from known repos and writes a static catalog snapshot.
4+
* Run: node scripts/sync-extension-catalog.mjs
5+
*/
6+
7+
import fs from 'node:fs';
8+
import path from 'node:path';
9+
import { fileURLToPath } from 'node:url';
10+
import JSON5 from 'json5';
11+
12+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
13+
const ROOT = path.resolve(__dirname, '..');
14+
const OUT_PATH = path.join(ROOT, 'src', 'data', 'generated', 'extension-catalog.json');
15+
16+
const knownExtensionRepos = [
17+
{
18+
owner: 'antora-supplemental',
19+
repo: 'valentus-theme',
20+
branch: 'main',
21+
localRegistryIndex: '../valentus-theme/ui-modules/registry-index.json',
22+
},
23+
];
24+
25+
const UI_MODULES_ROOT = 'ui-modules';
26+
const REGISTRY_INDEX = `${UI_MODULES_ROOT}/registry-index.json`;
27+
const REGISTRY = `${UI_MODULES_ROOT}/registry.json5`;
28+
29+
async function fetchRaw(owner, repo, filePath, branch = 'main') {
30+
for (const b of [branch, 'main', 'master']) {
31+
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${b}/${filePath}`;
32+
const res = await fetch(url, { headers: { 'User-Agent': 'Antora-Extensions-Sync' } });
33+
if (res.ok) return res.text();
34+
}
35+
return null;
36+
}
37+
38+
function readLocalIndex(ref) {
39+
if (!ref.localRegistryIndex) return null;
40+
const indexPath = path.resolve(ROOT, ref.localRegistryIndex);
41+
if (!fs.existsSync(indexPath)) return null;
42+
return JSON.parse(fs.readFileSync(indexPath, 'utf8'));
43+
}
44+
45+
async function discoverRepo(ref) {
46+
const { owner, repo, branch = 'main' } = ref;
47+
48+
const local = readLocalIndex(ref);
49+
if (local) {
50+
return { source: 'registry-index.json (local)', ...local };
51+
}
52+
53+
const indexText = await fetchRaw(owner, repo, REGISTRY_INDEX, branch);
54+
if (indexText) {
55+
const index = JSON.parse(indexText);
56+
return { source: 'registry-index.json', ...index };
57+
}
58+
59+
const registryText = await fetchRaw(owner, repo, REGISTRY, branch);
60+
if (!registryText) return null;
61+
62+
const registry = JSON5.parse(registryText);
63+
const modules = [];
64+
const recipes = [];
65+
66+
for (const entry of registry.modules) {
67+
const manifestPath = `${UI_MODULES_ROOT}/${entry.path}/${entry.manifest || 'ui-module.json5'}`;
68+
const manifestText = await fetchRaw(owner, repo, manifestPath, branch);
69+
if (!manifestText) continue;
70+
const manifest = JSON5.parse(manifestText);
71+
modules.push({
72+
id: entry.id,
73+
name: manifest.name,
74+
version: manifest.version,
75+
type: 'ui-module',
76+
description: manifest.description || '',
77+
repository: manifest.repository || registry.repository,
78+
manifestPath: `${entry.path}/${entry.manifest || 'ui-module.json5'}`,
79+
modulePath: entry.path,
80+
requires: manifest.requires || [],
81+
recommends: manifest.recommends || [],
82+
conflicts: manifest.conflicts || [],
83+
partials: manifest.ui?.partials,
84+
slots: manifest.slots,
85+
});
86+
}
87+
88+
for (const recipeEntry of registry.recipes || []) {
89+
const recipePath = `${UI_MODULES_ROOT}/${recipeEntry.path}`;
90+
const recipeText = await fetchRaw(owner, repo, recipePath, branch);
91+
if (!recipeText) continue;
92+
const recipe = JSON5.parse(recipeText);
93+
recipes.push({
94+
id: recipeEntry.id,
95+
name: recipe.name,
96+
version: recipe.version,
97+
type: 'ui-recipe',
98+
description: recipe.description || '',
99+
modules: recipe.modules,
100+
manifestPath: recipeEntry.path,
101+
});
102+
}
103+
104+
return {
105+
schema: registry.schema || '1.0',
106+
repository: registry.repository || `https://github.com/${owner}/${repo}`,
107+
source: REGISTRY,
108+
modules,
109+
recipes,
110+
};
111+
}
112+
113+
async function main() {
114+
const modules = [];
115+
const recipes = [];
116+
117+
for (const ref of knownExtensionRepos) {
118+
const catalog = await discoverRepo(ref);
119+
if (!catalog) {
120+
console.warn(`No catalog in ${ref.owner}/${ref.repo}`);
121+
continue;
122+
}
123+
const slug = `${ref.owner}/${ref.repo}`.toLowerCase();
124+
for (const mod of catalog.modules) {
125+
modules.push({
126+
catalogId: `${slug}/${mod.id}`,
127+
repositoryOwner: ref.owner,
128+
repositoryName: ref.repo,
129+
...mod,
130+
});
131+
}
132+
for (const recipe of catalog.recipes) {
133+
recipes.push({
134+
catalogId: `${slug}/${recipe.id}`,
135+
repositoryOwner: ref.owner,
136+
repositoryName: ref.repo,
137+
repository: catalog.repository,
138+
...recipe,
139+
});
140+
}
141+
console.log(`Indexed ${catalog.modules.length} modules, ${catalog.recipes.length} recipes from ${ref.owner}/${ref.repo}`);
142+
}
143+
144+
const output = {
145+
schema: '1.0',
146+
generatedAt: new Date().toISOString(),
147+
source: 'manifest',
148+
modules,
149+
recipes,
150+
};
151+
152+
fs.mkdirSync(path.dirname(OUT_PATH), { recursive: true });
153+
fs.writeFileSync(OUT_PATH, `${JSON.stringify(output, null, 2)}\n`, 'utf8');
154+
console.log(`Wrote ${OUT_PATH}`);
155+
}
156+
157+
main().catch((err) => {
158+
console.error(err);
159+
process.exit(1);
160+
});

src/data/extension-repos.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { KnownExtensionRepo } from '../types/ui-module';
2+
import { ANTORA_APPEARANCE_TOPICS, ANTORA_TOPICS } from './github-topics';
3+
4+
/**
5+
* Curated extension repositories. GitHub topic search (`antora-extension`) supplements this.
6+
*
7+
* valentus-theme: composable doc-site chrome (ui-modules registry).
8+
* antora-dark-mode: dark-mode overlay only — no ui-modules catalog (see known-theme-repos / extension gallery filters).
9+
*/
10+
export const knownExtensionRepos: KnownExtensionRepo[] = [
11+
{
12+
owner: 'antora-supplemental',
13+
repo: 'valentus-theme',
14+
branch: 'main',
15+
discoveredVia: 'curated',
16+
githubTopics: [ANTORA_TOPICS.EXTENSION],
17+
localRegistryIndex: '../valentus-theme/ui-modules/registry-index.json',
18+
},
19+
];

0 commit comments

Comments
 (0)