Skip to content

Commit 1c49353

Browse files
authored
chore: automatic README generation from user config schema (#709)
1 parent 3d7208f commit 1c49353

File tree

7 files changed

+471
-108
lines changed

7 files changed

+471
-108
lines changed

README.md

Lines changed: 26 additions & 25 deletions
Large diffs are not rendered by default.

eslint-rules/enforce-zod-v4.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use strict";
2+
import path from "path";
3+
4+
// The file that is allowed to import from zod/v4
5+
const configFilePath = path.resolve(import.meta.dirname, "../src/common/config.ts");
6+
7+
// Ref: https://eslint.org/docs/latest/extend/custom-rules
8+
export default {
9+
meta: {
10+
type: "problem",
11+
docs: {
12+
description:
13+
"Only allow importing 'zod/v4' in config.ts, all other imports are allowed elsewhere. We should only adopt zod v4 for tools and resources once https://github.com/modelcontextprotocol/typescript-sdk/issues/555 is resolved.",
14+
recommended: true,
15+
},
16+
fixable: null,
17+
messages: {
18+
enforceZodV4:
19+
"Only 'zod/v4' imports are allowed in config.ts. Found import from '{{importPath}}'. Use 'zod/v4' instead.",
20+
},
21+
},
22+
create(context) {
23+
const currentFilePath = path.resolve(context.getFilename());
24+
25+
// Only allow zod v4 import in config.ts
26+
if (currentFilePath === configFilePath) {
27+
return {};
28+
}
29+
30+
return {
31+
ImportDeclaration(node) {
32+
const importPath = node.source.value;
33+
34+
// Check if this is a zod import
35+
if (typeof importPath !== "string") {
36+
return;
37+
}
38+
39+
const isZodV4Import = importPath === "zod/v4";
40+
41+
if (isZodV4Import) {
42+
context.report({
43+
node,
44+
messageId: "enforceZodV4",
45+
data: {
46+
importPath,
47+
},
48+
});
49+
}
50+
},
51+
};
52+
},
53+
};
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import path from "path";
2+
import { RuleTester } from "eslint";
3+
import { describe, it } from "vitest";
4+
import tsParser from "@typescript-eslint/parser";
5+
import rule from "./enforce-zod-v4.js";
6+
7+
const ROOT = process.cwd();
8+
const resolve = (p) => path.resolve(ROOT, p);
9+
10+
const ruleTester = new RuleTester({
11+
languageOptions: {
12+
parser: tsParser,
13+
parserOptions: { ecmaVersion: 2022, sourceType: "module" },
14+
},
15+
});
16+
17+
describe("enforce-zod-v4", () => {
18+
it("should allow zod/v4 imports in config.ts", () => {
19+
ruleTester.run("enforce-zod-v4", rule, {
20+
valid: [
21+
{
22+
filename: resolve("src/common/config.ts"),
23+
code: 'import { z } from "zod/v4";\n',
24+
},
25+
{
26+
filename: resolve("src/common/config.ts"),
27+
code: 'import * as z from "zod/v4";\n',
28+
},
29+
{
30+
filename: resolve("src/common/config.ts"),
31+
code: 'import type { ZodType } from "zod/v4";\n',
32+
},
33+
],
34+
invalid: [],
35+
});
36+
});
37+
38+
it("should allow regular zod imports in other files", () => {
39+
ruleTester.run("enforce-zod-v4", rule, {
40+
valid: [
41+
{
42+
filename: resolve("src/tools/tool.ts"),
43+
code: 'import { z } from "zod";\n',
44+
},
45+
{
46+
filename: resolve("src/resources/resource.ts"),
47+
code: 'import * as z from "zod";\n',
48+
},
49+
{
50+
filename: resolve("src/some/module.ts"),
51+
code: 'import type { ZodType } from "zod";\n',
52+
},
53+
],
54+
invalid: [],
55+
});
56+
});
57+
58+
it("should allow non-zod imports in any file", () => {
59+
ruleTester.run("enforce-zod-v4", rule, {
60+
valid: [
61+
{
62+
filename: resolve("src/tools/tool.ts"),
63+
code: 'import { something } from "some-package";\n',
64+
},
65+
{
66+
filename: resolve("src/common/config.ts"),
67+
code: 'import path from "path";\n',
68+
},
69+
{
70+
filename: resolve("src/resources/resource.ts"),
71+
code: 'import { Logger } from "./logger.js";\n',
72+
},
73+
],
74+
invalid: [],
75+
});
76+
});
77+
78+
it("should report error when zod/v4 is imported in files other than config.ts", () => {
79+
ruleTester.run("enforce-zod-v4", rule, {
80+
valid: [],
81+
invalid: [
82+
{
83+
filename: resolve("src/tools/tool.ts"),
84+
code: 'import { z } from "zod/v4";\n',
85+
errors: [
86+
{
87+
messageId: "enforceZodV4",
88+
data: { importPath: "zod/v4" },
89+
},
90+
],
91+
},
92+
{
93+
filename: resolve("src/resources/resource.ts"),
94+
code: 'import * as z from "zod/v4";\n',
95+
errors: [
96+
{
97+
messageId: "enforceZodV4",
98+
data: { importPath: "zod/v4" },
99+
},
100+
],
101+
},
102+
{
103+
filename: resolve("src/some/module.ts"),
104+
code: 'import type { ZodType } from "zod/v4";\n',
105+
errors: [
106+
{
107+
messageId: "enforceZodV4",
108+
data: { importPath: "zod/v4" },
109+
},
110+
],
111+
},
112+
{
113+
filename: resolve("tests/unit/toolBase.test.ts"),
114+
code: 'import { z } from "zod/v4";\n',
115+
errors: [
116+
{
117+
messageId: "enforceZodV4",
118+
data: { importPath: "zod/v4" },
119+
},
120+
],
121+
},
122+
],
123+
});
124+
});
125+
126+
it("should handle multiple imports in a single file", () => {
127+
ruleTester.run("enforce-zod-v4", rule, {
128+
valid: [
129+
{
130+
filename: resolve("src/common/config.ts"),
131+
code: `import { z } from "zod/v4";
132+
import path from "path";
133+
import type { UserConfig } from "./types.js";
134+
`,
135+
},
136+
],
137+
invalid: [
138+
{
139+
filename: resolve("src/tools/tool.ts"),
140+
code: `import { z } from "zod/v4";
141+
import path from "path";
142+
`,
143+
errors: [
144+
{
145+
messageId: "enforceZodV4",
146+
data: { importPath: "zod/v4" },
147+
},
148+
],
149+
},
150+
],
151+
});
152+
});
153+
});

eslint.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import tseslint from "typescript-eslint";
66
import eslintPluginPrettierRecommended from "eslint-plugin-prettier/recommended";
77
import vitestPlugin from "@vitest/eslint-plugin";
88
import noConfigImports from "./eslint-rules/no-config-imports.js";
9+
import enforceZodV4 from "./eslint-rules/enforce-zod-v4.js";
910

1011
const testFiles = ["tests/**/*.test.ts", "tests/**/*.ts"];
1112

@@ -72,9 +73,15 @@ export default defineConfig([
7273
"no-config-imports": noConfigImports,
7374
},
7475
},
76+
"enforce-zod-v4": {
77+
rules: {
78+
"enforce-zod-v4": enforceZodV4,
79+
},
80+
},
7581
},
7682
rules: {
7783
"no-config-imports/no-config-imports": "error",
84+
"enforce-zod-v4/enforce-zod-v4": "error",
7885
},
7986
},
8087
globalIgnores([

0 commit comments

Comments
 (0)