mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 13:48:14 +00:00
fix(site): skip docs links for features without reference pages (#1394)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Playback feature
|
||||
description: Fixture stub used by the preset pipeline e2e test to exercise hasReference=true.
|
||||
---
|
||||
Reference in New Issue
Block a user