mcp-optimizer 0.0.1-alpha.1
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/.github/workflows/publish.yml +34 -0
- package/README.md +31 -0
- package/bin/mcp-optimizer.js +16 -0
- package/dist/fix/fixer.js +19 -0
- package/dist/index.js +12 -0
- package/dist/mcpServer.js +351 -0
- package/dist/runner/lighthouseRunner.js +66 -0
- package/dist/server.js +79 -0
- package/docs/MCP.md +21 -0
- package/package.json +43 -0
- package/server.ts +600 -0
- package/src/fix/fixer.ts +18 -0
- package/src/index.ts +10 -0
- package/src/mcpServer.ts +335 -0
- package/src/runner/lighthouseRunner.ts +66 -0
- package/src/types/modelcontextprotocol__sdk.d.ts +4 -0
- package/tsconfig.json +13 -0
package/src/mcpServer.ts
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { runLighthouseAudit } from "./runner/lighthouseRunner";
|
|
4
|
+
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
5
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
6
|
+
import * as http from 'http';
|
|
7
|
+
import { IncomingMessage, ServerResponse } from 'http';
|
|
8
|
+
import { autoFixFromReport } from './fix/fixer';
|
|
9
|
+
|
|
10
|
+
export class LighthouseMcpServer {
|
|
11
|
+
private readonly server: McpServer;
|
|
12
|
+
private readonly reports: Map<string, any> = new Map();
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.server = new McpServer({ name: "Lighthouse MCP Server", version: "0.1.0" });
|
|
16
|
+
this.registerTools();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Run an audit and store the report. Returns stored record.
|
|
21
|
+
*/
|
|
22
|
+
public async runAudit(options: { url: string; categories?: string[]; formFactor?: 'mobile' | 'desktop' }) {
|
|
23
|
+
const { url, categories, formFactor } = options;
|
|
24
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
25
|
+
|
|
26
|
+
const opts: any = {};
|
|
27
|
+
if (categories && categories.length > 0) opts.categories = categories;
|
|
28
|
+
if (formFactor === 'mobile') opts.emulateMobile = true;
|
|
29
|
+
|
|
30
|
+
const runnerResult = await runLighthouseAudit(url, opts);
|
|
31
|
+
const reportJson = runnerResult.report;
|
|
32
|
+
const lhr = runnerResult.lhr;
|
|
33
|
+
const reportObj = typeof reportJson === 'string' ? JSON.parse(reportJson) : reportJson;
|
|
34
|
+
|
|
35
|
+
const record = {
|
|
36
|
+
id,
|
|
37
|
+
url,
|
|
38
|
+
fetchedAt: new Date().toISOString(),
|
|
39
|
+
lhr,
|
|
40
|
+
report: reportObj
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.reports.set(id, record);
|
|
44
|
+
return record;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private registerTools(): void {
|
|
48
|
+
this.server.tool(
|
|
49
|
+
"lighthouse_run_audit",
|
|
50
|
+
"Run a Lighthouse audit against a URL and store the report",
|
|
51
|
+
{
|
|
52
|
+
url: z.string().describe("The URL to audit, including protocol (http:// or https://)"),
|
|
53
|
+
categories: z.array(z.string()).optional().describe("Optional Lighthouse categories to run, e.g. ['performance','accessibility']"),
|
|
54
|
+
formFactor: z.enum(["mobile", "desktop"]).optional().describe("Emulated form factor")
|
|
55
|
+
},
|
|
56
|
+
async ({ url, categories, formFactor }) => {
|
|
57
|
+
const id = `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
58
|
+
try {
|
|
59
|
+
const opts: any = {};
|
|
60
|
+
if (categories && categories.length > 0) opts.categories = categories;
|
|
61
|
+
if (formFactor === 'mobile') opts.emulateMobile = true;
|
|
62
|
+
const runnerResult = await runLighthouseAudit(url, opts);
|
|
63
|
+
const reportJson = runnerResult.report;
|
|
64
|
+
const lhr = runnerResult.lhr;
|
|
65
|
+
const reportObj = typeof reportJson === 'string' ? JSON.parse(reportJson) : reportJson;
|
|
66
|
+
this.reports.set(id, {
|
|
67
|
+
id,
|
|
68
|
+
url,
|
|
69
|
+
fetchedAt: new Date().toISOString(),
|
|
70
|
+
lhr,
|
|
71
|
+
report: reportObj
|
|
72
|
+
});
|
|
73
|
+
const perf = lhr.categories?.performance?.score ?? null;
|
|
74
|
+
const accessibility = lhr.categories?.accessibility?.score ?? null;
|
|
75
|
+
const summary = {
|
|
76
|
+
reportId: id,
|
|
77
|
+
url,
|
|
78
|
+
fetchedAt: new Date().toISOString(),
|
|
79
|
+
performance: perf !== null ? Math.round(perf * 100) : undefined,
|
|
80
|
+
accessibility: accessibility !== null ? Math.round(accessibility * 100) : undefined
|
|
81
|
+
};
|
|
82
|
+
// 返回所有信息,便于 HTTP 路由复用
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) },
|
|
86
|
+
{ type: "text", text: JSON.stringify(lhr, null, 2) },
|
|
87
|
+
{ type: "text", text: JSON.stringify(reportObj, null, 2) }
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
} catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{ type: "text", text: `Lighthouse audit failed: ${error}` }
|
|
94
|
+
]
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
this.server.tool(
|
|
100
|
+
"lighthouse_get_report",
|
|
101
|
+
"Retrieve a previously-run Lighthouse report by reportId",
|
|
102
|
+
{
|
|
103
|
+
reportId: z.string().describe("The report id returned by `lighthouse_run_audit`")
|
|
104
|
+
},
|
|
105
|
+
async ({ reportId }) => {
|
|
106
|
+
const record = this.reports.get(reportId);
|
|
107
|
+
if (!record) {
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{ type: "text", text: `Report not found: ${reportId}` }
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{ type: "text", text: JSON.stringify(record.report, null, 2) }
|
|
117
|
+
]
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
// 新增:从用户 Prompt 中识别 URL 并自动运行 Lighthouse 审计
|
|
123
|
+
this.server.tool(
|
|
124
|
+
"lighthouse_analyze_prompt",
|
|
125
|
+
"Scan a text prompt for a URL, run Lighthouse, and return a summary",
|
|
126
|
+
{
|
|
127
|
+
prompt: z.string().describe("A text prompt that may contain a URL to analyze")
|
|
128
|
+
},
|
|
129
|
+
async ({ prompt }) => {
|
|
130
|
+
try {
|
|
131
|
+
const urlMatch = prompt.match(/https?:\/\/[^\s"'<>]+/i);
|
|
132
|
+
if (!urlMatch) {
|
|
133
|
+
return {
|
|
134
|
+
content: [{ type: "text", text: "No URL found in prompt." }]
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const url = urlMatch[0];
|
|
138
|
+
const result = await this.runAuditViaTool({ url });
|
|
139
|
+
let fix = null;
|
|
140
|
+
if (result && result.lhr) {
|
|
141
|
+
fix = await autoFixFromReport({ lhr: result.lhr, report: JSON.stringify(result.report) });
|
|
142
|
+
}
|
|
143
|
+
const perf = result.lhr?.categories?.performance?.score ?? null;
|
|
144
|
+
const accessibility = result.lhr?.categories?.accessibility?.score ?? null;
|
|
145
|
+
const summary = {
|
|
146
|
+
reportId: result.id,
|
|
147
|
+
url: result.url,
|
|
148
|
+
fetchedAt: result.fetchedAt,
|
|
149
|
+
performance: perf !== null ? Math.round(perf * 100) : undefined,
|
|
150
|
+
accessibility: accessibility !== null ? Math.round(accessibility * 100) : undefined
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) },
|
|
155
|
+
{ type: "text", text: JSON.stringify(result.lhr || {}, null, 2) },
|
|
156
|
+
{ type: "text", text: JSON.stringify(result.report || {}, null, 2) },
|
|
157
|
+
{ type: "text", text: JSON.stringify({ fix }, null, 2) }
|
|
158
|
+
]
|
|
159
|
+
};
|
|
160
|
+
} catch (err: any) {
|
|
161
|
+
return { content: [{ type: "text", text: `Error analyzing prompt: ${String(err)}` }] };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 新增:暴露一个直接调用 MCP 工具的接口
|
|
168
|
+
async runAuditViaTool(params: { url: string; categories?: string[]; formFactor?: 'mobile' | 'desktop' }) {
|
|
169
|
+
// Some versions of the MCP SDK don't expose an `invokeTool` helper.
|
|
170
|
+
// Call the internal runner directly and return a shape similar to the
|
|
171
|
+
// tool's output so HTTP callers can use `result.summary` / `result.lhr`.
|
|
172
|
+
const record = await this.runAudit(params);
|
|
173
|
+
const perf = record.lhr?.categories?.performance?.score ?? null;
|
|
174
|
+
const accessibility = record.lhr?.categories?.accessibility?.score ?? null;
|
|
175
|
+
const summary = {
|
|
176
|
+
reportId: record.id,
|
|
177
|
+
url: record.url,
|
|
178
|
+
fetchedAt: record.fetchedAt,
|
|
179
|
+
performance: perf !== null ? Math.round(perf * 100) : undefined,
|
|
180
|
+
accessibility: accessibility !== null ? Math.round(accessibility * 100) : undefined
|
|
181
|
+
};
|
|
182
|
+
// return record plus a `summary` to match existing HTTP handler expectations
|
|
183
|
+
return { ...record, summary };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async connect(transport: Transport): Promise<void> {
|
|
187
|
+
await this.server.connect(transport);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export default LighthouseMcpServer;
|
|
192
|
+
|
|
193
|
+
export async function startMcpServer(): Promise<void> {
|
|
194
|
+
// Run HTTP/SSE server
|
|
195
|
+
const port = Number(process.env.PORT || process.env.AUDIT_PORT || 5000);
|
|
196
|
+
const mcp = new LighthouseMcpServer();
|
|
197
|
+
let sseTransport: SSEServerTransport | null = null;
|
|
198
|
+
const pendingPosts: Array<{ body: string; url: string | undefined; headers: any }> = [];
|
|
199
|
+
|
|
200
|
+
const { Readable, Writable } = await (async () => {
|
|
201
|
+
const mod = await import('stream');
|
|
202
|
+
return { Readable: mod.Readable, Writable: mod.Writable };
|
|
203
|
+
})();
|
|
204
|
+
|
|
205
|
+
function makeMockReq(body: string, url?: string, headers?: any) {
|
|
206
|
+
const r = new Readable({ read() { this.push(body); this.push(null); } }) as any;
|
|
207
|
+
r.method = 'POST';
|
|
208
|
+
r.url = url || '/sse';
|
|
209
|
+
r.headers = headers || { 'content-type': 'application/json' };
|
|
210
|
+
return r as IncomingMessage;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function makeMockRes() {
|
|
214
|
+
const w = new Writable({ write(chunk, _enc, cb) { cb(); } }) as any;
|
|
215
|
+
w.writeHead = (status: number, headers?: any) => { w.statusCode = status; w._headers = headers; };
|
|
216
|
+
w.end = (data?: any) => { if (data) { try { /* consume */ } catch (_) {} } };
|
|
217
|
+
return w as unknown as ServerResponse<IncomingMessage>;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const server = http.createServer(async (req, res) => {
|
|
221
|
+
try {
|
|
222
|
+
if (req.method === 'GET' && req.url && req.url.startsWith('/sse')) {
|
|
223
|
+
// SSE handshake: set headers and create transport
|
|
224
|
+
// Ensure we send proper SSE headers so clients don't fallback to polling.
|
|
225
|
+
// Register the transport using '/sse' as the message POST path
|
|
226
|
+
sseTransport = new SSEServerTransport('/sse', res as unknown as ServerResponse<IncomingMessage>);
|
|
227
|
+
try {
|
|
228
|
+
await mcp.connect(sseTransport);
|
|
229
|
+
console.info('SSE: new connection established');
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.error('SSE: failed to start transport:', err);
|
|
232
|
+
// Ensure client receives an error
|
|
233
|
+
try {
|
|
234
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
235
|
+
res.end(JSON.stringify({ error: 'failed to start sse transport' }));
|
|
236
|
+
} catch (_) { }
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// replay any pending POSTs that arrived before the GET
|
|
240
|
+
if (pendingPosts.length > 0) {
|
|
241
|
+
console.info(`SSE: replaying ${pendingPosts.length} pending POST(s)`);
|
|
242
|
+
for (const p of pendingPosts.splice(0)) {
|
|
243
|
+
try {
|
|
244
|
+
const mockReq = makeMockReq(p.body, p.url, p.headers);
|
|
245
|
+
const mockRes = makeMockRes();
|
|
246
|
+
// don't await to avoid blocking the handshake
|
|
247
|
+
sseTransport.handlePostMessage(mockReq as unknown as IncomingMessage, mockRes as unknown as ServerResponse<IncomingMessage>).catch((err: any) => {
|
|
248
|
+
console.error('SSE: replay handlePostMessage failed:', err);
|
|
249
|
+
});
|
|
250
|
+
} catch (err) {
|
|
251
|
+
console.error('SSE: failed to replay pending POST:', err);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (req.method === 'POST' && req.url && (req.url === '/messages' || req.url.startsWith('/sse'))) {
|
|
259
|
+
console.info(`SSE: received POST to ${req.url}`);
|
|
260
|
+
if (!sseTransport) {
|
|
261
|
+
// Buffer the POST body so it can be processed once the GET arrives.
|
|
262
|
+
try {
|
|
263
|
+
let body = '';
|
|
264
|
+
for await (const chunk of req) {
|
|
265
|
+
body += chunk;
|
|
266
|
+
}
|
|
267
|
+
console.info('SSE: POST received before GET; buffering');
|
|
268
|
+
pendingPosts.push({ body, url: req.url, headers: req.headers });
|
|
269
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
270
|
+
res.end(JSON.stringify({ ok: true }));
|
|
271
|
+
return;
|
|
272
|
+
} catch (e) {
|
|
273
|
+
console.error('SSE: error reading POST body before GET:', e);
|
|
274
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
275
|
+
res.end(JSON.stringify({ error: String(e) }));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
try {
|
|
280
|
+
await sseTransport.handlePostMessage(req as unknown as IncomingMessage, res as unknown as ServerResponse<IncomingMessage>);
|
|
281
|
+
return;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error('SSE: handlePostMessage failed:', err);
|
|
284
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
285
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (req.method === 'POST' && req.url === '/audit') {
|
|
291
|
+
let body = '';
|
|
292
|
+
for await (const chunk of req) {
|
|
293
|
+
body += chunk;
|
|
294
|
+
}
|
|
295
|
+
const parsed = JSON.parse(body || '{}');
|
|
296
|
+
const url = parsed.url as string | undefined;
|
|
297
|
+
if (!url) {
|
|
298
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
299
|
+
res.end(JSON.stringify({ error: 'missing url' }));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
const result = await mcp.runAuditViaTool({ url, categories: parsed.categories, formFactor: parsed.formFactor });
|
|
304
|
+
let fix = null;
|
|
305
|
+
if (result && result.lhr) {
|
|
306
|
+
fix = await autoFixFromReport({ lhr: result.lhr, report: JSON.stringify(result.report) });
|
|
307
|
+
}
|
|
308
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
309
|
+
res.end(JSON.stringify({ summary: result.summary, fix, error: (result as any).error }));
|
|
310
|
+
} catch (err: any) {
|
|
311
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
312
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
313
|
+
}
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// fallback
|
|
318
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
319
|
+
res.end(JSON.stringify({ message: 'MCP Optimizer running — POST /audit { "url": "https://..." }' }));
|
|
320
|
+
} catch (outerErr: any) {
|
|
321
|
+
// ensure no plain-text stdout noise
|
|
322
|
+
try {
|
|
323
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
324
|
+
res.end(JSON.stringify({ error: String(outerErr) }));
|
|
325
|
+
} catch (_) {
|
|
326
|
+
// ignore
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
return new Promise((resolve, reject) => {
|
|
332
|
+
server.listen(port, () => resolve());
|
|
333
|
+
server.on('error', reject);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export async function runLighthouseAudit(
|
|
2
|
+
url: string,
|
|
3
|
+
opts?: { emulateMobile?: boolean; categories?: string[] }
|
|
4
|
+
): Promise<any> {
|
|
5
|
+
// Use the Lighthouse CLI via `npx` to avoid importing the ESM-only
|
|
6
|
+
// Lighthouse package into this CommonJS runtime.
|
|
7
|
+
const dynamicImport = new Function('specifier', 'return import(specifier)');
|
|
8
|
+
const chromeLauncherModule = await (dynamicImport as any)('chrome-launcher');
|
|
9
|
+
const chromeLauncher = (chromeLauncherModule && (chromeLauncherModule.default ?? chromeLauncherModule)) as any;
|
|
10
|
+
const { execFile } = await (dynamicImport as any)('node:child_process');
|
|
11
|
+
|
|
12
|
+
const os = await (dynamicImport as any)('node:os');
|
|
13
|
+
const pathMod = await (dynamicImport as any)('node:path');
|
|
14
|
+
const tmpDir = pathMod.join(os.tmpdir(), `lighthouse-${Date.now()}-${Math.random().toString(36).slice(2,6)}`);
|
|
15
|
+
const fs = await (dynamicImport as any)('node:fs');
|
|
16
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
17
|
+
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'], userDataDir: tmpDir });
|
|
18
|
+
try {
|
|
19
|
+
const port = chrome.port;
|
|
20
|
+
const args: string[] = [
|
|
21
|
+
'lighthouse',
|
|
22
|
+
url,
|
|
23
|
+
`--port=${port}`,
|
|
24
|
+
'--output=json',
|
|
25
|
+
'--quiet'
|
|
26
|
+
];
|
|
27
|
+
if (opts?.emulateMobile) args.push('--preset=mobile');
|
|
28
|
+
if (opts?.categories && opts.categories.length) args.push(`--only-categories=${opts.categories.join(',')}`);
|
|
29
|
+
|
|
30
|
+
// Run via npx to ensure local package is used
|
|
31
|
+
const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
32
|
+
|
|
33
|
+
const reportJson: string = await new Promise((resolve, reject) => {
|
|
34
|
+
execFile(cmd, args, { maxBuffer: 10 * 1024 * 1024 }, (err: any, stdout: string, stderr: string) => {
|
|
35
|
+
if (err) {
|
|
36
|
+
const message = stderr || (err && err.message) || String(err);
|
|
37
|
+
return reject(new Error(message));
|
|
38
|
+
}
|
|
39
|
+
resolve(stdout);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// The CLI returns a JSON string containing the report and LHR; parse it
|
|
44
|
+
const parsed = JSON.parse(reportJson);
|
|
45
|
+
// Lighthouse CLI places the LHR inside `lhr` when output=json
|
|
46
|
+
const lhr = parsed.lhr ?? parsed;
|
|
47
|
+
return { lhr, report: parsed };
|
|
48
|
+
} catch (err: any) {
|
|
49
|
+
// If anything goes wrong launching Chrome or running Lighthouse,
|
|
50
|
+
// return a minimal error-shaped report so the MCP server can continue
|
|
51
|
+
// to respond and surface the error to callers instead of crashing.
|
|
52
|
+
const message = (err && (err.stack || err.message)) || String(err);
|
|
53
|
+
return {
|
|
54
|
+
lhr: { categories: {} },
|
|
55
|
+
report: { error: message },
|
|
56
|
+
error: message
|
|
57
|
+
};
|
|
58
|
+
} finally {
|
|
59
|
+
try {
|
|
60
|
+
await chrome.kill();
|
|
61
|
+
}
|
|
62
|
+
catch (_) {
|
|
63
|
+
// ignore cleanup errors
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|