@uarlouski/testrail-mcp-server 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  <a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/TypeScript-5.9-blue.svg" alt="TypeScript"></a>
17
17
  <a href="https://github.com/uarlouski/testrail-mcp-server"><img src="https://img.shields.io/github/stars/uarlouski/testrail-mcp-server.svg?style=social&label=Star" alt="GitHub stars"></a>
18
18
  <a href="https://glama.ai/mcp/servers/uarlouski/testrail-mcp-server"><img src="https://glama.ai/mcp/servers/uarlouski/testrail-mcp-server/badges/score.svg" alt="Score Badge"></a>
19
+ <a href="https://m8ven.ai/mcp/uarlouski/testrail-mcp-server"><img src="https://m8ven.ai/badge/mcp/uarlouski/testrail-mcp-server" alt="M8ven Score"></a>
19
20
  </p>
20
21
 
21
22
  <p align="center">
@@ -138,7 +139,62 @@ Once configured, turbo-charge your QA workflow by asking your AI assistant:
138
139
  - *"Show me all test cases in section 5 of project 3."*
139
140
  - *"Create a comprehensive test case for 'Login Validation' with detailed steps."*
140
141
  - *"Start a new test run containing cases from section 5."*
141
- - *"Mark test case ID 1042 as passed with the comment 'Tested successfully on staging'."*
142
+ ---
143
+
144
+ ## 🖥️ Command Line Interface (CLI) & CI/CD Automation
145
+
146
+ In addition to interacting via AI assistants, you can invoke any TestRail tool directly from shell scripts, terminal environments, and automated CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins) using **`testrail-cli`** or **`npx`** — with zero LLM overhead.
147
+
148
+ - **Deterministic Execution**: Returns standard Unix exit codes (`0` on success, `1` on error).
149
+ - **Pipeline-Native Output**: Emits clean JSON to `stdout` for piping into tools like `jq`, while diagnostics and errors go to `stderr`.
150
+ - **Zero Duplication**: Reuses the exact same API client, retry logic, and validation schemas as the MCP server.
151
+
152
+ ### Invocation Methods
153
+
154
+ ```bash
155
+ # Method 1: Direct npx subcommand (Recommended)
156
+ npx @uarlouski/testrail-mcp-server cli <command> [flags]
157
+
158
+ # Method 2: Global or local binary
159
+ testrail-cli <command> [flags]
160
+
161
+ # Method 3: Via package runner
162
+ npx -p @uarlouski/testrail-mcp-server testrail-cli <command> [flags]
163
+ ```
164
+
165
+ ### Examples
166
+
167
+ #### Query Projects (`query_project`)
168
+ ```bash
169
+ # List all active projects
170
+ npx @uarlouski/testrail-mcp-server cli query_project --action many
171
+
172
+ # Query a single project by ID
173
+ npx @uarlouski/testrail-mcp-server cli query_project --action one --project_id 1
174
+ ```
175
+
176
+ #### Export Cases for Knowledge Base / RAG (`export_cases_for_rag`)
177
+ ```bash
178
+ # Export all cases for a project into Markdown & metadata sidecars
179
+ npx @uarlouski/testrail-mcp-server cli export_cases_for_rag \
180
+ --project_id 1 \
181
+ --output_dir ./rag_exports
182
+
183
+ # Export specific cases by ID (comma-separated list)
184
+ npx @uarlouski/testrail-mcp-server cli export_cases_for_rag \
185
+ --case_ids C101,C102,103 \
186
+ --output_dir ./rag_exports
187
+ ```
188
+
189
+ #### Command Discovery & Flag Documentation
190
+ ```bash
191
+ # List all available commands
192
+ npx @uarlouski/testrail-mcp-server cli --help
193
+
194
+ # Show parameter options for a specific tool
195
+ npx @uarlouski/testrail-mcp-server cli query_project --help
196
+ npx @uarlouski/testrail-mcp-server cli export_cases_for_rag --help
197
+ ```
142
198
 
143
199
  ---
144
200
 
@@ -164,7 +220,8 @@ To ensure smooth transitions, deprecated tools remain available by default (`TES
164
220
 
165
221
  | Deprecated Tool | Replacement | Status |
166
222
  |-----------------|-------------|--------|
167
- | `add_attachment_to_run` | `add_attachment` (`entity_type: "case" \| "run"`) | Deprecated in `2.3.0`, scheduled for removal in `3.0.0` |
223
+ | `add_attachment_to_run` | `add_attachment` (`entity_type: "case" \| "run" \| "result"`) | Deprecated in `2.3.0`, scheduled for removal in `3.0.0` |
224
+ | `get_sections` | `query_section` (`action: "many"`) | Deprecated in `2.8.0`, scheduled for removal in `3.0.0` |
168
225
 
169
226
  > **💡 Token Tip**: If you are not using legacy tools, set `TESTRAIL_ENABLE_DEPRECATED_TOOLS=false` in your environment to eliminate deprecated tool definitions from the LLM prompt and save tokens!
170
227
 
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Parses raw command-line arguments into a command name, tool flags, and global connection options.
3
+ */
4
+ export function parseArgs(argv) {
5
+ let command;
6
+ const flags = {};
7
+ const globalOptions = {};
8
+ for (let i = 0; i < argv.length; i++) {
9
+ const arg = argv[i];
10
+ if (arg === "-h" || arg === "--help") {
11
+ globalOptions.help = true;
12
+ flags.help = true;
13
+ }
14
+ else if (arg === "-v" || arg === "--version") {
15
+ globalOptions.version = true;
16
+ flags.version = true;
17
+ }
18
+ else if (!arg.startsWith("-")) {
19
+ if (!command) {
20
+ command = arg;
21
+ }
22
+ }
23
+ else {
24
+ let [rawKey, val] = arg.replace(/^--?/, "").split("=");
25
+ let isNegative = false;
26
+ if (rawKey.startsWith("no-")) {
27
+ rawKey = rawKey.slice(3);
28
+ isNegative = true;
29
+ }
30
+ const key = rawKey.replace(/-/g, "_");
31
+ if (val === undefined) {
32
+ if (isNegative) {
33
+ val = "false";
34
+ }
35
+ else if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
36
+ val = argv[++i];
37
+ }
38
+ else {
39
+ val = "true";
40
+ }
41
+ }
42
+ if (key === "url") {
43
+ globalOptions.url = val;
44
+ }
45
+ else if (key === "username") {
46
+ globalOptions.username = val;
47
+ }
48
+ else if (key === "api_key" || key === "apikey") {
49
+ globalOptions.apiKey = val;
50
+ }
51
+ else {
52
+ flags[key] = val === "true" ? true : val === "false" ? false : val;
53
+ }
54
+ }
55
+ }
56
+ return { command, flags, globalOptions };
57
+ }
58
+ /**
59
+ * Coerces primitive CLI strings to numbers, booleans, JSON objects, or arrays.
60
+ */
61
+ export function coerceValue(raw) {
62
+ if (raw === undefined || raw === null || typeof raw === "boolean") {
63
+ return raw;
64
+ }
65
+ if (typeof raw === "string") {
66
+ const trimmed = raw.trim();
67
+ if (trimmed === "true") {
68
+ return true;
69
+ }
70
+ if (trimmed === "false") {
71
+ return false;
72
+ }
73
+ // JSON objects or arrays
74
+ if ((trimmed.startsWith("{") && trimmed.endsWith("}")) || (trimmed.startsWith("[") && trimmed.endsWith("]"))) {
75
+ try {
76
+ return JSON.parse(trimmed);
77
+ }
78
+ catch {
79
+ /* fall through */
80
+ }
81
+ }
82
+ // Comma-separated array list
83
+ if (trimmed.includes(",")) {
84
+ return trimmed.split(",").map(item => coerceValue(item.trim()));
85
+ }
86
+ // Numeric string
87
+ if (!isNaN(Number(trimmed)) && trimmed !== "") {
88
+ return Number(trimmed);
89
+ }
90
+ return raw;
91
+ }
92
+ if (Array.isArray(raw)) {
93
+ return raw.map(item => coerceValue(item));
94
+ }
95
+ return raw;
96
+ }
97
+ /**
98
+ * Coerces flat CLI flags and auto-wraps into `{ payload: ... }` for discriminated union tools.
99
+ */
100
+ export function coerceFlagsForTool(rawFlags, parameters) {
101
+ const cleanFlags = {};
102
+ for (const [key, val] of Object.entries(rawFlags)) {
103
+ if (key === "help" || key === "version" || key === "_") {
104
+ continue;
105
+ }
106
+ cleanFlags[key] = coerceValue(val);
107
+ }
108
+ // Auto-wrap into payload if the tool expects a payload container (e.g. query_project)
109
+ if (parameters.payload && !cleanFlags.payload) {
110
+ return { payload: cleanFlags };
111
+ }
112
+ return cleanFlags;
113
+ }
114
+ /**
115
+ * Formats general CLI help documentation.
116
+ */
117
+ export function formatGeneralHelp(tools) {
118
+ const maxLen = Math.max(...tools.map(t => t.name.length), 20);
119
+ const cmdList = tools
120
+ .map(t => ` ${t.name.padEnd(maxLen + 2, " ")}${t.description.split("\n")[0]}`)
121
+ .join("\n");
122
+ return [
123
+ "TestRail CLI - Execute TestRail actions directly in terminal & CI/CD",
124
+ "",
125
+ "Usage: testrail-cli <command> [options]",
126
+ "",
127
+ "Commands:",
128
+ cmdList,
129
+ "",
130
+ "Global Options:",
131
+ " --url <url> TestRail instance URL (overrides TESTRAIL_INSTANCE_URL)",
132
+ " --username <email> TestRail username (overrides TESTRAIL_USERNAME)",
133
+ " --api-key <key> TestRail API key (overrides TESTRAIL_API_KEY)",
134
+ " -h, --help Show help documentation",
135
+ " -v, --version Show CLI version",
136
+ "",
137
+ "Run 'testrail-cli <command> --help' for command-specific flags."
138
+ ].join("\n");
139
+ }
140
+ /**
141
+ * Formats tool-specific help documentation.
142
+ */
143
+ export function formatToolHelp(tool) {
144
+ const shapes = [];
145
+ const params = tool.parameters;
146
+ if (params.payload) {
147
+ const payloadDef = params.payload.def;
148
+ if (Array.isArray(payloadDef?.options)) {
149
+ for (const opt of payloadDef.options) {
150
+ shapes.push(opt.shape);
151
+ }
152
+ }
153
+ }
154
+ else {
155
+ shapes.push(params);
156
+ }
157
+ const seen = new Set();
158
+ const options = [];
159
+ for (const shape of shapes) {
160
+ for (const [name, fieldSchema] of Object.entries(shape)) {
161
+ if (seen.has(name)) {
162
+ continue;
163
+ }
164
+ seen.add(name);
165
+ const def = fieldSchema?.def;
166
+ const isOptional = def?.type === "optional";
167
+ const innerDef = isOptional ? def?.innerType?.def : def;
168
+ const type = innerDef?.type || "string";
169
+ const desc = fieldSchema?.description || "";
170
+ const req = isOptional ? "(Optional)" : "(Required)";
171
+ options.push(` --${name} <${type}> ${desc} ${req}`.trimEnd());
172
+ }
173
+ }
174
+ return [
175
+ `Command: ${tool.name}`,
176
+ `Description: ${tool.description}`,
177
+ "",
178
+ "Options:",
179
+ ...options,
180
+ "",
181
+ "Global Options:",
182
+ " --url <url> TestRail instance URL",
183
+ " --username <email> TestRail username",
184
+ " --api-key <key> TestRail API key",
185
+ " -h, --help Show help for this command"
186
+ ].join("\n");
187
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+ import "dotenv/config";
3
+ import { z } from "zod";
4
+ import { TestRailClient } from "./client/testrail.js";
5
+ import { ALL_TOOLS } from "./tools/registry.js";
6
+ import { removeNullish } from "./utils/sanitizer.js";
7
+ import { parseArgs, coerceFlagsForTool, formatGeneralHelp, formatToolHelp } from "./adapters/cli_parser.js";
8
+ import { VERSION } from "./version.js";
9
+ const CredentialSchema = z.object({
10
+ url: z.url("Must be a valid TestRail URL (e.g. https://example.testrail.io)"),
11
+ username: z.email("Must be a valid email address"),
12
+ apiKey: z.string().min(1, "API key is required"),
13
+ });
14
+ export function resolveClient(options, env) {
15
+ const instanceUrl = options.url || env.TESTRAIL_INSTANCE_URL;
16
+ const username = options.username || env.TESTRAIL_USERNAME;
17
+ const apiKey = options.apiKey || env.TESTRAIL_API_KEY;
18
+ const credResult = CredentialSchema.safeParse({
19
+ url: instanceUrl,
20
+ username,
21
+ apiKey,
22
+ });
23
+ if (!credResult.success) {
24
+ console.error("Error: Missing or invalid TestRail credentials.");
25
+ for (const issue of credResult.error.issues) {
26
+ console.error(` - ${issue.path.join(".")}: ${issue.message}`);
27
+ }
28
+ console.error("\nPlease set TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, and TESTRAIL_API_KEY environment variables,");
29
+ console.error("or supply them via --url, --username, and --api-key options.");
30
+ return null;
31
+ }
32
+ return new TestRailClient(credResult.data.url, credResult.data.username, credResult.data.apiKey);
33
+ }
34
+ export function validateToolArgs(tool, flags) {
35
+ const coercedArgs = coerceFlagsForTool(flags, tool.parameters);
36
+ const ToolSchema = z.object(tool.parameters);
37
+ const validationResult = ToolSchema.safeParse(coercedArgs);
38
+ if (!validationResult.success) {
39
+ console.error(`Validation error for command "${tool.name}":`);
40
+ for (const issue of validationResult.error.issues) {
41
+ console.error(` - ${issue.path.join(".")}: ${issue.message}`);
42
+ }
43
+ console.error(`\nRun 'testrail-cli ${tool.name} --help' for flag specifications.`);
44
+ return null;
45
+ }
46
+ return validationResult.data;
47
+ }
48
+ export async function executeTool(tool, client, args) {
49
+ try {
50
+ const output = await tool.handler(args, client);
51
+ const sanitized = removeNullish(output);
52
+ console.log(JSON.stringify(sanitized, null, 2));
53
+ return 0;
54
+ }
55
+ catch (error) {
56
+ console.error(`Error executing "${tool.name}": ${error.message || error}`);
57
+ return 1;
58
+ }
59
+ }
60
+ export async function runCli(argv, env = process.env, clientOverride) {
61
+ const { command, flags, globalOptions } = parseArgs(argv);
62
+ if (globalOptions.version) {
63
+ console.log(`testrail-cli v${VERSION}`);
64
+ return 0;
65
+ }
66
+ if (!command) {
67
+ if (globalOptions.help) {
68
+ console.log(formatGeneralHelp(ALL_TOOLS));
69
+ return 0;
70
+ }
71
+ console.error("Error: No command specified.\n");
72
+ console.error(formatGeneralHelp(ALL_TOOLS));
73
+ return 1;
74
+ }
75
+ const tool = ALL_TOOLS.find(t => t.name === command);
76
+ if (!tool) {
77
+ console.error(`Error: Unknown command "${command}".\n`);
78
+ console.error("Run 'testrail-cli --help' to view available commands.");
79
+ return 1;
80
+ }
81
+ if (globalOptions.help) {
82
+ console.log(formatToolHelp(tool));
83
+ return 0;
84
+ }
85
+ const client = clientOverride || resolveClient(globalOptions, env);
86
+ if (!client) {
87
+ return 1;
88
+ }
89
+ const toolArgs = validateToolArgs(tool, flags);
90
+ if (!toolArgs) {
91
+ return 1;
92
+ }
93
+ return executeTool(tool, client, toolArgs);
94
+ }
95
+ // Direct execution from CLI
96
+ const isDirectExecution = process.argv[1] && (process.argv[1].endsWith("/cli.js") ||
97
+ process.argv[1].endsWith("/cli.ts") ||
98
+ process.argv[1].endsWith("/testrail-cli"));
99
+ if (isDirectExecution) {
100
+ runCli(process.argv.slice(2)).then(code => {
101
+ if (code !== 0) {
102
+ process.exit(code);
103
+ }
104
+ }).catch(err => {
105
+ console.error("Unexpected error:", err);
106
+ process.exit(1);
107
+ });
108
+ }
package/dist/index.js CHANGED
@@ -5,7 +5,17 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5
5
  import { TestRailClient } from "./client/testrail.js";
6
6
  import { getToolsToRegister } from "./tools/registry.js";
7
7
  import { removeNullish } from "./utils/sanitizer.js";
8
+ import { VERSION } from "./version.js";
8
9
  import z from "zod";
10
+ // Allow delegating to CLI directly when invoked via 'cli' subcommand (e.g. npx @uarlouski/testrail-mcp-server cli ...)
11
+ const isDirectExecution = process.argv[1] && (process.argv[1].endsWith("/index.js") ||
12
+ process.argv[1].endsWith("/index.ts") ||
13
+ process.argv[1].endsWith("/testrail-mcp-server"));
14
+ if (isDirectExecution && process.argv[2] === "cli") {
15
+ const { runCli } = await import("./cli.js");
16
+ const code = await runCli(process.argv.slice(3));
17
+ process.exit(code);
18
+ }
9
19
  const EnvSchema = z.object({
10
20
  TESTRAIL_INSTANCE_URL: z.url('Must be a valid TestRail URL'),
11
21
  TESTRAIL_USERNAME: z.email('Must be a valid email address'),
@@ -27,7 +37,7 @@ if (!parseResult.success) {
27
37
  const { TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY, TESTRAIL_ENABLE_SHARED_STEPS, TESTRAIL_ENABLE_CASE_HISTORY, TESTRAIL_ENABLE_RAG_TOOLS, TESTRAIL_ALLOW_WRITE_OPERATIONS, TESTRAIL_ALLOW_READ_OPERATIONS, TESTRAIL_ALLOW_DELETE_OPERATIONS, TESTRAIL_ENABLE_DEPRECATED_TOOLS, TESTRAIL_DISABLED_TOOLS } = parseResult.data;
28
38
  const server = new McpServer({
29
39
  name: "TestRail MCP Server",
30
- version: "2.8.0",
40
+ version: VERSION,
31
41
  });
32
42
  const client = new TestRailClient(TESTRAIL_INSTANCE_URL, TESTRAIL_USERNAME, TESTRAIL_API_KEY);
33
43
  let tools;
@@ -2,12 +2,12 @@ import { z } from "zod";
2
2
  import { AttachmentSchema } from "./types.js";
3
3
  import { normalizeEntityId, prepareUploadFile } from "../../utils/attachment_helper.js";
4
4
  const parameters = {
5
- entity_type: z.enum(["case", "run"]).describe("The type of entity to attach the file to ('case' or 'run')"),
6
- entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run ID)"),
5
+ entity_type: z.enum(["case", "run", "result"]).describe("The type of entity to attach the file to ('case', 'run', or 'result')"),
6
+ entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run / result ID)"),
7
7
  file_path: z.string().describe("The path to the file or directory to attach. Directories will be automatically zipped."),
8
8
  };
9
9
  const description = `
10
- Add an attachment to a test case or test run in TestRail.
10
+ Add an attachment to a test case, test run, or test result in TestRail.
11
11
  If the file_path points to a directory, it will be automatically zipped before uploading.
12
12
  Maximum upload size is 256MB.
13
13
  `;
@@ -10,8 +10,8 @@ export const AttachmentItemSchema = z.object({
10
10
  is_image: z.union([z.boolean(), z.number()]).optional(),
11
11
  });
12
12
  export const AddAttachmentSchema = z.object({
13
- entity_type: z.enum(["case", "run"]).describe("The type of entity to attach the file to ('case' or 'run')"),
14
- entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run ID)"),
13
+ entity_type: z.enum(["case", "run", "result"]).describe("The type of entity to attach the file to ('case', 'run', or 'result')"),
14
+ entity_id: z.union([z.number(), z.string()]).describe("The ID of the entity to attach the file to (e.g. 123 or 'C123' for cases, or numeric run / result ID)"),
15
15
  file_path: z.string().describe("The path to the file or directory to attach. Directories will be automatically zipped."),
16
16
  });
17
17
  export const GetOneAttachmentSchema = z.object({
@@ -13,7 +13,7 @@ const parameters = {
13
13
  filter: z.record(z.string(), z.string()).optional().describe("Optional API-side filters (e.g. priority_id, type_id, milestone_id, refs, created_after, etc.)."),
14
14
  where: z.record(z.string(), z.any()).optional().describe("Optional client-side filter for any field including custom fields (filters after fetching cases). Supports exact value matching. Example: {\"custom_automation_status\": 1}"),
15
15
  output_dir: z.string().optional().describe("Directory to save exported files. Defaults to an auto-generated directory in the current working directory."),
16
- ignored_fields: z.array(z.string()).optional().describe("Optional list of custom field names or system names to ignore/exclude from export (e.g. ['custom_review_status', 'review_status']). Supports both full system_name and stripped field names. Core metadata attributes (case_id, title, section, priority, references, labels) cannot be ignored."),
16
+ ignored_fields: z.array(z.string()).optional().describe("Optional list of custom field names or system names to ignore/exclude from export (e.g. ['custom_review_status', 'review_status']). Supports both full system_name and stripped field names. Core metadata attributes (case_id, updated_on, title, section, priority, references, labels) cannot be ignored."),
17
17
  };
18
18
  function formatStepsSeparated(steps) {
19
19
  return steps.map((step, idx) => {
@@ -125,6 +125,7 @@ function buildMarkdownBody(testCase, sectionName, markdownFields) {
125
125
  function buildMetadataAttributes(testCase, sectionName, priority, metadataFields) {
126
126
  const metadataAttributes = {
127
127
  case_id: testCase.id,
128
+ updated_on: testCase.updated_on,
128
129
  title: testCase.title,
129
130
  section: sectionName,
130
131
  priority: priority,
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  export const ResultSchema = z.object({
3
3
  id: z.number(),
4
4
  test_id: z.number(),
5
- status_id: z.number(),
5
+ status_id: z.number().nullable(),
6
6
  comment: z.string().nullable(),
7
7
  defects: z.string().nullable(),
8
8
  });
@@ -0,0 +1 @@
1
+ export const VERSION = "2.10.0";
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@uarlouski/testrail-mcp-server",
3
- "version": "2.8.0",
3
+ "version": "2.10.0",
4
4
  "mcpName": "io.github.uarlouski/testrail-mcp-server",
5
5
  "description": "Model Context Protocol (MCP) server for TestRail integration, enabling LLMs to fetch test cases and metadata.",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
- "testrail-mcp-server": "./dist/index.js"
8
+ "testrail-mcp-server": "./dist/index.js",
9
+ "testrail-cli": "./dist/cli.js"
9
10
  },
10
11
  "files": [
11
12
  "dist"
@@ -41,10 +42,10 @@
41
42
  "homepage": "https://github.com/uarlouski/testrail-mcp-server#readme",
42
43
  "license": "Apache-2.0",
43
44
  "dependencies": {
44
- "@modelcontextprotocol/sdk": "^1.29.0",
45
+ "@modelcontextprotocol/sdk": "^1.30.0",
45
46
  "archiver": "^7.0.1",
46
47
  "dotenv": "^17.4.2",
47
- "zod": "^4.4.3"
48
+ "zod": "^4.6.5"
48
49
  },
49
50
  "devDependencies": {
50
51
  "@jest/globals": "^29.7.0",
@@ -55,9 +56,7 @@
55
56
  "rimraf": "^6.1.3",
56
57
  "ts-jest": "^29.4.6",
57
58
  "ts-node": "^10.9.2",
58
- "typescript": "^5.9.3",
59
- "vitepress": "^1.6.4",
60
- "vue": "^3.5.41"
59
+ "typescript": "^5.9.3"
61
60
  },
62
61
  "devEngines": {
63
62
  "packageManager": {
@@ -67,13 +66,16 @@
67
66
  }
68
67
  },
69
68
  "scripts": {
70
- "build": "rimraf dist && tsc",
69
+ "version:sync": "node scripts/sync-version.mjs",
70
+ "prebuild": "pnpm version:sync",
71
+ "build": "rimraf dist && tsc && node -e \"if (process.platform !== 'win32') { ['dist/index.js', 'dist/cli.js'].forEach(f => require('fs').chmodSync(f, 0o755)); }\"",
71
72
  "start": "node dist/index.js",
73
+ "cli": "ts-node src/cli.ts",
72
74
  "dev": "ts-node src/index.ts",
73
75
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
74
76
  "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
75
- "docs:dev": "vitepress dev docs",
76
- "docs:build": "vitepress build docs",
77
- "docs:preview": "vitepress preview docs"
77
+ "docs:dev": "pnpm --prefix docs dev",
78
+ "docs:build": "pnpm --prefix docs build",
79
+ "docs:preview": "pnpm --prefix docs preview"
78
80
  }
79
81
  }