From f493f967423e935e22b9faf03614c9bd8ec2280e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:32:18 +0000 Subject: [PATCH 1/5] chore(deps): bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- .github/workflows/publish.yml | 2 +- .github/workflows/release-please.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57075bc..1bd5da8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: node-version: [20, 22] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 35e9459..9c37374 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,7 +12,7 @@ jobs: id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v4 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index c184e90..e5ead8b 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -29,7 +29,7 @@ jobs: contents: read id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install pnpm uses: pnpm/action-setup@v4 From bbb3a23d03c89d4d4717a979d521519debe46591 Mon Sep 17 00:00:00 2001 From: Lukas Kahwe Smith Date: Fri, 27 Mar 2026 13:32:36 +0100 Subject: [PATCH 2/5] fix flaky tests --- test/generator/__snapshots__/snapshot.test.ts.snap | 10 +++++----- test/generator/snapshot.test.ts | 6 ++---- 2 files changed, 7 insertions(+), 9 deletions(-) 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/snapshot.test.ts b/test/generator/snapshot.test.ts index 2a5a21e..8fae65f 100644 --- a/test/generator/snapshot.test.ts +++ b/test/generator/snapshot.test.ts @@ -19,10 +19,8 @@ function stabilize(content: string): string { /\*\*Generated\*\* \| \d{4}-\d{2}-\d{2}/gu, '**Generated** | ', ) - .replaceAll( - /Generated:\*\* \d{4}-\d{2}-\d{2}/gu, - 'Generated:** ', - ); + .replaceAll(/Generated:\*\* \d{4}-\d{2}-\d{2}/gu, 'Generated:** ') + .replaceAll(/Generated: \d{4}-\d{2}-\d{2}/gu, 'Generated: '); } describe('documentation plugin: snapshot', () => { From 017e93b69c5255a60a105479ca5f2a0a2588d211 Mon Sep 17 00:00:00 2001 From: Lukas Kahwe Smith Date: Fri, 27 Mar 2026 13:33:05 +0100 Subject: [PATCH 3/5] fix #10, add cleanOutput option --- README.md | 1 + src/generator.ts | 18 ++++++++++++++++++ src/types.ts | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/README.md b/README.md index cd4db1d..9560fc7 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,7 @@ plugin documentation { | Option | Type | Default | Description | |---|---|---|---| | `output` | `string` | ZenStack default output path | Directory to write generated docs | +| `cleanOutput` | `boolean` | `false` | When `true`, the output directory is removed before generation to ensure a clean build (use with caution) | | `title` | `string` | `"Schema Documentation"` | Heading on the index page | | `fieldOrder` | `"declaration"` or `"alphabetical"` | `"declaration"` | How fields are ordered in tables | | `includeInternalModels` | `boolean` | `false` | Include models marked `@@ignore` in output | diff --git a/src/generator.ts b/src/generator.ts index 3ab7727..294df90 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -44,6 +44,23 @@ export async function generate(context: CliGeneratorContext): Promise { options.genCtx = genCtx; try { + if (pluginOptions.cleanOutput) { + try { + const root = path.parse(outputDir).root; + if (outputDir === root) { + throw new Error('Refusing to remove root directory'); + } + + if (fs.existsSync(outputDir)) { + fs.rmSync(outputDir, { force: true, recursive: true }); + } + } catch (error) { + throw new Error( + `Failed to clean output directory "${outputDir}": ${error instanceof Error ? error.message : String(error)}`, + ); + } + } + fs.mkdirSync(outputDir, { recursive: true }); } catch (error) { throw new Error( @@ -302,6 +319,7 @@ function resolveOutputDir(options: PluginOptions, defaultPath: string): string { */ function resolvePluginOptions(raw: Record): PluginOptions { return { + cleanOutput: raw['cleanOutput'] === true, diagramEmbed: (['file', 'inline'] as const).includes( raw['diagramEmbed'] as 'file' | 'inline', ) diff --git a/src/types.ts b/src/types.ts index bbf4fbe..4fc8bf6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -74,6 +74,10 @@ export type PluginOptions = { * inline in the markdown (`'inline'`). Only applies when `diagramFormat` is * `'svg'` or `'both'`. */ + /** + * If true, delete the output directory before generating files. + */ + cleanOutput?: boolean; diagramEmbed?: 'file' | 'inline'; diagramFormat?: 'both' | 'mermaid' | 'svg'; erdFormat?: 'both' | 'mmd' | 'svg'; From 218d8d70ac5a9aadea03c343fcab2f603bd44f7e Mon Sep 17 00:00:00 2001 From: Lukas Kahwe Smith Date: Fri, 27 Mar 2026 13:33:30 +0100 Subject: [PATCH 4/5] fix #15, add support for @@meta('doc:ignore', true) in includeInternalModels --- README.md | 2 +- src/extractors.ts | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9560fc7..8ad7307 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ plugin documentation { | `cleanOutput` | `boolean` | `false` | When `true`, the output directory is removed before generation to ensure a clean build (use with caution) | | `title` | `string` | `"Schema Documentation"` | Heading on the index page | | `fieldOrder` | `"declaration"` or `"alphabetical"` | `"declaration"` | How fields are ordered in tables | -| `includeInternalModels` | `boolean` | `false` | Include models marked `@@ignore` in output | +| `includeInternalModels` | `boolean` | `false` | Include models marked `@@ignore` or annotated with `@@meta('doc:ignore', true)` in output | | `includeRelationships` | `boolean` | `true` | Generate relationship sections and `relationships.md` | | `includePolicies` | `boolean` | `true` | Generate access policy tables | | `includeValidation` | `boolean` | `true` | Generate validation rule tables | diff --git a/src/extractors.ts b/src/extractors.ts index 0febce0..c81a25f 100644 --- a/src/extractors.ts +++ b/src/extractors.ts @@ -306,7 +306,24 @@ export function isFieldRequired(field: DataField): boolean { * Returns `true` if the model has the `@@ignore` attribute. */ export function isIgnoredModel(model: DataModel): boolean { - return model.attributes.some((a) => a.decl.ref?.name === '@@ignore'); + if (model.attributes.some((a) => a.decl.ref?.name === '@@ignore')) { + return true; + } + + // Support @@meta('doc:ignore', true) + for (const attribute of model.attributes) { + if (attribute.decl.ref?.name !== '@@meta') { + continue; + } + + const key = stripQuotes(attribute.args[0]?.$cstNode?.text ?? ''); + const value = stripQuotes(attribute.args[1]?.$cstNode?.text ?? ''); + if (key === 'doc:ignore' && value === 'true') { + return true; + } + } + + return false; } /** From 3ed60fda6d1c0013f2b6912da1eff8040956d65e Mon Sep 17 00:00:00 2001 From: Lukas Kahwe Smith Date: Fri, 27 Mar 2026 13:33:44 +0100 Subject: [PATCH 5/5] add tests for cleanOutput and @@meta('doc:ignore', true) --- test/generator/plugin-options.test.ts | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 test/generator/plugin-options.test.ts diff --git a/test/generator/plugin-options.test.ts b/test/generator/plugin-options.test.ts new file mode 100644 index 0000000..41cf22b --- /dev/null +++ b/test/generator/plugin-options.test.ts @@ -0,0 +1,61 @@ +import plugin from '../../src'; +import { loadSchema } from '../utils'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +describe('documentation plugin: plugin options', () => { + it('cleanOutput deletes existing files in the output directory before generation', async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'doc-plugin-')); + // create a stale file that should be removed by cleanOutput + fs.writeFileSync(path.join(tmpDir, 'stale.txt'), 'stale'); + + const model = await loadSchema(` + model A { + id String @id @default(cuid()) + } + `); + + await plugin.generate({ + defaultOutputPath: tmpDir, + model, + pluginOptions: { cleanOutput: true, output: tmpDir }, + schemaFile: 'schema.zmodel', + }); + + // ensure output dir is prepopulated and removed by cleanOutput + + expect(fs.existsSync(path.join(tmpDir, 'stale.txt'))).toBe(false); + expect(fs.existsSync(path.join(tmpDir, 'index.md'))).toBe(true); + }); + it("models with @@meta('doc:ignore', true) are treated as internal and excluded when includeInternalModels is false", async () => { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'doc-plugin-')); + + const schema = ` + model Public { + id String @id @default(cuid()) + } + + model Internal { + id String @id @default(cuid()) + @@meta('doc:ignore', true) + } + `; + + const model = await loadSchema(schema); + + await plugin.generate({ + defaultOutputPath: tmpDir, + model, + pluginOptions: { includeInternalModels: false, output: tmpDir }, + schemaFile: 'schema.zmodel', + }); + + // Public model should be present, internal model should be excluded + expect(fs.existsSync(path.join(tmpDir, 'models', 'Public.md'))).toBe(true); + expect(fs.existsSync(path.join(tmpDir, 'models', 'Internal.md'))).toBe( + false, + ); + }); +});