mcpill 1.2.4 → 1.5.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.
@@ -1,236 +0,0 @@
1
- import { readdirSync, readFileSync } from 'fs';
2
- import { join } from 'path';
3
- import type { ServerDoc, ToolDoc, PromptDoc, ResourceDoc } from './types.js';
4
-
5
- function extractSections(content: string): Map<string, string> {
6
- const sections = new Map<string, string>();
7
- const pieces = ('\n' + content).split('\n## ');
8
- for (const piece of pieces) {
9
- if (!piece.trim()) continue;
10
- const nlIdx = piece.indexOf('\n');
11
- if (nlIdx === -1) continue;
12
- const name = piece.slice(0, nlIdx).trim();
13
- const body = piece.slice(nlIdx + 1);
14
- sections.set(name, body);
15
- }
16
- return sections;
17
- }
18
-
19
- function extractFencedBlock(body: string, lang: string): string | null {
20
- // Match ``` + lang followed immediately by a newline to avoid matching
21
- // a longer lang identifier (e.g. ```json when looking for ```js).
22
- const startMarker = '```' + lang + '\n';
23
- const start = body.indexOf(startMarker);
24
- if (start === -1) return null;
25
- const contentStart = start + startMarker.length;
26
- const end = body.indexOf('\n```', contentStart);
27
- if (end === -1) return body.slice(contentStart).trim();
28
- return body.slice(contentStart, end).trim();
29
- }
30
-
31
- export function parseKvBlock(body: string): Record<string, string> {
32
- const result: Record<string, string> = {};
33
- for (const line of body.split('\n')) {
34
- const colonIdx = line.indexOf(':');
35
- if (colonIdx === -1) continue;
36
- const key = line.slice(0, colonIdx).trim();
37
- const value = line.slice(colonIdx + 1).trim();
38
- if (key) result[key] = value;
39
- }
40
- return result;
41
- }
42
-
43
- function parseFrontmatter(block: string): Record<string, string> {
44
- return parseKvBlock(block);
45
- }
46
-
47
- function stripHandlerMarkers(jsContent: string): string {
48
- return jsContent
49
- .split('\n')
50
- .filter((l) => !l.trim().startsWith('// @handler:') && !l.trim().startsWith('// @end-handler:'))
51
- .join('\n')
52
- .trim();
53
- }
54
-
55
- function parseToolsSection(body: string): ToolDoc[] {
56
- const tools: ToolDoc[] = [];
57
- const pieces = ('\n' + body).split('\n### ');
58
- for (const piece of pieces.slice(1)) {
59
- const nlIdx = piece.indexOf('\n');
60
- const name = (nlIdx === -1 ? piece : piece.slice(0, nlIdx)).trim();
61
- const rest = nlIdx === -1 ? '' : piece.slice(nlIdx + 1);
62
-
63
- const descMatch = rest.match(/\*\*Description:\*\*\s*(.+)/);
64
- const description = descMatch?.[1]?.trim() ?? '';
65
-
66
- const schemaStr = extractFencedBlock(rest, 'json');
67
- const schema = schemaStr
68
- ? (JSON.parse(schemaStr) as ToolDoc['schema'])
69
- : {};
70
-
71
- const jsBlock = extractFencedBlock(rest, 'js');
72
- // Strip @handler/@end-handler markers so ToolDoc.handler holds only the function source.
73
- const handler = jsBlock !== null ? stripHandlerMarkers(jsBlock) : undefined;
74
-
75
- tools.push({ name, description, schema, handler });
76
- }
77
- return tools;
78
- }
79
-
80
- function parseResourcesSection(body: string): ResourceDoc[] {
81
- const resources: ResourceDoc[] = [];
82
- const blocks = body.split('\n---\n');
83
- for (let i = 0; i < blocks.length; i += 2) {
84
- const frontmatterBlock = (blocks[i] ?? '').trim();
85
- if (!frontmatterBlock) continue;
86
- const bodyBlock = blocks[i + 1] ?? '';
87
- const frontmatter = parseFrontmatter(frontmatterBlock);
88
- if (!frontmatter['uri']) continue;
89
- resources.push({
90
- uri: frontmatter['uri'],
91
- name: frontmatter['name'],
92
- mimeType: frontmatter['mimeType'],
93
- content: bodyBlock.trim(),
94
- });
95
- }
96
- return resources;
97
- }
98
-
99
- function parseToolFile(content: string): ToolDoc {
100
- const lines = content.split('\n');
101
-
102
- const h1Line = lines.find((l) => l.startsWith('# '));
103
- const name = h1Line ? h1Line.slice(2).trim() : '';
104
-
105
- let h1Seen = false;
106
- let description = '';
107
- for (const line of lines) {
108
- if (line.startsWith('# ')) { h1Seen = true; continue; }
109
- if (!h1Seen) continue;
110
- if (line.startsWith('#')) break;
111
- const trimmed = line.trim();
112
- if (trimmed) { description = trimmed; break; }
113
- }
114
-
115
- const sections = extractSections(content);
116
-
117
- const parametersBody = sections.get('Parameters') ?? '';
118
- const schema: ToolDoc['schema'] = {};
119
- for (const line of parametersBody.split('\n')) {
120
- if (!line.startsWith('- ')) continue;
121
- const m = line.match(/^- (\w+) \(([^)]+)\): (.+)/);
122
- if (!m) continue;
123
- const pName = m[1]!;
124
- const typeStr = m[2]!.split(',')[0]!.trim() as 'string' | 'number' | 'boolean';
125
- const desc = m[3]!.trim();
126
- schema[pName] = { type: typeStr, description: desc };
127
- }
128
-
129
- const handlerBody = sections.get('Handler') ?? '';
130
- const handler = extractFencedBlock(handlerBody, 'js') ?? undefined;
131
-
132
- return { name, description, schema, handler };
133
- }
134
-
135
- function parsePromptFile(content: string): PromptDoc {
136
- const lines = content.split('\n');
137
-
138
- const h1Line = lines.find((l) => l.startsWith('# '));
139
- const name = h1Line ? h1Line.slice(2).trim() : '';
140
-
141
- let h1Seen = false;
142
- let description = '';
143
- for (const line of lines) {
144
- if (line.startsWith('# ')) { h1Seen = true; continue; }
145
- if (!h1Seen) continue;
146
- if (line.startsWith('#')) break;
147
- const trimmed = line.trim();
148
- if (trimmed) { description = trimmed; break; }
149
- }
150
-
151
- const sections = extractSections(content);
152
-
153
- const argsBody = sections.get('Args') ?? '';
154
- const args: PromptDoc['args'] = {};
155
- for (const line of argsBody.split('\n')) {
156
- if (!line.startsWith('- ')) continue;
157
- const m = line.match(/^- (\w+) \(([^)]+)\): .+/);
158
- if (!m) continue;
159
- args[m[1]!] = { type: m[2]!.trim() };
160
- }
161
-
162
- const messageBody = sections.get('Message') ?? '';
163
- const messages: PromptDoc['messages'] = [];
164
- for (const line of messageBody.split('\n')) {
165
- if (!line.startsWith('> ')) continue;
166
- const colonIdx = line.indexOf(': ', 2);
167
- if (colonIdx === -1) continue;
168
- const role = line.slice(2, colonIdx).trim();
169
- const msgContent = line.slice(colonIdx + 2).trim();
170
- messages.push({ role, content: msgContent });
171
- }
172
-
173
- return { name, description, args, messages };
174
- }
175
-
176
- export function parseServerDoc(content: string): ServerDoc {
177
- const sections = extractSections(content);
178
-
179
- const configBody = sections.get('Config') ?? '';
180
- const configStr = extractFencedBlock(configBody, 'json') ?? '{}';
181
- const config = JSON.parse(configStr) as ServerDoc['config'];
182
-
183
- const toolsBody = sections.get('Tools') ?? '';
184
- const tools = parseToolsSection(toolsBody);
185
-
186
- const promptsBody = sections.get('Prompts') ?? '';
187
- const promptsStr = extractFencedBlock(promptsBody, 'json') ?? '[]';
188
- const prompts = JSON.parse(promptsStr) as PromptDoc[];
189
-
190
- const resourcesBody = sections.get('Resources') ?? '';
191
- const resources = parseResourcesSection(resourcesBody);
192
-
193
- return { config, tools, prompts, resources };
194
- }
195
-
196
- export function parseServerDir(dir: string): ServerDoc {
197
- const serverMdContent = readFileSync(join(dir, 'server.md'), 'utf-8');
198
- const sections = extractSections(serverMdContent);
199
-
200
- const configBody = sections.get('Config') ?? '';
201
- const configKv = parseKvBlock(configBody);
202
- const config: ServerDoc['config'] = {
203
- name: configKv['name'] ?? '',
204
- transport: (configKv['transport'] ?? 'stdio') as 'stdio' | 'http',
205
- ...(configKv['port'] ? { port: parseInt(configKv['port'], 10) } : {}),
206
- };
207
-
208
- const resourcesBody = sections.get('Resources') ?? '';
209
- const resources = parseResourcesSection(resourcesBody);
210
-
211
- const toolsDir = join(dir, 'server', 'tools');
212
- const tools: ToolDoc[] = [];
213
- let toolFiles: string[] = [];
214
- try {
215
- toolFiles = readdirSync(toolsDir).filter((f) => f.endsWith('.md'));
216
- } catch {
217
- // tools/ dir doesn't exist — no tools
218
- }
219
- for (const file of toolFiles) {
220
- tools.push(parseToolFile(readFileSync(join(toolsDir, file), 'utf-8')));
221
- }
222
-
223
- const promptsDir = join(dir, 'server', 'prompts');
224
- const prompts: PromptDoc[] = [];
225
- let promptFiles: string[] = [];
226
- try {
227
- promptFiles = readdirSync(promptsDir).filter((f) => f.endsWith('.md'));
228
- } catch {
229
- // prompts/ dir doesn't exist — no prompts
230
- }
231
- for (const file of promptFiles) {
232
- prompts.push(parsePromptFile(readFileSync(join(promptsDir, file), 'utf-8')));
233
- }
234
-
235
- return { config, tools, prompts, resources };
236
- }
@@ -1,100 +0,0 @@
1
- import { writeFileSync, mkdirSync } from 'fs';
2
- import { join } from 'path';
3
- import type { ServerDoc, ResourceDoc } from './types.js';
4
-
5
- function serializeResources(resources: ResourceDoc[]): string {
6
- return resources
7
- .map((r) => {
8
- let fm = `uri: ${r.uri}`;
9
- if (r.name) fm += `\nname: ${r.name}`;
10
- if (r.mimeType) fm += `\nmimeType: ${r.mimeType}`;
11
- return `${fm}\n---\n${r.content}`;
12
- })
13
- .join('\n---\n');
14
- }
15
-
16
- export function serializeServerDoc(doc: ServerDoc): string {
17
- const configSection =
18
- `## Config\n\`\`\`json\n${JSON.stringify(doc.config, null, 2)}\n\`\`\``;
19
-
20
- const toolParts = doc.tools.map((tool) => {
21
- let part = `### ${tool.name}\n**Description:** ${tool.description}\n\n`;
22
- part += `\`\`\`json\n${JSON.stringify(tool.schema, null, 2)}\n\`\`\``;
23
- if (tool.handler !== undefined) {
24
- part +=
25
- `\n\n\`\`\`js\n// @handler:${tool.name}\n${tool.handler}\n// @end-handler:${tool.name}\n\`\`\``;
26
- }
27
- return part;
28
- });
29
- const toolsSection =
30
- doc.tools.length > 0
31
- ? `## Tools\n\n${toolParts.join('\n\n')}`
32
- : `## Tools`;
33
-
34
- const promptsSection =
35
- `## Prompts\n\`\`\`json\n${JSON.stringify(doc.prompts, null, 2)}\n\`\`\``;
36
-
37
- const resourcesSection =
38
- doc.resources.length > 0
39
- ? `## Resources\n${serializeResources(doc.resources)}`
40
- : `## Resources`;
41
-
42
- return [configSection, toolsSection, promptsSection, resourcesSection].join('\n\n') + '\n';
43
- }
44
-
45
- export function serializeServerDir(doc: ServerDoc, dir: string): void {
46
- mkdirSync(join(dir, 'server', 'tools'), { recursive: true });
47
- mkdirSync(join(dir, 'server', 'prompts'), { recursive: true });
48
-
49
- let serverMd = '## Config\n';
50
- serverMd += `name: ${doc.config.name}\n`;
51
- serverMd += `transport: ${doc.config.transport}\n`;
52
- if (doc.config.port !== undefined) serverMd += `port: ${doc.config.port}\n`;
53
-
54
- if (doc.resources.length > 0) {
55
- serverMd += `\n## Resources\n${serializeResources(doc.resources)}\n`;
56
- } else {
57
- serverMd += '\n## Resources\n';
58
- }
59
-
60
- writeFileSync(join(dir, 'server.md'), serverMd);
61
- console.log('✓ server.md updated');
62
-
63
- for (const tool of doc.tools) {
64
- let md = `# ${tool.name}\n\n${tool.description}\n\n## Parameters\n\n`;
65
- for (const [pName, pDef] of Object.entries(tool.schema)) {
66
- md += `- ${pName} (${pDef.type}): ${pDef.description ?? pName}\n`;
67
- }
68
- if (tool.handler !== undefined) {
69
- const cleanHandler = tool.handler
70
- .split('\n')
71
- .filter((l) => !l.trim().startsWith('// @handler:') && !l.trim().startsWith('// @end-handler:'))
72
- .join('\n')
73
- .trim();
74
- md += `\n## Handler\n\n\`\`\`js\n${cleanHandler}\n\`\`\`\n`;
75
- }
76
- writeFileSync(join(dir, 'server', 'tools', `${tool.name}.md`), md);
77
- }
78
-
79
- for (const prompt of doc.prompts) {
80
- let md = `# ${prompt.name}\n\n`;
81
- if (prompt.description) md += `${prompt.description}\n\n`;
82
- if (Object.keys(prompt.args).length > 0) {
83
- md += '## Args\n\n';
84
- for (const [argName, argDef] of Object.entries(prompt.args)) {
85
- md += `- ${argName} (${argDef.type}): ${argName}\n`;
86
- }
87
- md += '\n';
88
- }
89
- if (prompt.messages.length > 0) {
90
- md += '## Message\n\n';
91
- for (const msg of prompt.messages) {
92
- md += `> ${msg.role}: ${msg.content}\n`;
93
- }
94
- }
95
- writeFileSync(join(dir, 'server', 'prompts', `${prompt.name}.md`), md);
96
- }
97
-
98
- console.log(`✓ tools/ updated (${doc.tools.length} files)`);
99
- console.log(`✓ prompts/ updated (${doc.prompts.length} files)`);
100
- }
@@ -1,27 +0,0 @@
1
- export type ToolDoc = {
2
- name: string;
3
- description: string;
4
- schema: Record<string, { type: 'string' | 'number' | 'boolean'; description?: string }>;
5
- handler?: string;
6
- };
7
-
8
- export type PromptDoc = {
9
- name: string;
10
- description?: string;
11
- args: Record<string, { type: string }>;
12
- messages: Array<{ role: string; content: string }>;
13
- };
14
-
15
- export type ResourceDoc = {
16
- uri: string;
17
- name?: string;
18
- mimeType?: string;
19
- content: string;
20
- };
21
-
22
- export type ServerDoc = {
23
- config: { name: string; transport: 'stdio' | 'http'; port?: number };
24
- tools: ToolDoc[];
25
- prompts: PromptDoc[];
26
- resources: ResourceDoc[];
27
- };
@@ -1,25 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- export type CliConfig = {
5
- name: string;
6
- transport: "stdio" | "http";
7
- port: number;
8
- };
9
-
10
- const defaults: CliConfig = {
11
- name: "mcpill-server",
12
- transport: "stdio",
13
- port: 3333,
14
- };
15
-
16
- export async function loadConfig(mcpillDir: string): Promise<CliConfig> {
17
- const configPath = path.join(mcpillDir, "mcpill.config.json");
18
- if (!fs.existsSync(configPath)) {
19
- return { ...defaults };
20
- }
21
- const raw = JSON.parse(
22
- fs.readFileSync(configPath, "utf-8")
23
- ) as Partial<CliConfig>;
24
- return { ...defaults, ...raw };
25
- }
@@ -1,60 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { z } from "mcpill-runtime";
4
-
5
- type ZodRawShape = Record<string, z.ZodTypeAny>;
6
-
7
- export type RawPromptDef = {
8
- name: string;
9
- description?: string;
10
- argsShape: ZodRawShape;
11
- messages: Array<{ role: string; content: string }>;
12
- };
13
-
14
- const typeMap: Record<string, z.ZodTypeAny> = {
15
- string: z.string(),
16
- number: z.number(),
17
- boolean: z.boolean(),
18
- };
19
-
20
- export async function loadPrompts(mcpillDir: string): Promise<RawPromptDef[]> {
21
- const promptsPath = path.join(mcpillDir, "prompts.json");
22
- let raw: unknown;
23
- try {
24
- const content = fs.readFileSync(promptsPath, "utf-8");
25
- raw = JSON.parse(content);
26
- } catch (err) {
27
- const msg = err instanceof Error ? err.message : String(err);
28
- throw new Error(`prompts.json is not valid JSON: ${msg}`);
29
- }
30
-
31
- if (!Array.isArray(raw)) {
32
- throw new Error("prompts.json is not valid JSON: must be an array");
33
- }
34
-
35
- const prompts: RawPromptDef[] = [];
36
-
37
- for (const entry of raw as Array<Record<string, unknown>>) {
38
- const argsShape: ZodRawShape = {};
39
- const args = (entry["args"] ?? {}) as Record<string, { type: string }>;
40
-
41
- for (const [argName, argDef] of Object.entries(args)) {
42
- const typeName = argDef.type;
43
- if (!(typeName in typeMap)) {
44
- throw new Error(
45
- `Prompt '${entry["name"]}': arg '${argName}' uses unsupported type '${typeName}'. Supported: string, number, boolean`
46
- );
47
- }
48
- argsShape[argName] = typeMap[typeName]!;
49
- }
50
-
51
- prompts.push({
52
- name: entry["name"] as string,
53
- description: entry["description"] as string | undefined,
54
- argsShape,
55
- messages: entry["messages"] as Array<{ role: string; content: string }>,
56
- });
57
- }
58
-
59
- return prompts;
60
- }
@@ -1,54 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- export type RawResourceDef = {
5
- uri: string;
6
- name?: string;
7
- mimeType?: string;
8
- content: string;
9
- };
10
-
11
- function parseFrontmatter(block: string): Record<string, string> {
12
- const result: Record<string, string> = {};
13
- for (const line of block.split("\n")) {
14
- const colonIdx = line.indexOf(":");
15
- if (colonIdx === -1) continue;
16
- const key = line.slice(0, colonIdx).trim();
17
- const value = line.slice(colonIdx + 1).trim();
18
- if (key) result[key] = value;
19
- }
20
- return result;
21
- }
22
-
23
- export async function loadResources(mcpillDir: string): Promise<RawResourceDef[]> {
24
- const resourcesPath = path.join(mcpillDir, "resources.md");
25
- const fileContent = fs.readFileSync(resourcesPath, "utf-8");
26
- const blocks = fileContent.split("\n---\n");
27
-
28
- const resources: RawResourceDef[] = [];
29
-
30
- // blocks alternate: [frontmatter, body, frontmatter, body, ...]
31
- // even indices (0-indexed) = frontmatter = block N (1-indexed odd)
32
- for (let i = 0; i < blocks.length; i += 2) {
33
- const frontmatterBlock = blocks[i] ?? "";
34
- if (frontmatterBlock.trim() === "") continue;
35
-
36
- const bodyBlock = blocks[i + 1] ?? "";
37
- const frontmatter = parseFrontmatter(frontmatterBlock);
38
-
39
- if (!frontmatter["uri"]) {
40
- throw new Error(
41
- `resources.md block ${i + 1} is missing required frontmatter field: uri`
42
- );
43
- }
44
-
45
- resources.push({
46
- uri: frontmatter["uri"],
47
- name: frontmatter["name"],
48
- mimeType: frontmatter["mimeType"],
49
- content: bodyBlock.trim(),
50
- });
51
- }
52
-
53
- return resources;
54
- }
@@ -1,68 +0,0 @@
1
- import { pathToFileURL } from "url";
2
- import path from "path";
3
-
4
- export type RawToolDef = {
5
- name: string;
6
- description: string;
7
- schema: Record<string, unknown>;
8
- // eslint-disable-next-line @typescript-eslint/ban-types
9
- handler: Function;
10
- };
11
-
12
- export async function loadTools(mcpillDir: string): Promise<RawToolDef[]> {
13
- const toolsPath = path.join(mcpillDir, "tools.js");
14
- let mod: unknown;
15
- try {
16
- mod = await import(pathToFileURL(toolsPath).href);
17
- } catch (err) {
18
- const msg = err instanceof Error ? err.message : String(err);
19
- if (msg.includes("mcpill-runtime")) {
20
- throw new Error(
21
- "tools.js failed to load: mcpill-runtime not found.\nRun: npm install mcpill-runtime"
22
- );
23
- }
24
- throw new Error(`tools.js failed to load: ${msg}`);
25
- }
26
-
27
- const tools = (mod as { default?: unknown }).default;
28
- if (!Array.isArray(tools)) {
29
- throw new Error("tools.js failed to load: default export must be an array");
30
- }
31
-
32
- for (let i = 0; i < tools.length; i++) {
33
- const tool = tools[i] as Record<string, unknown>;
34
-
35
- for (const field of ["name", "description", "schema", "handler"] as const) {
36
- if (tool[field] === undefined || tool[field] === null) {
37
- throw new Error(`Tool at index ${i} is missing required field: ${field}`);
38
- }
39
- }
40
-
41
- if (typeof tool["name"] !== "string") {
42
- throw new Error(`Tool at index ${i} is missing required field: name`);
43
- }
44
- if (typeof tool["description"] !== "string") {
45
- throw new Error(`Tool at index ${i} is missing required field: description`);
46
- }
47
- if (
48
- typeof tool["schema"] !== "object" ||
49
- Array.isArray(tool["schema"]) ||
50
- tool["schema"] === null
51
- ) {
52
- throw new Error(`Tool at index ${i} is missing required field: schema`);
53
- }
54
- const name = tool["name"] as string;
55
- for (const [key, value] of Object.entries(tool["schema"] as Record<string, unknown>)) {
56
- if (value == null || typeof value !== "object" || !("_def" in (value as object))) {
57
- throw new Error(
58
- `Tool '${name}': schema field '${key}' must be a Zod type (e.g. z.string()). Got a plain value instead.`
59
- );
60
- }
61
- }
62
- if (typeof tool["handler"] !== "function") {
63
- throw new Error(`Tool '${tool["name"]}': handler must be a function`);
64
- }
65
- }
66
-
67
- return tools as RawToolDef[];
68
- }
@@ -1,11 +0,0 @@
1
- # greeting
2
-
3
- Generate a greeting
4
-
5
- ## Args
6
-
7
- - name (string): The name to greet
8
-
9
- ## Message
10
-
11
- > user: Say hello to {{name}}
@@ -1,9 +0,0 @@
1
- ## Config
2
- name: my-server
3
- transport: stdio
4
-
5
- ## Resources
6
- uri: info://status
7
- name: Status
8
- ---
9
- The server is running.
@@ -1,15 +0,0 @@
1
- # echo
2
-
3
- Echo a message back
4
-
5
- ## Parameters
6
-
7
- - message (string): The message to echo
8
-
9
- ## Handler
10
-
11
- ```js
12
- async ({ message }) => {
13
- return { content: [{ type: "text", text: message }] };
14
- }
15
- ```
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- "module": "NodeNext",
5
- "moduleResolution": "NodeNext",
6
- "strict": true,
7
- "outDir": "dist"
8
- },
9
- "include": ["src"]
10
- }
package/tsup.config.ts DELETED
@@ -1,13 +0,0 @@
1
- import { defineConfig } from "tsup";
2
-
3
- export default defineConfig({
4
- entry: ["src/cli.ts"],
5
- format: ["esm"],
6
- target: "node18",
7
- platform: "node",
8
- shims: true,
9
- banner: {
10
- js: "#!/usr/bin/env node",
11
- },
12
- clean: true,
13
- });
package/vitest.config.ts DELETED
@@ -1,12 +0,0 @@
1
- import { defineConfig } from "vitest/config";
2
-
3
- export default defineConfig({
4
- test: {
5
- globals: true,
6
- environment: "node",
7
- coverage: {
8
- provider: "v8",
9
- reporter: ["text", "lcov"],
10
- },
11
- },
12
- });