diff --git a/.chronus/changes/tspd-doc-rules-dir-option-2026-7-20-14-0-0.md b/.chronus/changes/tspd-doc-rules-dir-option-2026-7-20-14-0-0.md new file mode 100644 index 00000000000..857a669ab11 --- /dev/null +++ b/.chronus/changes/tspd-doc-rules-dir-option-2026-7-20-14-0-0.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/tspd" +--- + +Add a `--rules-dir` option (and `rulesDir` API option) to `tspd doc` to control where per-rule reference pages are written. Defaults to `rules` (relative to `--output-dir`); can be set to a path escaping the output dir (e.g. `../rules`) to keep rule pages outside the generated reference folder. diff --git a/packages/tspd/src/cli.ts b/packages/tspd/src/cli.ts index 4291f3834ea..aa6995ea5ec 100644 --- a/packages/tspd/src/cli.ts +++ b/packages/tspd/src/cli.ts @@ -85,6 +85,11 @@ async function main() { description: "Add llmstxt frontmatter to generated docs to aide in generating llms.txt files.", type: "boolean", + }) + .option("rules-dir", { + description: + "Relative directory (from --output-dir) where per-rule reference pages are written. Defaults to 'rules'. Use e.g. '../rules' to place them outside the reference folder.", + type: "string", }); }, async (args) => { @@ -97,6 +102,7 @@ async function main() { skipJSApi: args["skip-js"], typekits: args["typekits"], llmstxt: args["llmstxt"], + rulesDir: args["rules-dir"], }, ); // const diagnostics = await generateExternSignatures(host, resolvedRoot); diff --git a/packages/tspd/src/ref-doc/emitters/starlight.ts b/packages/tspd/src/ref-doc/emitters/starlight.ts index 96d88252daa..bad5ee311a1 100644 --- a/packages/tspd/src/ref-doc/emitters/starlight.ts +++ b/packages/tspd/src/ref-doc/emitters/starlight.ts @@ -20,6 +20,12 @@ import { MarkdownRenderer, groupByNamespace } from "./markdown.js"; export interface RenderToStarlightMarkdownOptions { llmstxt?: boolean; + /** + * Relative directory (from the output dir) where the per-rule reference pages are written. + * Defaults to `"rules"` (i.e. `/rules`). Can be set to a path escaping the output + * dir (e.g. `"../rules"`) to keep rule pages at a location outside the generated reference folder. + */ + rulesDir?: string; } /** @@ -29,7 +35,7 @@ export function renderToAstroStarlightMarkdown( refDoc: TypeSpecRefDoc, options: RenderToStarlightMarkdownOptions = {}, ): Record { - const renderer = new StarlightRenderer(refDoc); + const renderer = new StarlightRenderer(refDoc, options); const files: Record = { "index.mdx": renderIndexFile(renderer, refDoc), }; @@ -58,8 +64,9 @@ export function renderToAstroStarlightMarkdown( files["linter.md"] = linter; } + const rulesDir = options.rulesDir ?? "rules"; for (const rule of refDoc.linter?.rules ?? []) { - files[`rules/${rule.rule.name}.md`] = renderRule(rule); + files[`${rulesDir}/${rule.rule.name}.md`] = renderRule(rule); } // Generate one page per documented diagnostic, under `diagnostics/`. No index page. @@ -431,6 +438,11 @@ function renderSubExport( } export class StarlightRenderer extends MarkdownRenderer { + #rulesDir: string; + constructor(refDoc: TypeSpecRefDoc, options: RenderToStarlightMarkdownOptions = {}) { + super(refDoc); + this.#rulesDir = options.rulesDir ?? "rules"; + } headingTitle(item: NamedTypeRefDoc): string { // Set an explicit anchor id. return `${inlinecode(item.name)} {#${item.id}}`; @@ -473,7 +485,8 @@ export class StarlightRenderer extends MarkdownRenderer { } linterRuleLink(rule: LinterRuleRefDoc) { - return `./rules/${rule.rule.name}.md`; + const prefix = this.#rulesDir.startsWith(".") ? this.#rulesDir : `./${this.#rulesDir}`; + return `${prefix}/${rule.rule.name}.md`; } deprecationNotice(notice: DeprecationNotice): MarkdownDoc { diff --git a/packages/tspd/src/ref-doc/experimental.ts b/packages/tspd/src/ref-doc/experimental.ts index 188dd252b3a..886f96a2c24 100644 --- a/packages/tspd/src/ref-doc/experimental.ts +++ b/packages/tspd/src/ref-doc/experimental.ts @@ -19,6 +19,11 @@ export interface GenerateLibraryDocsOptions { typekits?: boolean; skipJSApi?: boolean; llmstxt?: boolean; + /** + * Relative directory (from the output dir) where the per-rule reference pages are written. + * Defaults to `"rules"`. Pass e.g. `"../rules"` to keep rule pages outside the reference folder. + */ + rulesDir?: string; } /** * @experimental this is for experimental and is for internal use only. Breaking change to this API can happen at anytime. @@ -31,7 +36,10 @@ export async function generateLibraryDocs( const diagnostics = createDiagnosticCollector(); const pkgJson = await readPackageJson(libraryPath); const refDoc = diagnostics.pipe(await extractLibraryRefDocs(libraryPath)); - const files = renderToAstroStarlightMarkdown(refDoc, options); + const files = renderToAstroStarlightMarkdown(refDoc, { + llmstxt: options.llmstxt, + rulesDir: options.rulesDir, + }); await mkdir(outputDir, { recursive: true }); const config = await prettier.resolveConfig(libraryPath); for (const [name, content] of Object.entries(files)) {