diff --git a/eslint.config.js b/eslint.config.js index cf20609ed..6f51e4832 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -140,6 +140,7 @@ export default tseslint.config( ignores: [ '**/*.mock.*', '**/code-pushup.config.ts', + '**/zod2md.config.ts', '**/mocks/fixtures/**', '**/__snapshots__/**', '**/dist', diff --git a/nx.json b/nx.json index 4f25228cc..879cc1050 100644 --- a/nx.json +++ b/nx.json @@ -9,7 +9,6 @@ "!{projectRoot}/CHANGELOG.md", "!{projectRoot}/perf/**/*", "!{projectRoot}/tools/**/*", - "!{projectRoot}/zod2md.config.ts", "!{projectRoot}/eslint.config.?(c)js", "!{workspaceRoot}/**/.code-pushup/**/*", "!{projectRoot}/code-pushup.config.?(m)[jt]s", @@ -46,7 +45,7 @@ "options": { "command": "eslint", "args": [ - "'{projectRoot}/**/*.ts'", + "'{projectRoot}/**/*.{ts,cjs,mjs,js}'", "{projectRoot}/package.json", "--config={projectRoot}/eslint.config.js", "--max-warnings=0", @@ -337,6 +336,7 @@ "releaseTagPattern": "v{version}" }, "plugins": [ + "./tools/zod2md-jsdocs/src/nx-plugin.cjs", { "plugin": "@push-based/nx-verdaccio", "options": { diff --git a/packages/models/eslint.config.js b/packages/models/eslint.config.js index 26af011ea..48fd0b2be 100644 --- a/packages/models/eslint.config.js +++ b/packages/models/eslint.config.js @@ -18,7 +18,4 @@ export default tseslint.config( '@nx/dependency-checks': 'error', }, }, - { - ignores: ['packages/models/transformers/**/*.ts'], - }, ); diff --git a/packages/models/package.json b/packages/models/package.json index be3e4daee..68c536727 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -29,7 +29,8 @@ "dependencies": { "ansis": "^3.3.2", "vscode-material-icons": "^0.1.0", - "zod": "^4.0.5" + "zod": "^4.0.5", + "zod2md": "^0.2.4" }, "files": [ "src", diff --git a/packages/models/project.json b/packages/models/project.json index b09523d29..282f79504 100644 --- a/packages/models/project.json +++ b/packages/models/project.json @@ -4,23 +4,11 @@ "sourceRoot": "packages/models/src", "projectType": "library", "targets": { - "generate-docs": { - "executor": "nx:run-commands", - "options": { - "commands": [ - "zod2md --config {projectRoot}/zod2md.config.ts", - "prettier --write {projectRoot}/docs/models-reference.md" - ], - "parallel": false - }, - "cache": true, - "inputs": ["production", "^production", "{projectRoot}/zod2md.config.ts"], - "outputs": ["{projectRoot}/docs/models-reference.md"] - }, "build": { "dependsOn": [ "^build", "generate-docs", + "ts-patch", { "projects": "zod2md-jsdocs", "target": "build" } ] }, diff --git a/tools/zod2md-jsdocs/README.md b/tools/zod2md-jsdocs/README.md index c56a57a0b..d40a648a5 100644 --- a/tools/zod2md-jsdocs/README.md +++ b/tools/zod2md-jsdocs/README.md @@ -1,57 +1,41 @@ -# @code-pushup/zod2md-jsdocs +# zod2md-jsdocs -TypeScript transformer plugin that automatically enhances type definitions with JSDoc comments and schema metadata. +A comprehensive toolset for generating and enhancing TypeScript documentation from Zod schemas. This package combines an Nx plugin for automated documentation generation with a TypeScript transformer that enriches type definitions with JSDoc comments. -## Purpose +## What's Included -This package provides a TypeScript compiler transformer that automatically adds JSDoc documentation to type aliases and interfaces during compilation. It's designed to improve developer experience by injecting helpful metadata and documentation links directly into generated type definitions. +This package provides two main components: -## How It Works +1. **[Nx Plugin](./docs/zod2md-jsdocs-nx-plugin.md)** - Automatically generates documentation targets for projects with Zod schemas +2. **[TypeScript Transformer](./docs/zod2md-jsdocs-ts-transformer.md)** - Enhances generated type definitions with JSDoc comments and schema metadata -The [TS transformer](https://github.com/itsdouges/typescript-transformer-handbook) hooks into the TypeScript compilation process using `ts-patch` and automatically adds JSDoc comments above type definitions. Each comment includes: +## Quick Start -- The type name -- A description explaining the type is derived from a Zod schema -- A link to the type reference documentation +### Using the Nx Plugin -## Example +Add the plugin to your `nx.json`: -Given a type definition like: - -```typescript -export type Report = { - // ... type properties -}; +```jsonc +{ + "plugins": ["./tools/zod2md-jsdocs/src/lib/plugin.js"], +} ``` -The transformer automatically generates: +Create a `zod2md.config.ts` in your project, and you'll automatically get a `generate-docs` target. -```typescript -/** - * Type Definition: `Report` - * - * This type is derived from a Zod schema and represents - * the validated structure of `Report` used within the application. - * - * @see {@link https://github.com/code-pushup/cli/blob/main/packages/models/docs/models-reference.md#report} - */ -export type Report = { - // ... type properties -}; -``` +[Learn more about the Nx Plugin →](./docs/zod2md-jsdocs-nx-plugin.md) -## Usage +### Using the TypeScript Transformer -1. `ts-patch install` - -2. Add the transformer to your `tsconfig.json`: +1. Install ts-patch: `ts-patch install` +2. Add to your `tsconfig.json`: ```json { "compilerOptions": { "plugins": [ { - "transform": "./path/to/transformer/dist", + "transform": "./tools/zod2md-jsdocs/dist/src", "afterDeclarations": true, "baseUrl": "https://example.com/docs/api-reference.md" } @@ -60,12 +44,4 @@ export type Report = { } ``` -3. Build your TypeScript project. The transformer will run automatically and add JSDoc comments to your type definitions. - -### Options - -| Option | Type | Required | Description | -| ------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `transform` | `string` | Yes | Path to the transformer module | -| `afterDeclarations` | `boolean` | No | Set to `true` to run the transformer after TypeScript generates declaration files (`.d.ts`). This ensures JSDoc comments are added to the emitted type definitions. | -| `baseUrl` | `string` | Yes | Base URL for documentation links (e.g., `https://example.com/docs/api-reference.md`) | +[Learn more about the TypeScript Transformer →](./docs/zod2md-jsdocs-ts-transformer.md) diff --git a/tools/zod2md-jsdocs/docs/zod2md-jsdocs-nx-plugin.md b/tools/zod2md-jsdocs/docs/zod2md-jsdocs-nx-plugin.md new file mode 100644 index 000000000..2dd2d58a4 --- /dev/null +++ b/tools/zod2md-jsdocs/docs/zod2md-jsdocs-nx-plugin.md @@ -0,0 +1,122 @@ +# @code-pushup/zod2md-jsdocs-nx-plugin + +The Nx Plugin for [zod2md](https://github.com/matejchalk/zod2md), a tool for generating documentation from Zod schemas. + +Why should you use this plugin? + +- Zero setup cost. Just add a `zod2md.config.ts` file and you're good to go. +- Automatic target generation +- Minimal configuration +- Automated caching and dependency tracking + +## Usage + +```jsonc +// nx.json +{ + //... + "plugins": ["./tools/zod2md-jsdocs-nx-plugin/src/lib/plugin.js"], +} +``` + +or with options: + +```jsonc +// nx.json +{ + //... + "plugins": [ + { + "plugin": "./tools/zod2md-jsdocs-nx-plugin/src/lib/plugin.js", + "options": { + "targetName": "zod-docs", + }, + }, + ], +} +``` + +Now every project with a `zod2md.config.ts` file will have a `generate-docs` target automatically created. + +- `nx run :generate-docs` + +Run it and the project will automatically generate documentation from your Zod schemas. + +```text +Root/ +├── project-name/ +│ ├── zod2md.config.ts +│ ├── docs/ +│ │ └── project-name-reference.md 👈 generated +│ └── ... +└── ... +``` + +The generated target: + +1. Runs `zod2md` with the project's configuration +2. Formats the generated markdown with Prettier +3. Caches the result for better performance + +### Passing zod2md options + +You can override the config and output paths when running the target: + +```bash +# Use custom output file +nx generate-docs my-project --output=docs/custom-api.md + +# Use custom config file +nx generate-docs my-project --config=custom-zod2md.config.ts + +# Use both +nx generate-docs my-project --config=custom.config.ts --output=docs/api.md +``` + +Default values: + +- `config`: `{projectRoot}/zod2md.config.ts` +- `output`: `{projectRoot}/docs/{projectName}-reference.md` + +## Options + +| Name | type | description | +| --------------------------- | ---------------------------------- | ------------------------------------------------------------------- | +| **docsTargetName** | `string` (DEFAULT 'generate-docs') | The name of the docs generation target. | +| **jsDocsTypesAugmentation** | `boolean` (DEFAULT `true`) | Whether to enable TypeScript transformer integration with ts-patch. | + +All options are optional and provided in the `nx.json` file. + +```jsonc +// nx.json +{ + //... + "plugins": [ + { + "plugin": "./tools/zod2md-jsdocs-nx-plugin/src/lib/plugin.js", + "options": { + "docsTargetName": "docs", + "jsDocsTypesAugmentation": true, + }, + }, + ], +} +``` + +## Configuration + +Create a `zod2md.config.ts` file in your project: + +```ts +import type { Config } from 'zod2md'; + +export default { + entry: 'packages/models/src/index.ts', + tsconfig: 'packages/models/tsconfig.lib.json', + format: 'esm', + title: 'Models reference', + output: 'packages/models/docs/models-reference.md', +} satisfies Config; +``` + +For a full list of configuration options visit the [zod2md documentation](https://github.com/matejchalk/zod2md?tab=readme-ov-file#configuration). diff --git a/tools/zod2md-jsdocs/docs/zod2md-jsdocs-ts-transformer.md b/tools/zod2md-jsdocs/docs/zod2md-jsdocs-ts-transformer.md new file mode 100644 index 000000000..2bfcae90f --- /dev/null +++ b/tools/zod2md-jsdocs/docs/zod2md-jsdocs-ts-transformer.md @@ -0,0 +1,71 @@ +# zod2md-jsdocs-ts-transformer + +TypeScript transformer plugin that automatically enhances type definitions with JSDoc comments and schema metadata. + +## Purpose + +This package provides a TypeScript compiler transformer that automatically adds JSDoc documentation to type aliases and interfaces during compilation. It's designed to improve developer experience by injecting helpful metadata and documentation links directly into generated type definitions. + +## How It Works + +The [TS transformer](https://github.com/itsdouges/typescript-transformer-handbook) hooks into the TypeScript compilation process using `ts-patch` and automatically adds JSDoc comments above type definitions. Each comment includes: + +- The type name +- A description explaining the type is derived from a Zod schema +- A link to the type reference documentation + +## Example + +Given a type definition like: + +```typescript +export type Report = { + // ... type properties +}; +``` + +The transformer automatically generates: + +```typescript +/** + * Type Definition: `Report` + * + * This type is derived from a Zod schema and represents + * the validated structure of `Report` used within the application. + * + * @see {@link https://github.com/code-pushup/cli/blob/main/packages/models/docs/models-reference.md#report} + */ +export type Report = { + // ... type properties +}; +``` + +## Usage + +1. `ts-patch install` + +2. Add the transformer to your `tsconfig.json`: + +```json +{ + "compilerOptions": { + "plugins": [ + { + "transform": "./path/to/transformer/dist", + "afterDeclarations": true, + "baseUrl": "https://example.com/docs/api-reference.md" + } + ] + } +} +``` + +3. Build your TypeScript project. The transformer will run automatically and add JSDoc comments to your type definitions. + +### Options + +| Option | Type | Required | Description | +| ------------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `transform` | `string` | Yes | Path to the transformer module | +| `afterDeclarations` | `boolean` | No | Set to `true` to run the transformer after TypeScript generates declaration files (`.d.ts`). This ensures JSDoc comments are added to the emitted type definitions. | +| `baseUrl` | `string` | Yes | Base URL for documentation links (e.g., `https://example.com/docs/api-reference.md`) | diff --git a/tools/zod2md-jsdocs/eslint.config.cjs b/tools/zod2md-jsdocs/eslint.config.cjs deleted file mode 100644 index 467b6c94b..000000000 --- a/tools/zod2md-jsdocs/eslint.config.cjs +++ /dev/null @@ -1,11 +0,0 @@ -const baseConfig = require('../../eslint.config.js').default; - -module.exports = [ - ...baseConfig, - { - files: ['**/*.json'], - rules: { - '@nx/dependency-checks': 'error', - }, - }, -]; diff --git a/tools/zod2md-jsdocs/eslint.config.js b/tools/zod2md-jsdocs/eslint.config.js new file mode 100644 index 000000000..4facd9089 --- /dev/null +++ b/tools/zod2md-jsdocs/eslint.config.js @@ -0,0 +1,22 @@ +const baseConfig = require('../../eslint.config.js').default; + +module.exports = [ + ...baseConfig, + { + files: ['**/*.json'], + rules: { + '@nx/dependency-checks': 'error', + }, + }, + { + files: ['**/*.ts', '**/*.cjs', '**/*.js'], + rules: { + 'import/no-commonjs': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/no-unused-vars': 'off', + 'unicorn/prefer-module': 'off', + 'functional/immutable-data': 'off', + 'arrow-body-style': 'off', + }, + }, +]; diff --git a/tools/zod2md-jsdocs/project.json b/tools/zod2md-jsdocs/project.json index 18501546f..3ad54a536 100644 --- a/tools/zod2md-jsdocs/project.json +++ b/tools/zod2md-jsdocs/project.json @@ -4,20 +4,8 @@ "sourceRoot": "tools/zod2md-jsdocs/src", "projectType": "library", "targets": { - "build": { - "executor": "@nx/js:tsc", - "outputs": ["{options.outputPath}"], - "dependsOn": ["pre-build"], - "options": { - "outputPath": "tools/zod2md-jsdocs/dist", - "main": "tools/zod2md-jsdocs/src/index.ts", - "tsConfig": "tools/zod2md-jsdocs/tsconfig.lib.json" - } - }, - "pre-build": { - "command": "ts-patch install", - "cache": true, - "inputs": ["sharedGlobals", { "runtime": "ts-patch check" }] - } + "lint": {}, + "build": {}, + "unit-test": {} } } diff --git a/tools/zod2md-jsdocs/src/lib/transformers.ts b/tools/zod2md-jsdocs/src/lib/transformers.ts index 33fb2e6ba..dc146fd1d 100644 --- a/tools/zod2md-jsdocs/src/lib/transformers.ts +++ b/tools/zod2md-jsdocs/src/lib/transformers.ts @@ -3,7 +3,15 @@ import type * as ts from 'typescript'; const tsInstance: typeof ts = require('typescript'); -function generateJSDocComment(typeName: string, baseUrl: string): string { +/** + * Generates a JSDoc comment for a given type name and base URL. + * @param typeName + * @param baseUrl + */ +export function generateJSDocComment( + typeName: string, + baseUrl: string, +): string { const markdownLink = `${baseUrl}#${typeName.toLowerCase()}`; return `* * Type Definition: \`${typeName}\` @@ -46,9 +54,8 @@ function annotateTypeDefinitions( } return tsLib.visitEachChild(node, visitor, context); }; - return (sourceFile: ts.SourceFile) => { - return tsLib.visitNode(sourceFile, visitor, tsLib.isSourceFile); - }; + return (sourceFile: ts.SourceFile) => + tsLib.visitNode(sourceFile, visitor, tsLib.isSourceFile); }; } diff --git a/tools/zod2md-jsdocs/src/lib/transformers.unit.test.ts b/tools/zod2md-jsdocs/src/lib/transformers.unit.test.ts new file mode 100644 index 000000000..55b090a03 --- /dev/null +++ b/tools/zod2md-jsdocs/src/lib/transformers.unit.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest'; +import { generateJSDocComment } from './transformers.js'; + +describe('generateJSDocComment', () => { + it('should generate JSDoc comment with type name and base URL', () => { + const result = generateJSDocComment( + 'UserSchema', + 'https://example.com/docs', + ); + expect(result).toMatchInlineSnapshot(` + "* + * Type Definition: \`UserSchema\` + * + * This type is derived from a Zod schema and represents + * the validated structure of \`UserSchema\` used within the application. + * + * @see {@link https://example.com/docs#userschema} + " + `); + }); + + it('should use type name in description', () => { + const result = generateJSDocComment('SchemaName123', 'https://example.com'); + expect(result).toContain('Type Definition: `SchemaName123`'); + }); + + it('should convert type name to lowercase in the link anchor', () => { + const result = generateJSDocComment('SchemaName123', 'https://example.com'); + expect(result).toContain('#schemaname123'); + }); + + it('should baseUrl in the link', () => { + const result = generateJSDocComment('Schema', 'https://example.com'); + expect(result).toContain('https://example.com#'); + }); +}); diff --git a/tools/zod2md-jsdocs/src/nx-plugin.cjs b/tools/zod2md-jsdocs/src/nx-plugin.cjs new file mode 100644 index 000000000..256a87ece --- /dev/null +++ b/tools/zod2md-jsdocs/src/nx-plugin.cjs @@ -0,0 +1,84 @@ +const path = require('node:path'); + +const ZOD2MD_CONFIG_FILE = 'zod2md.config.ts'; +const TS_PATCH_TARGET_NAME = 'ts-patch'; +const GENERATE_DOCS_TARGET_NAME = 'generate-docs'; + +/** + * Creates the ts-patch target configuration + * @returns {object} ts-patch target configuration + */ +const createTsPatchTargetConfig = { + command: 'ts-patch install', + cache: true, + inputs: ['sharedGlobals', { runtime: 'ts-patch check' }], +}; + +/** + * Creates the docs generation target configuration + * @param {object} params - Configuration parameters + * @param {string} params.config - Path to the zod2md config file + * @param {string} params.output - Path to the output markdown file + * @returns {object} Docs target configuration + */ +function createDocsTargetConfig({ config, output }) { + return { + executor: 'nx:run-commands', + options: { + commands: [ + `zod2md --config ${config} --output ${output}`, + `prettier --write ${output}`, + ], + parallel: false, + }, + cache: true, + inputs: ['production', '^production', config], + outputs: [output], + }; +} + +const createNodesV2 = [ + `**/${ZOD2MD_CONFIG_FILE}`, + async (zod2MdConfigurationFiles, createNodesOptions) => { + const { + docsTargetName = GENERATE_DOCS_TARGET_NAME, + jsDocsTypesAugmentation = true, + } = createNodesOptions ?? {}; + + return Promise.all( + zod2MdConfigurationFiles.map(async zod2MdConfigurationFile => { + const projectRoot = path.dirname(zod2MdConfigurationFile); + const normalizedProjectRoot = projectRoot === '.' ? '' : projectRoot; + const output = '{projectRoot}/docs/{projectName}-reference.md'; + const config = `{projectRoot}/${ZOD2MD_CONFIG_FILE}`; + + const result = { + projects: { + [normalizedProjectRoot]: { + targets: { + ...(jsDocsTypesAugmentation + ? { [TS_PATCH_TARGET_NAME]: createTsPatchTargetConfig } + : {}), + [docsTargetName]: createDocsTargetConfig({ + config, + output, + }), + }, + }, + }, + }; + + return [zod2MdConfigurationFile, result]; + }), + ); + }, +]; + +// default export for nx.json#plugins +const nxPlugin = { + name: 'zod2md-jsdocs-nx-plugin', + createNodesV2, +}; + +module.exports = nxPlugin; +module.exports.createNodesV2 = createNodesV2;