Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 8 additions & 0 deletions docs/app/_llms/__fixtures__/component-grid/basic.output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
## Buttons

- [Action Button](https://example.com/llms/docs/components/action-button.txt) — 기본 인터랙션 컴포넌트입니다.
- [Floating Action Button](https://example.com/llms/docs/components/floating-action-button.txt)

## Controls

- [Checkbox](https://example.com/llms/docs/components/checkbox.txt) — 옵션 선택 컴포넌트입니다.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<SomethingElse />
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<SomethingElse />
44 changes: 44 additions & 0 deletions docs/app/_llms/rules/component-grid-rule.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { describe, expect, it } from "bun:test";
import { normalizeLLMBodyWithRules } from "../normalize-llm-body";
import { normalizeForAssert, readFixture } from "../test-utils";
import { buildMarkdown, componentGridRule, type ComponentEntry } from "./component-grid-rule";

const sampleEntries: ComponentEntry[] = [
{
category: "Controls",
title: "Checkbox",
description: "옵션 선택 컴포넌트입니다.",
url: "https://example.com/llms/docs/components/checkbox.txt",
},
{
category: "Buttons",
title: "Floating Action Button",
description: "",
url: "https://example.com/llms/docs/components/floating-action-button.txt",
},
{
category: "Buttons",
title: "Action Button",
description: "기본 인터랙션 컴포넌트입니다.",
url: "https://example.com/llms/docs/components/action-button.txt",
},
];

describe("componentGridRule", () => {
it("renders categorized markdown from entries", () => {
const expected = readFixture("component-grid", "basic.output.md");

const actual = buildMarkdown(sampleEntries);

expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
});

it("non-target node passthrough", () => {
const input = readFixture("component-grid", "passthrough.input.mdx");
const expected = readFixture("component-grid", "passthrough.output.mdx");

const actual = normalizeLLMBodyWithRules(input, [componentGridRule]);

expect(normalizeForAssert(actual)).toBe(normalizeForAssert(expected));
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
115 changes: 115 additions & 0 deletions docs/app/_llms/rules/component-grid-rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import matter from "gray-matter";
import type { MdxJsxFlowElement } from "mdast-util-mdx-jsx";
import { baseUrl } from "@/app/metadata";
import { getLLMMarkdownUrl } from "../config";
import type { Rule } from "./types";

export interface ComponentEntry {
category: string;
title: string;
description: string;
url: string;
}

type Frontmatter = {
title?: string;
description?: string;
deprecated?: boolean;
};

function resolveComponentsDir(): string | null {
const candidates = [
path.resolve(process.cwd(), "content/docs/components"),
path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../content/docs/components"),
];
for (const candidate of candidates) {
if (fs.existsSync(candidate)) return candidate;
}
console.warn(`[ComponentGrid] components directory not found; tried: ${candidates.join(", ")}`);
return null;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function titleCase(value: string): string {
return value.charAt(0).toUpperCase() + value.slice(1);
}

function loadEntries(): ComponentEntry[] {
const componentsDir = resolveComponentsDir();
if (!componentsDir) return [];

const entries: ComponentEntry[] = [];
for (const dirent of fs.readdirSync(componentsDir, { withFileTypes: true })) {
if (!dirent.isDirectory()) continue;
const match = dirent.name.match(/^\(([^)]+)\)$/);
if (!match) continue;

const category = titleCase(match[1]);
const categoryDir = path.join(componentsDir, dirent.name);

for (const file of fs.readdirSync(categoryDir)) {
if (!file.endsWith(".mdx")) continue;

const source = fs.readFileSync(path.join(categoryDir, file), "utf8");
const fm = matter(source).data as Frontmatter;
if (fm.deprecated) continue;

const slug = file.slice(0, -".mdx".length);
entries.push({
category,
title: fm.title ?? slug,
description: fm.description ?? "",
url: new URL(getLLMMarkdownUrl("docs", ["components", slug]), baseUrl).toString(),
});
}
}
return entries;
}

let cachedEntries: ComponentEntry[] | null = null;

function getEntries(): ComponentEntry[] {
if (cachedEntries === null) cachedEntries = loadEntries();
return cachedEntries;
}

export function buildMarkdown(entries: ComponentEntry[]): string {
const grouped = new Map<string, ComponentEntry[]>();
for (const entry of entries) {
if (!grouped.has(entry.category)) grouped.set(entry.category, []);
grouped.get(entry.category)!.push(entry);
}
for (const list of grouped.values()) {
list.sort((a, b) => a.title.localeCompare(b.title));
}

const sections: string[] = [];
for (const [category, list] of Array.from(grouped.entries()).sort(([a], [b]) =>
a.localeCompare(b),
)) {
const lines = [`## ${category}`, ""];
for (const entry of list) {
const suffix = entry.description ? ` — ${entry.description}` : "";
lines.push(`- [${entry.title}](${entry.url})${suffix}`);
}
sections.push(lines.join("\n"));
}
return sections.join("\n\n");
}

export const componentGridRule: Rule = {
name: "ComponentGrid",
match: (node): node is MdxJsxFlowElement =>
node.type === "mdxJsxFlowElement" && node.name === "ComponentGrid",
transform: (node) => {
try {
const entries = getEntries();
if (entries.length === 0) return [node];
return [{ type: "html", value: buildMarkdown(entries) }];
} catch {
return [node];
}
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
3 changes: 3 additions & 0 deletions docs/app/_llms/rules/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { changelogPageRule } from "./changelog-page-rule";
import { codeBlockTabsRule } from "./codeblock-tabs-rule";
import { componentExampleRule } from "./component-example-rule";
import { componentGridRule } from "./component-grid-rule";
import { platformStatusRule } from "./platform-status-rule";
import { progressBoardRule } from "./progress-board-rule";
import { typeTableRule } from "./type-table-rule";
Expand All @@ -17,6 +18,7 @@ export const activeRules: Rule[] = [
platformStatusRule,
progressBoardRule,
iconLibraryRule,
componentGridRule,
componentSpecBlockRule,
changelogPageRule,
];
Expand All @@ -25,6 +27,7 @@ export {
changelogPageRule,
codeBlockTabsRule,
componentExampleRule,
componentGridRule,
typeTableRule,
tokenReferenceRule,
componentSpecBlockRule,
Expand Down
Loading