Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
31 changes: 31 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,31 @@
import { describe, expect, it } from "bun:test";
import { normalizeLLMBodyWithRules } from "../normalize-llm-body";
import { componentGridRule } from "./component-grid-rule";

describe("componentGridRule", () => {
it("expands ComponentGrid into a categorized component list", () => {
const input = "<ComponentGrid />\n";

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

expect(actual).toContain("## Buttons");
expect(actual).toContain("## Controls");
expect(actual).toContain("[Checkbox](/docs/components/checkbox)");
});

it("excludes deprecated components", () => {
const input = "<ComponentGrid />\n";

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

expect(actual).not.toContain("/docs/components/fab");
});

it("leaves the node as-is when no components are available", () => {
const input = "<SomethingElse />\n";

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

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

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

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;
}
return null;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function parseFrontmatter(source: string): Record<string, string> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

언젠가는 gray-matter로 다 갈아 버리시죠..!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0cb61b8 이전 PR 에서 gray-matter 도입해서 바로 수정해뒀어요.
gray-matter 스크립트 쪽만 적용해뒀는데 더 rule 파싱 로직에도 적용해볼 수 있겠네요

const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return {};
const result: Record<string, string> = {};
for (const line of match[1].split(/\r?\n/)) {
const colon = line.indexOf(":");
if (colon === -1) continue;
const key = line.slice(0, colon).trim();
const value = line
.slice(colon + 1)
.trim()
.replace(/^["']|["']$/g, "");
if (key) result[key] = value;
}
return result;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}

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 = parseFrontmatter(source);
if (fm.deprecated) continue;

const slug = file.slice(0, -".mdx".length);
entries.push({
category,
title: fm.title ?? slug,
description: fm.description ?? "",
url: `/docs/components/${slug}`,
});
}
}
return entries;
}

let cachedEntries: ComponentEntry[] | null = null;

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

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 { typeTableRule } from "./type-table-rule";
import { tokenReferenceRule } from "./token-reference-rule";
Expand All @@ -15,6 +16,7 @@ export const activeRules: Rule[] = [
tokenReferenceRule,
platformStatusRule,
iconLibraryRule,
componentGridRule,
componentSpecBlockRule,
changelogPageRule,
];
Expand All @@ -23,6 +25,7 @@ export {
changelogPageRule,
codeBlockTabsRule,
componentExampleRule,
componentGridRule,
typeTableRule,
tokenReferenceRule,
componentSpecBlockRule,
Expand Down
Loading