Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
43 changes: 8 additions & 35 deletions scripts/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@ import * as fs from "fs";
import * as path from "path";

import loadConfigFiles from "./src/config";
import {
type AEP,
type ConsolidatedLinterRule,
type GroupFile,
type LinterRule,
} from "./src/types";
import { type AEP, type GroupFile, type LinterRule } from "./src/types";
import { buildMarkdown, Markdown } from "./src/markdown";
import { load, dump } from "js-yaml";
import {
assembleLinterRules,
assembleOpenAPILinterRules,
consolidateLinterRule,
writeRule,
getUniqueAeps,
} from "./src/linter";
import {
logFileRead,
Expand Down Expand Up @@ -371,18 +365,13 @@ if (AEP_LINTER_LOC != "") {
"",
);

// Write out linter rules.
// Scan linter rules to determine which AEPs have rules
// Astro will read these directly from the external repo at build time
// See: src/pages/tooling/linter/rules/[aep].astro
let linter_rules = await assembleLinterRules(AEP_LINTER_LOC);
let consolidated_rules = consolidateLinterRule({ linterRules: linter_rules });
for (var rule of consolidated_rules) {
writeRule(rule);
}

// Add to site structure
addLinterRules(
siteStructure,
consolidated_rules.map((r) => r.aep),
);
addLinterRules(siteStructure, getUniqueAeps(linter_rules));
addToolingPage(siteStructure, { label: "Website", link: "tooling/website" });
} else {
console.warn("Proto linter repo is not found.");
Expand All @@ -405,25 +394,9 @@ if (AEP_OPENAPI_LINTER_LOC != "") {
"OpenAPI Linter",
);

// Consolidate rules (groups by AEP number)
const consolidatedOpenAPIRules = consolidateLinterRule({
linterRules: openapiLinterRules,
});

// Write rule markdown files
for (const rule of consolidatedOpenAPIRules) {
const outputPath = path.join(
"src/content/docs/tooling/openapi-linter/rules",
`${rule.aep}.md`,
);
writeRule(rule, outputPath);
}

// Add to site structure
addOpenAPILinterRules(
siteStructure,
consolidatedOpenAPIRules.map((r) => r.aep),
);
// Astro will read these directly from the external repo at build time
addOpenAPILinterRules(siteStructure, getUniqueAeps(openapiLinterRules));

console.log("✅ OpenAPI linter integration complete\n");
} else {
Expand Down
38 changes: 38 additions & 0 deletions scripts/src/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,3 +237,41 @@ export function writeRule(rule: ConsolidatedLinterRule, outputPath?: string) {
path.join(`src/content/docs/tooling/linter/rules/`, `${rule.aep}.md`);
writeFile(filePath, rule.contents);
}

/**
* Writes individual linter rules as separate markdown files
* Astro components will consolidate them at build time
*
* @param rules - Array of linter rules to write
* @param outputDir - Directory to write the rule files
*/
export function writeIndividualRules(
rules: LinterRule[],
outputDir: string,
): void {
// Ensure the directory exists
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

for (const rule of rules) {
const filePath = path.join(outputDir, rule.filename);
writeFile(filePath, rule.contents);
}

console.log(
`✓ Wrote ${rules.length} individual linter rules to ${outputDir}`,
);
}

/**
* Gets unique AEP numbers from linter rules
* Used for site structure generation
*
* @param rules - Array of linter rules
* @returns Array of unique AEP numbers, sorted
*/
export function getUniqueAeps(rules: LinterRule[]): string[] {
const aeps = new Set(rules.map((r) => r.aep));
return Array.from(aeps).sort();
}
76 changes: 76 additions & 0 deletions src/components/LinterRules.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
/**
* LinterRules Component
*
* Renders a list of linter rules in collapsible <details> elements.
* Consolidates individual rule files at build time by reading directly from external repos.
*
* Props:
* - rules: Array of LinterRule objects to render (with pre-rendered HTML content)
* - preamble: Optional preamble text to display before the rules
*/

export interface LinterRule {
title: string;
aep: string;
htmlContent: string; // Pre-rendered HTML content
filename: string;
slug: string;
preamble?: string;
}

export interface Props {
rules: LinterRule[];
preamble?: string;
}

const { rules, preamble } = Astro.props;

// Extract preamble from first rule if available, otherwise use provided preamble
const displayPreamble = rules[0]?.preamble || preamble || "";
---

{displayPreamble && (
<div class="preamble" set:html={displayPreamble} />
)}

{
rules.map((rule) => (
<details>
<summary>{rule.title}</summary>
<div set:html={rule.htmlContent} />
</details>
))
}

<style>
.preamble {
margin-bottom: 1.5rem;
}

details {
margin-bottom: 1rem;
border: 1px solid var(--sl-color-gray-5);
border-radius: 0.5rem;
padding: 0;
}

summary {
padding: 1rem;
cursor: pointer;
font-weight: 600;
user-select: none;
}

summary:hover {
background-color: var(--sl-color-gray-6);
}

details[open] summary {
border-bottom: 1px solid var(--sl-color-gray-5);
}

details > div {
padding: 1rem;
}
</style>
73 changes: 73 additions & 0 deletions src/pages/tooling/linter/rules/[aep].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
/**
* Dynamic route for Protobuf Linter rule pages
* Reads directly from external linter repo and consolidates by AEP number
*/

import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
import LinterRules from "../../../../components/LinterRules.astro";
import { assembleLinterRules } from "../../../../../scripts/src/linter";
import { marked } from "marked";

const AEP_LINTER_LOC = process.env.AEP_LINTER_LOC || "";

export async function getStaticPaths() {
const AEP_LINTER_LOC = process.env.AEP_LINTER_LOC || "";

if (!AEP_LINTER_LOC) {
console.warn("⚠️ AEP_LINTER_LOC not set, skipping protobuf linter rules");
return [];
}

// Read rules directly from external repo
const allRules = await assembleLinterRules(AEP_LINTER_LOC);

// Group rules by AEP number
const rulesByAep: Record<string, any[]> = {};
for (const rule of allRules) {
if (!rulesByAep[rule.aep]) {
rulesByAep[rule.aep] = [];
}

// Strip frontmatter and render markdown to HTML
const contentWithoutFrontmatter = rule.contents.replace(
/---[\s\S]*?---/m,
"",
);
const htmlContent = await marked(contentWithoutFrontmatter);

rulesByAep[rule.aep].push({
title: rule.title,
aep: rule.aep,
htmlContent: htmlContent,
filename: rule.filename,
slug: rule.slug,
preamble: rule.preamble,
});
}

// Generate paths for each AEP
return Object.keys(rulesByAep).map((aep) => ({
params: { aep },
props: {
rules: rulesByAep[aep],
aep,
},
}));
}

const { rules, aep } = Astro.props;

// Extract preamble from first rule if available
const preamble = rules[0]?.preamble || "";
---

<StarlightPage
frontmatter={{
title: `AEP-${aep} Linter Rules`,
tableOfContents: { minHeadingLevel: 1 },
}}
headings={[]}
>
<LinterRules rules={rules} preamble={preamble} />
</StarlightPage>
75 changes: 75 additions & 0 deletions src/pages/tooling/openapi-linter/rules/[aep].astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
/**
* Dynamic route for OpenAPI Linter rule pages
* Reads directly from external linter repo and consolidates by AEP number
*/

import StarlightPage from "@astrojs/starlight/components/StarlightPage.astro";
import LinterRules from "../../../../components/LinterRules.astro";
import { assembleOpenAPILinterRules } from "../../../../../scripts/src/linter";
import { marked } from "marked";

const AEP_OPENAPI_LINTER_LOC = process.env.AEP_OPENAPI_LINTER_LOC || "";

export async function getStaticPaths() {
const AEP_OPENAPI_LINTER_LOC = process.env.AEP_OPENAPI_LINTER_LOC || "";

if (!AEP_OPENAPI_LINTER_LOC) {
console.warn(
"⚠️ AEP_OPENAPI_LINTER_LOC not set, skipping OpenAPI linter rules",
);
return [];
}

// Read rules directly from external repo
const allRules = await assembleOpenAPILinterRules(AEP_OPENAPI_LINTER_LOC);

// Group rules by AEP number
const rulesByAep: Record<string, any[]> = {};
for (const rule of allRules) {
if (!rulesByAep[rule.aep]) {
rulesByAep[rule.aep] = [];
}

// Strip frontmatter and render markdown to HTML
const contentWithoutFrontmatter = rule.contents.replace(
/---[\s\S]*?---/m,
"",
);
const htmlContent = await marked(contentWithoutFrontmatter);

rulesByAep[rule.aep].push({
title: rule.title,
aep: rule.aep,
htmlContent: htmlContent,
filename: rule.filename,
slug: rule.slug,
preamble: rule.preamble,
});
}

// Generate paths for each AEP
return Object.keys(rulesByAep).map((aep) => ({
params: { aep },
props: {
rules: rulesByAep[aep],
aep,
},
}));
}

const { rules, aep } = Astro.props;

// Extract preamble from first rule if available
const preamble = rules[0]?.preamble || "";
---

<StarlightPage
frontmatter={{
title: `AEP-${aep} Linter Rules`,
tableOfContents: { minHeadingLevel: 1 },
}}
headings={[]}
>
<LinterRules rules={rules} preamble={preamble} />
</StarlightPage>
Loading