diff --git a/README.md b/README.md index cd4db1d..1090138 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,8 @@ plugin documentation { includeRelationships = true includePolicies = true includeValidation = true + includeGeneratedHeader = true + includeGenerationStats = true includeIndexes = true generateSkill = true generateErd = true @@ -158,6 +160,8 @@ plugin documentation { | `includeRelationships` | `boolean` | `true` | Generate relationship sections and `relationships.md` | | `includePolicies` | `boolean` | `true` | Generate access policy tables | | `includeValidation` | `boolean` | `true` | Generate validation rule tables | +| `includeGeneratedHeader` | `boolean` | `true` | Show the top-of-file “DO NOT MODIFY” banner (source path and generation date) on every generated page | +| `includeGenerationStats` | `boolean` | `true` | Append the collapsible **Generation Stats** block (files, duration, source, timestamp) to `index.md` | | `includeIndexes` | `boolean` | `true` | Generate index/constraint tables | | `generateSkill` | `boolean` | `false` | Generate a `SKILL.md` file for AI agent consumption | | `generateErd` | `boolean` | `false` | Generate a complete ERD as `.mmd` and `.svg` files | diff --git a/src/extractors.ts b/src/extractors.ts index 0febce0..ae5d0d3 100644 --- a/src/extractors.ts +++ b/src/extractors.ts @@ -316,6 +316,7 @@ export function resolveRenderOptions(options: PluginOptions): RenderOptions { return { fieldOrder: options.fieldOrder === 'alphabetical' ? 'alphabetical' : 'declaration', + includeGeneratedHeader: options.includeGeneratedHeader !== false, includeIndexes: options.includeIndexes !== false, includePolicies: options.includePolicies !== false, includeRelationships: options.includeRelationships !== false, diff --git a/src/generator.ts b/src/generator.ts index 3ab7727..f797dfe 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -130,6 +130,7 @@ export async function generate(context: CliGeneratorContext): Promise { const mdPath = path.join(outputDir, 'relationships.md'); const content = renderRelationshipsPage({ genCtx, + includeGeneratedHeader: options.includeGeneratedHeader, relations: allRelations, }); await writePageWithDiagrams(mdPath, content, diagFmt, diagEmbed, diagTheme); @@ -322,6 +323,8 @@ function resolvePluginOptions(raw: Record): PluginOptions { raw['fieldOrder'] === 'alphabetical' ? 'alphabetical' : 'declaration', generateErd: raw['generateErd'] === true, generateSkill: raw['generateSkill'] === true, + includeGeneratedHeader: raw['includeGeneratedHeader'] !== false, + includeGenerationStats: raw['includeGenerationStats'] !== false, includeIndexes: raw['includeIndexes'] !== false, includeInternalModels: raw['includeInternalModels'] === true, includePolicies: raw['includePolicies'] !== false, diff --git a/src/renderers/common.ts b/src/renderers/common.ts index f372daf..a1abf32 100644 --- a/src/renderers/common.ts +++ b/src/renderers/common.ts @@ -81,7 +81,14 @@ export function breadcrumbs( /** * Renders the auto-generated header banner with optional source file and generation date. */ -export function generatedHeader(context?: GenerationContext): string[] { +export function generatedHeader( + context?: GenerationContext, + includeBanner = true, +): string[] { + if (!includeBanner) { + return []; + } + const metaParts: string[] = []; if (context?.schemaFile) { metaParts.push(`Source: ${context.schemaFile}`); diff --git a/src/renderers/enum-page.ts b/src/renderers/enum-page.ts index b403453..943dcb7 100644 --- a/src/renderers/enum-page.ts +++ b/src/renderers/enum-page.ts @@ -67,7 +67,10 @@ function collectEnumUsage(enumDecl: Enum, allModels: DataModel[]): EnumUsage[] { */ function renderHeader(props: EnumPageProps): string[] { return [ - ...generatedHeader(props.options.genCtx), + ...generatedHeader( + props.options.genCtx, + props.options.includeGeneratedHeader, + ), breadcrumbs('Enums', props.enumDecl.name, '../'), '', `# ${props.enumDecl.name} Enum`, diff --git a/src/renderers/index-page.ts b/src/renderers/index-page.ts index aec29c6..1e46564 100644 --- a/src/renderers/index-page.ts +++ b/src/renderers/index-page.ts @@ -27,6 +27,8 @@ type IndexData = { hasErdMmd: boolean; hasErdSvg: boolean; hasRelationships: boolean; + includeGeneratedHeader: boolean; + includeGenerationStats: boolean; models: DataModel[]; procedures: Procedure[]; title: string; @@ -51,7 +53,7 @@ export function renderIndexPage(props: IndexPageProps): string { ...renderProceduresSection(data), ...renderErdSection(data), ...renderSeeAlso(data), - ...renderGenerationStats(data.genCtx), + ...renderGenerationStats(data.genCtx, data.includeGenerationStats), ].join('\n'); } @@ -132,7 +134,14 @@ function renderErdSection(data: IndexData): string[] { /** * Renders a collapsible footer with generation stats (files, duration, source, date). */ -function renderGenerationStats(genContext?: GenerationContext): string[] { +function renderGenerationStats( + genContext: GenerationContext | undefined, + includeStats: boolean, +): string[] { + if (!includeStats) { + return []; + } + if (genContext?.durationMs == null || genContext.filesGenerated == null) { return []; } @@ -179,7 +188,7 @@ function renderModelsSection(data: IndexData): string[] { */ function renderPageHeader(data: IndexData): string[] { return [ - ...generatedHeader(data.genCtx), + ...generatedHeader(data.genCtx, data.includeGeneratedHeader), `# ${data.title}`, '', 'This documentation describes a [ZModel](https://zenstack.dev/docs/reference/zmodel/overview) schema' + @@ -368,6 +377,8 @@ function resolveIndexData(props: IndexPageProps): IndexData { hasErdMmd: props.hasErdMmd === true, hasErdSvg: props.hasErdSvg === true, hasRelationships, + includeGeneratedHeader: pluginOptions.includeGeneratedHeader !== false, + includeGenerationStats: pluginOptions.includeGenerationStats !== false, models: allDataModels .filter((m) => !m.isView) .sort((a, b) => a.name.localeCompare(b.name)), diff --git a/src/renderers/model-page.ts b/src/renderers/model-page.ts index 9fa6e43..c977acb 100644 --- a/src/renderers/model-page.ts +++ b/src/renderers/model-page.ts @@ -150,7 +150,10 @@ function renderHeader(props: ModelPageProps): string[] { } return [ - ...generatedHeader(props.options.genCtx), + ...generatedHeader( + props.options.genCtx, + props.options.includeGeneratedHeader, + ), breadcrumbs('Models', props.model.name, '../'), '', `# 🗃️ ${nameDisplay} ${badgeParts.join(' ')}`, diff --git a/src/renderers/procedure-page.ts b/src/renderers/procedure-page.ts index 0a406a9..0e85637 100644 --- a/src/renderers/procedure-page.ts +++ b/src/renderers/procedure-page.ts @@ -61,7 +61,10 @@ function renderFlowDiagram(proc: Procedure): string[] { */ function renderHeader(props: ProcedurePageProps): string[] { return [ - ...generatedHeader(props.options.genCtx), + ...generatedHeader( + props.options.genCtx, + props.options.includeGeneratedHeader, + ), breadcrumbs('Procedures', props.proc.name, '../'), '', `# ${props.proc.name} ${props.proc.mutation ? 'Mutation' : 'Query'}`, diff --git a/src/renderers/relationships-page.ts b/src/renderers/relationships-page.ts index ceb1ace..d44d0c5 100644 --- a/src/renderers/relationships-page.ts +++ b/src/renderers/relationships-page.ts @@ -75,7 +75,7 @@ function renderErDiagram( */ function renderHeader(props: RelationshipsPageProps): string[] { return [ - ...generatedHeader(props.genCtx), + ...generatedHeader(props.genCtx, props.includeGeneratedHeader), '[Index](./index.md) / Relationships', '', '# Relationships', diff --git a/src/renderers/type-page.ts b/src/renderers/type-page.ts index 99acbc2..3648ba0 100644 --- a/src/renderers/type-page.ts +++ b/src/renderers/type-page.ts @@ -79,7 +79,10 @@ function renderClassDiagram(typeDef: TypeDef, usedBy: DataModel[]): string[] { */ function renderHeader(props: TypePageProps): string[] { return [ - ...generatedHeader(props.options.genCtx), + ...generatedHeader( + props.options.genCtx, + props.options.includeGeneratedHeader, + ), breadcrumbs('Types', props.typeDef.name, '../'), '', `# ${props.typeDef.name} Type`, diff --git a/src/renderers/view-page.ts b/src/renderers/view-page.ts index 87223f4..2de8391 100644 --- a/src/renderers/view-page.ts +++ b/src/renderers/view-page.ts @@ -63,7 +63,10 @@ function renderHeader(props: ViewPageProps): string[] { : ' View'; return [ - ...generatedHeader(props.options.genCtx), + ...generatedHeader( + props.options.genCtx, + props.options.includeGeneratedHeader, + ), breadcrumbs('Views', props.view.name, '../'), '', `# ${nameDisplay}${badges}`, diff --git a/src/types.ts b/src/types.ts index bbf4fbe..dcb19de 100644 --- a/src/types.ts +++ b/src/types.ts @@ -81,6 +81,8 @@ export type PluginOptions = { fieldOrder?: 'alphabetical' | 'declaration'; generateErd?: boolean; generateSkill?: boolean; + includeGeneratedHeader?: boolean; + includeGenerationStats?: boolean; includeIndexes?: boolean; includeInternalModels?: boolean; includePolicies?: boolean; @@ -114,6 +116,7 @@ export type Relationship = { export type RelationshipsPageProps = { genCtx?: GenerationContext; + includeGeneratedHeader: boolean; relations: Relationship[]; }; @@ -137,6 +140,7 @@ export type RenderOptions = { * Metadata about the current generation run (timestamps, file counts). */ genCtx?: GenerationContext; + includeGeneratedHeader: boolean; includeIndexes: boolean; includePolicies: boolean; includeRelationships: boolean; diff --git a/test/generator/__snapshots__/snapshot.test.ts.snap b/test/generator/__snapshots__/snapshot.test.ts.snap index e034400..df248c4 100644 --- a/test/generator/__snapshots__/snapshot.test.ts.snap +++ b/test/generator/__snapshots__/snapshot.test.ts.snap @@ -5,7 +5,7 @@ exports[`documentation plugin: snapshot > snapshot: full representative schema o ////////////////////////////////////////////////////////////////////////////////////////////// // DO NOT MODIFY THIS FILE // // This file is automatically generated by ZenStack CLI and should not be manually updated. // -// Source: schema.zmodel · Generated: 2026-03-08 // +// Source: schema.zmodel · Generated: // ////////////////////////////////////////////////////////////////////////////////////////////// \`\`\` @@ -73,7 +73,7 @@ exports[`documentation plugin: snapshot > snapshot: full representative schema o ////////////////////////////////////////////////////////////////////////////////////////////// // DO NOT MODIFY THIS FILE // // This file is automatically generated by ZenStack CLI and should not be manually updated. // -// Source: schema.zmodel · Generated: 2026-03-08 // +// Source: schema.zmodel · Generated: // ////////////////////////////////////////////////////////////////////////////////////////////// \`\`\` @@ -125,7 +125,7 @@ exports[`documentation plugin: snapshot > snapshot: full representative schema o ////////////////////////////////////////////////////////////////////////////////////////////// // DO NOT MODIFY THIS FILE // // This file is automatically generated by ZenStack CLI and should not be manually updated. // -// Source: schema.zmodel · Generated: 2026-03-08 // +// Source: schema.zmodel · Generated: // ////////////////////////////////////////////////////////////////////////////////////////////// \`\`\` @@ -218,7 +218,7 @@ exports[`documentation plugin: snapshot > snapshot: full representative schema o ////////////////////////////////////////////////////////////////////////////////////////////// // DO NOT MODIFY THIS FILE // // This file is automatically generated by ZenStack CLI and should not be manually updated. // -// Source: schema.zmodel · Generated: 2026-03-08 // +// Source: schema.zmodel · Generated: // ////////////////////////////////////////////////////////////////////////////////////////////// \`\`\` @@ -349,7 +349,7 @@ exports[`documentation plugin: snapshot > snapshot: full representative schema o ////////////////////////////////////////////////////////////////////////////////////////////// // DO NOT MODIFY THIS FILE // // This file is automatically generated by ZenStack CLI and should not be manually updated. // -// Source: schema.zmodel · Generated: 2026-03-08 // +// Source: schema.zmodel · Generated: // ////////////////////////////////////////////////////////////////////////////////////////////// \`\`\` diff --git a/test/generator/common.test.ts b/test/generator/common.test.ts index a201325..1f4263f 100644 --- a/test/generator/common.test.ts +++ b/test/generator/common.test.ts @@ -50,6 +50,31 @@ describe('documentation plugin: common features', () => { expect(userDocument).not.toContain('[!CAUTION]'); }); + it('omits the generated banner when includeGeneratedHeader is false', async () => { + const noBanner = await generateFromSchema( + ` + model User { + id String @id @default(cuid()) + posts Post[] + } + model Post { + id String @id @default(cuid()) + author User @relation(fields: [authorId], references: [id]) + authorId String + } + `, + { includeGeneratedHeader: false }, + ); + + const headerPattern = /DO NOT MODIFY THIS FILE/u; + expect(readDoc(noBanner, 'index.md')).not.toMatch(headerPattern); + expect(readDoc(noBanner, 'models', 'User.md')).not.toMatch(headerPattern); + expect(readDoc(noBanner, 'relationships.md')).not.toMatch(headerPattern); + expect(readDoc(noBanner, 'index.md')).toMatch( + /^# Schema Documentation\n/u, + ); + }); + it('entity pages show breadcrumb navigation and type badges', () => { const userDocument = readDoc(tmpDir, 'models', 'User.md'); expect(userDocument).toContain('[Index](../index.md)'); diff --git a/test/generator/index-page.test.ts b/test/generator/index-page.test.ts index 822edc4..159f639 100644 --- a/test/generator/index-page.test.ts +++ b/test/generator/index-page.test.ts @@ -99,6 +99,21 @@ describe('documentation plugin: index page', () => { }); }); + it('omits generation stats on index.md when includeGenerationStats is false', async () => { + const tmpDir = await generateFromSchema( + ` + model User { + id String @id @default(cuid()) + } + `, + { includeGenerationStats: false }, + ); + + const indexContent = readDocument(tmpDir, 'index.md'); + expect(indexContent).not.toContain('Generation Stats'); + expect(indexContent).not.toContain('**Duration**'); + }); + it('lists views in a separate section from models', async () => { const tmpDir = await generateFromSchema(` model User { diff --git a/test/generator/snapshot.test.ts b/test/generator/snapshot.test.ts index 2a5a21e..c01396a 100644 --- a/test/generator/snapshot.test.ts +++ b/test/generator/snapshot.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'; function stabilize(content: string): string { return content + .replaceAll(/(· Generated: )\d{4}-\d{2}-\d{2}/gu, '$1') .replaceAll( /zenstack-schema-[\da-f-]+\.zmodel/gu, 'zenstack-schema-.zmodel',