fix(site): skip docs links for features without reference pages (#1394)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Darius Cepulis
2026-07-02 11:04:31 -07:00
committed by GitHub
co-authored by Claude
parent fb50e331f2
commit 2a8cbd0df4
8 changed files with 115 additions and 34 deletions
@@ -193,6 +193,22 @@ function main() {
log.info(`Done! Generated ${presetSuccessCount} preset files.`);
// Report features referenced by presets that lack a reference page.
// These render as plain text in the preset table instead of links.
const unlinked = new Map<string, string>();
for (const { reference } of presetResults) {
for (const feature of reference.features) {
if (!feature.hasReference) unlinked.set(feature.name, feature.slug);
}
}
if (unlinked.size > 0) {
log.warn(`${unlinked.size} preset feature(s) have no reference page:`);
for (const [name, slug] of unlinked) {
log.warn(` - ${name} → site/src/content/docs/${slug}.mdx (missing)`);
}
}
console.warn = originalWarn;
if (errorCount > 0) {
@@ -552,11 +552,17 @@ export interface PresetSkinDef {
cssImport?: string;
}
export interface PresetFeatureRef {
name: string;
slug: string;
hasReference: boolean;
}
export interface PresetReference {
name: string;
description?: string;
featureBundle: string;
features: string[];
features: PresetFeatureRef[];
html: {
skins: PresetSkinDef[];
mediaElement?: string;
@@ -24,7 +24,7 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as ts from 'typescript';
import type { PresetReference, PresetResult, PresetSkinDef } from './pipeline.js';
import type { PresetFeatureRef, PresetReference, PresetResult, PresetSkinDef } from './pipeline.js';
// ─── Types ──────────────────────────────────────────────────────────
@@ -307,6 +307,31 @@ function findReactMediaElement(filePath: string): string | undefined {
// ─── Feature Bundle Resolution ──────────────────────────────────────
/**
* Feature names whose kebab-cased form doesn't match the docs page slug.
* Example: `textTrack` `feature-text-tracks.mdx`.
*/
const FEATURE_SLUG_OVERRIDES: Record<string, string> = {
textTrack: 'text-tracks',
};
function featureDocsSlug(featureName: string): string {
const override = FEATURE_SLUG_OVERRIDES[featureName];
if (override) return `reference/feature-${override}`;
const kebab = featureName.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
return `reference/feature-${kebab}`;
}
function featureReferenceExists(monorepoRoot: string, slug: string): boolean {
const mdxPath = path.join(monorepoRoot, 'site/src/content/docs', `${slug}.mdx`);
return fs.existsSync(mdxPath);
}
function resolveFeatureRef(name: string, monorepoRoot: string): PresetFeatureRef {
const slug = featureDocsSlug(name);
return { name, slug, hasReference: featureReferenceExists(monorepoRoot, slug) };
}
function parseFeatureBundles(presetsFilePath: string): Map<string, string[]> {
const map = new Map<string, string[]>();
if (!fs.existsSync(presetsFilePath)) return map;
@@ -416,7 +441,11 @@ function scanReactDirectory(scanDir: string, barrelPath: string, presetName: str
// ─── Preset Reference Building ──────────────────────────────────────
function buildPresetReference(preset: PresetInfo, featureBundleMap: Map<string, string[]>): PresetResult | null {
function buildPresetReference(
preset: PresetInfo,
featureBundleMap: Map<string, string[]>,
monorepoRoot: string
): PresetResult | null {
// Find feature bundle name from barrel files (try both frameworks)
const bundleName =
(preset.html && findFeatureBundleExport(preset.html.barrelPath)) ??
@@ -424,7 +453,8 @@ function buildPresetReference(preset: PresetInfo, featureBundleMap: Map<string,
if (!bundleName) return null;
const features = featureBundleMap.get(bundleName) ?? [];
const featureNames = featureBundleMap.get(bundleName) ?? [];
const features = featureNames.map((name) => resolveFeatureRef(name, monorepoRoot));
// Scan HTML directory
const htmlResult = preset.html ? scanHtmlDirectory(preset.html.scanDir) : { skins: [] as PresetSkinDef[] };
@@ -465,7 +495,7 @@ export function generatePresetReferences(monorepoRoot: string): PresetResult[] {
const results: PresetResult[] = [];
for (const preset of presets) {
const result = buildPresetReference(preset, featureBundleMap);
const result = buildPresetReference(preset, featureBundleMap, monorepoRoot);
if (result) results.push(result);
}
@@ -917,10 +917,30 @@ describe('Preset pipeline (end-to-end)', () => {
it('resolves feature names from the bundle', () => {
const ref = findPreset('video')!.reference;
expect(ref.features).toEqual(expect.arrayContaining(['playback', 'volume']));
expect(ref.features.map((f) => f.name)).toEqual(expect.arrayContaining(['playback', 'volume']));
expect(ref.features.length).toBe(2);
});
it('emits docs slugs for features', () => {
const ref = findPreset('video')!.reference;
const playback = ref.features.find((f) => f.name === 'playback');
const volume = ref.features.find((f) => f.name === 'volume');
expect(playback?.slug).toBe('reference/feature-playback');
expect(volume?.slug).toBe('reference/feature-volume');
});
it('flags hasReference true when the feature MDX page exists', () => {
const ref = findPreset('video')!.reference;
const playback = ref.features.find((f) => f.name === 'playback');
expect(playback?.hasReference).toBe(true);
});
it('flags hasReference false when the feature MDX page is missing', () => {
const ref = findPreset('video')!.reference;
const volume = ref.features.find((f) => f.name === 'volume');
expect(volume?.hasReference).toBe(false);
});
it('detects HTML skins with tagNames', () => {
const skins = findPreset('video')!.reference.html.skins;
expect(skins).toEqual(
@@ -974,7 +994,7 @@ describe('Preset pipeline (end-to-end)', () => {
it('resolves feature names (subset of video)', () => {
const ref = findPreset('audio')!.reference;
expect(ref.features).toEqual(['playback']);
expect(ref.features.map((f) => f.name)).toEqual(['playback']);
});
it('detects single HTML skin', () => {
@@ -1049,8 +1069,8 @@ describe('Preset pipeline (end-to-end)', () => {
const featureSlugs = featureResults.map((r) => r.slug);
const videoPreset = findPreset('video')!.reference;
for (const featureName of videoPreset.features) {
expect(featureSlugs).toContain(featureName);
for (const feature of videoPreset.features) {
expect(featureSlugs).toContain(feature.name);
}
});
});
@@ -0,0 +1,4 @@
---
title: Playback feature
description: Fixture stub used by the preset pipeline e2e test to exercise hasReference=true.
---