@supernovaio/cli
Version:
Supernova.io Command Line Interface
164 lines (162 loc) • 6.73 kB
JavaScript
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="0ce14f9a-c743-50b2-959b-559515b5fa6c")}catch(e){}}();
import { readFile } from "node:fs/promises";
import path from "node:path";
import { collectFiles, toRelative } from "./helpers.js";
const docsPattern = "**/*.{md,mdx}";
const snippetWindowBefore = 3;
const snippetWindowAfter = 8;
const maxReferenceSnippetsPerFile = 3;
function extractTitle(content) {
const headingMatch = content.match(/^#\s+(.+)$/m);
if (headingMatch?.[1]) {
return headingMatch[1].trim();
}
const metaMatch = content.match(/<Meta\s+title=["']([^"']+)["']/);
return metaMatch?.[1]?.trim();
}
function escapeRegex(input) {
return input.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw `\$&`);
}
function toPosix(input) {
return input.split(path.sep).join("/");
}
function lineAt(content, index) {
return content.slice(0, index).split("\n").length;
}
function normalizeLine(input) {
return input.replaceAll(/\s+/g, " ").trim().toLowerCase();
}
function createSnippet(content, line) {
const lines = content.split("\n");
const lineIndex = Math.max(0, Math.min(lines.length - 1, line - 1));
let lineStart = Math.max(1, line - snippetWindowBefore);
let lineEnd = Math.min(lines.length, line + snippetWindowAfter);
for (let i = lineIndex; i >= lineStart - 1; i -= 1) {
if (lines[i]?.trim() === "") {
lineStart = i + 2;
break;
}
}
for (let i = lineIndex; i <= lineEnd - 1; i += 1) {
if (lines[i]?.trim() === "") {
lineEnd = i;
break;
}
}
if (lineStart > lineEnd) {
lineStart = line;
lineEnd = line;
}
const snippet = lines.slice(lineStart - 1, lineEnd).join("\n").trimEnd();
return { lineEnd, lineStart, snippet };
}
function classifyDocRecord(relativePath, component, content) {
const normalizedPath = toPosix(relativePath).toLowerCase();
const componentPath = toPosix(component.componentPath).toLowerCase();
const componentDir = path.posix.dirname(componentPath);
const fileName = path.posix.basename(normalizedPath);
const exportName = component.exportName.toLowerCase();
const title = extractTitle(content);
const normalizedTitle = title ? normalizeLine(title) : "";
const componentToken = normalizeLine(component.exportName);
const inComponentDir = normalizedPath.startsWith(`${componentDir}/`);
const isReadme = fileName === "readme.md" || fileName === "readme.mdx";
const isDocsLike = fileName.startsWith("docs.") || fileName.endsWith(".docs.md") || fileName.endsWith(".docs.mdx");
const fileContainsName = fileName.includes(exportName);
const titleContainsName = normalizedTitle.includes(componentToken);
if (inComponentDir && isReadme) {
return { kind: "doc", score: 100 };
}
if (inComponentDir && isDocsLike) {
return { kind: "doc", score: 95 };
}
if (inComponentDir) {
return { kind: "doc", score: 90 };
}
if (fileContainsName || titleContainsName) {
return { kind: "doc", score: 80 };
}
if (normalizedPath.includes("/docs/") || normalizedPath.includes("/documentation/")) {
return { kind: "reference", score: 40 };
}
if (normalizedPath.includes("/storybook/") || normalizedPath.includes(".stories.")) {
return { kind: "reference", score: 35 };
}
return { kind: "reference", score: 20 };
}
export async function analyzeStorybookDocs(projectRoot, components, excludePaths = []) {
const files = await collectFiles({
excludePaths,
pattern: docsPattern,
projectRoot,
});
const records = {};
for (const file of files) {
const content = await readFile(file, "utf8");
const title = extractTitle(content);
const relativePath = toRelative(projectRoot, file);
for (const component of components) {
const classification = classifyDocRecord(relativePath, component, content);
const componentPattern = new RegExp(`\\b${escapeRegex(component.exportName)}\\b`, "gm");
const matches = [...content.matchAll(componentPattern)];
const isFullDoc = classification.kind === "doc";
if (!isFullDoc && matches.length === 0) {
continue;
}
if (!records[component.componentKey]) {
records[component.componentKey] = [];
}
if (isFullDoc) {
const firstMatchIndex = typeof matches[0]?.index === "number" ? matches[0].index : 0;
const line = lineAt(content, firstMatchIndex);
records[component.componentKey].push({
content,
docPath: relativePath,
kind: "doc",
lineEnd: line,
lineStart: line,
score: classification.score,
title,
});
continue;
}
const snippetMatches = matches.slice(0, maxReferenceSnippetsPerFile);
for (const match of snippetMatches) {
if (typeof match.index !== "number") {
continue;
}
const matchLine = lineAt(content, match.index);
const snippet = createSnippet(content, matchLine);
records[component.componentKey].push({
docPath: relativePath,
kind: "reference",
lineEnd: snippet.lineEnd,
lineStart: snippet.lineStart,
score: classification.score,
snippet: snippet.snippet,
title,
});
}
}
}
for (const componentKey of Object.keys(records)) {
records[componentKey] = records[componentKey].sort((a, b) => {
if (a.score !== b.score) {
return b.score - a.score;
}
if (a.kind !== b.kind) {
return a.kind === "doc" ? -1 : 1;
}
if (a.docPath === b.docPath) {
if (a.lineStart === b.lineStart) {
return a.lineEnd - b.lineEnd;
}
return a.lineStart - b.lineStart;
}
return a.docPath.localeCompare(b.docPath);
});
}
return records;
}
//# sourceMappingURL=storybook-docs.js.map
//# debugId=0ce14f9a-c743-50b2-959b-559515b5fa6c