projectpulse-mcp 1.0.3 → 1.1.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 +15 -4
- package/dist/tools/analyze-code-scanning.d.ts +10 -4
- package/dist/tools/analyze-code-scanning.d.ts.map +1 -1
- package/dist/tools/analyze-code-scanning.js +118 -7
- package/dist/tools/analyze-code-scanning.js.map +1 -1
- package/dist/tools/analyze-code-scanning.test.js +18 -8
- package/dist/tools/analyze-code-scanning.test.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -15,18 +15,20 @@ This Model Context Protocol (MCP) server allows AI assistants to analyze the hea
|
|
|
15
15
|
|
|
16
16
|
### Example Output
|
|
17
17
|
|
|
18
|
-

|
|
19
19
|
|
|
20
20
|
## Quick Start
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### Claude Code (CLI)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
To use this MCP server with Claude Code, run the following command in your terminal:
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npx projectpulse-mcp
|
|
27
|
+
claude mcp add projectpulse -- npx projectpulse-mcp
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
*Note: You must have a `.env` file with your `GITHUB_TOKEN` in the directory where you run Claude Code.*
|
|
31
|
+
|
|
30
32
|
### Claude Desktop Configuration
|
|
31
33
|
|
|
32
34
|
To use this with Claude Desktop, add the following configuration to your `claude_desktop_config.json` file (usually located at `%APPDATA%\Claude\claude_desktop_config.json` on Windows or `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
@@ -48,6 +50,15 @@ To use this with Claude Desktop, add the following configuration to your `claude
|
|
|
48
50
|
}
|
|
49
51
|
```
|
|
50
52
|
|
|
53
|
+
### Other MCP Clients (Cursor, Windsurf, etc.)
|
|
54
|
+
|
|
55
|
+
To use this MCP server in other clients that support `stdio` transport (like Cursor, Windsurf, or custom applications), configure a new MCP server with the following settings:
|
|
56
|
+
|
|
57
|
+
- **Type/Transport**: `stdio`
|
|
58
|
+
- **Command**: `npx`
|
|
59
|
+
- **Arguments**: `-y projectpulse-mcp`
|
|
60
|
+
- **Environment Variables**: Add `GITHUB_TOKEN` with your GitHub Personal Access Token.
|
|
61
|
+
|
|
51
62
|
## Tools
|
|
52
63
|
|
|
53
64
|
| Tool Name | Description | Input Schema |
|
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
|
|
3
|
-
owner:
|
|
4
|
-
repo:
|
|
5
|
-
|
|
3
|
+
declare const inputSchema: z.ZodObject<{
|
|
4
|
+
owner: z.ZodString;
|
|
5
|
+
repo: z.ZodString;
|
|
6
|
+
trigger_scan: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
7
|
+
poll_timeout_seconds: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
8
|
+
poll_interval_seconds: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare function executeAnalyzeCodeScanning(input: z.infer<typeof inputSchema>): Promise<{
|
|
6
11
|
content: {
|
|
7
12
|
type: "text";
|
|
8
13
|
text: string;
|
|
9
14
|
}[];
|
|
10
15
|
}>;
|
|
11
16
|
export declare function registerAnalyzeCodeScanning(server: McpServer): void;
|
|
17
|
+
export {};
|
|
12
18
|
//# sourceMappingURL=analyze-code-scanning.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-code-scanning.d.ts","sourceRoot":"","sources":["../../src/tools/analyze-code-scanning.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyze-code-scanning.d.ts","sourceRoot":"","sources":["../../src/tools/analyze-code-scanning.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,QAAA,MAAM,WAAW;;;;;;iBASf,CAAC;AAuFH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC;;;;;GAqElF;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CASnE"}
|
|
@@ -2,11 +2,111 @@ import { z } from "zod";
|
|
|
2
2
|
import { getOctokit } from "../github/client.js";
|
|
3
3
|
const inputSchema = z.object({
|
|
4
4
|
owner: z.string().describe("GitHub repository owner"),
|
|
5
|
-
repo: z.string().describe("GitHub repository name")
|
|
5
|
+
repo: z.string().describe("GitHub repository name"),
|
|
6
|
+
trigger_scan: z.boolean().optional().default(false)
|
|
7
|
+
.describe("If true, triggers CodeQL workflow then polls for results before returning alerts"),
|
|
8
|
+
poll_timeout_seconds: z.number().optional().default(300)
|
|
9
|
+
.describe("Max seconds to wait for CodeQL scan completion (default 300)"),
|
|
10
|
+
poll_interval_seconds: z.number().optional().default(15)
|
|
11
|
+
.describe("Seconds between poll attempts (default 15)")
|
|
6
12
|
});
|
|
7
|
-
|
|
13
|
+
async function discoverCodeQLWorkflow(owner, repo) {
|
|
14
|
+
const octokit = getOctokit();
|
|
8
15
|
try {
|
|
9
|
-
|
|
16
|
+
// Check if Default Setup is active
|
|
17
|
+
const { data: defaultSetup } = await octokit.request('GET /repos/{owner}/{repo}/code-scanning/default-setup', { owner, repo });
|
|
18
|
+
if (defaultSetup.state === 'configured') {
|
|
19
|
+
throw new Error(`CodeQL uses Default Setup (schedule: ${defaultSetup.schedule}, last update: ${defaultSetup.updated_at}). ` +
|
|
20
|
+
`Manual trigger not supported via API. Switch to Advanced Setup in repo settings to enable trigger_scan.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
// Rethrow if it's our specific error about Default Setup being active
|
|
25
|
+
if (e instanceof Error && e.message.includes("CodeQL uses Default Setup")) {
|
|
26
|
+
throw e;
|
|
27
|
+
}
|
|
28
|
+
// Otherwise (e.g., 404), proceed to look for a YAML workflow file
|
|
29
|
+
}
|
|
30
|
+
const { data: workflows } = await octokit.actions.listRepoWorkflows({ owner, repo });
|
|
31
|
+
const codeQLWorkflow = workflows.workflows.find(w => w.name.toLowerCase().includes("codeql") ||
|
|
32
|
+
w.path.toLowerCase().includes("codeql"));
|
|
33
|
+
if (!codeQLWorkflow) {
|
|
34
|
+
throw new Error("CodeQL workflow not found in this repository.");
|
|
35
|
+
}
|
|
36
|
+
return { id: codeQLWorkflow.id, name: codeQLWorkflow.name };
|
|
37
|
+
}
|
|
38
|
+
async function triggerWorkflow(owner, repo, workflowId) {
|
|
39
|
+
const octokit = getOctokit();
|
|
40
|
+
const { data: repoInfo } = await octokit.repos.get({ owner, repo });
|
|
41
|
+
try {
|
|
42
|
+
await octokit.actions.createWorkflowDispatch({
|
|
43
|
+
owner,
|
|
44
|
+
repo,
|
|
45
|
+
workflow_id: workflowId,
|
|
46
|
+
ref: repoInfo.default_branch
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
51
|
+
throw new Error(`Failed to trigger workflow (does it have workflow_dispatch trigger?): ${message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function findTriggeredRun(owner, repo, workflowId, triggeredAfter) {
|
|
55
|
+
const octokit = getOctokit();
|
|
56
|
+
for (let i = 0; i < 5; i++) {
|
|
57
|
+
const { data: runs } = await octokit.actions.listWorkflowRuns({
|
|
58
|
+
owner,
|
|
59
|
+
repo,
|
|
60
|
+
workflow_id: workflowId,
|
|
61
|
+
per_page: 5
|
|
62
|
+
});
|
|
63
|
+
const run = runs.workflow_runs.find(r => new Date(r.created_at) >= triggeredAfter);
|
|
64
|
+
if (run) {
|
|
65
|
+
return run.id;
|
|
66
|
+
}
|
|
67
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
68
|
+
}
|
|
69
|
+
throw new Error("Could not find the triggered workflow run.");
|
|
70
|
+
}
|
|
71
|
+
async function pollRunCompletion(owner, repo, runId, timeoutSec, intervalSec) {
|
|
72
|
+
const octokit = getOctokit();
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
const timeoutMs = timeoutSec * 1000;
|
|
75
|
+
const intervalMs = intervalSec * 1000;
|
|
76
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
77
|
+
const { data: run } = await octokit.actions.getWorkflowRun({ owner, repo, run_id: runId });
|
|
78
|
+
if (run.status === "completed") {
|
|
79
|
+
return { status: run.status, conclusion: run.conclusion };
|
|
80
|
+
}
|
|
81
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
82
|
+
}
|
|
83
|
+
return { status: "in_progress", conclusion: null };
|
|
84
|
+
}
|
|
85
|
+
export async function executeAnalyzeCodeScanning(input) {
|
|
86
|
+
const { owner, repo, trigger_scan, poll_timeout_seconds, poll_interval_seconds } = input;
|
|
87
|
+
let warning = "";
|
|
88
|
+
if (trigger_scan) {
|
|
89
|
+
try {
|
|
90
|
+
const workflow = await discoverCodeQLWorkflow(owner, repo);
|
|
91
|
+
// Allow a small buffer for the trigger time
|
|
92
|
+
const triggeredAfter = new Date(Date.now() - 5000);
|
|
93
|
+
await triggerWorkflow(owner, repo, workflow.id);
|
|
94
|
+
const runId = await findTriggeredRun(owner, repo, workflow.id, triggeredAfter);
|
|
95
|
+
const result = await pollRunCompletion(owner, repo, runId, poll_timeout_seconds, poll_interval_seconds);
|
|
96
|
+
if (result.status !== "completed") {
|
|
97
|
+
warning = `WARNING: CodeQL scan did not complete within the timeout of ${poll_timeout_seconds} seconds. Returning currently available alerts.`;
|
|
98
|
+
}
|
|
99
|
+
else if (result.conclusion !== "success") {
|
|
100
|
+
warning = `WARNING: CodeQL scan completed with conclusion: ${result.conclusion}. Returning currently available alerts.`;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
warning = `WARNING: Failed to trigger or poll scan: ${message}. Returning currently available alerts.`;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const { data } = await getOctokit().codeScanning.listAlertsForRepo({ owner, repo, state: "open" });
|
|
10
110
|
const result = data.map((alert) => ({
|
|
11
111
|
rule_id: alert.rule.id,
|
|
12
112
|
severity: alert.rule.severity,
|
|
@@ -14,12 +114,20 @@ export async function executeAnalyzeCodeScanning({ owner, repo }) {
|
|
|
14
114
|
state: alert.state,
|
|
15
115
|
most_recent_instance_path: alert.most_recent_instance.location?.path,
|
|
16
116
|
most_recent_instance_start_line: alert.most_recent_instance.location?.start_line,
|
|
17
|
-
html_url: alert.html_url
|
|
117
|
+
html_url: alert.html_url,
|
|
118
|
+
alert_number: alert.number,
|
|
119
|
+
message_text: alert.most_recent_instance.message?.text,
|
|
120
|
+
end_line: alert.most_recent_instance.location?.end_line,
|
|
121
|
+
created_at: alert.created_at
|
|
18
122
|
}));
|
|
123
|
+
const responseText = JSON.stringify({
|
|
124
|
+
alerts: result,
|
|
125
|
+
...(warning ? { warning } : {})
|
|
126
|
+
}, null, 2);
|
|
19
127
|
return {
|
|
20
128
|
content: [{
|
|
21
129
|
type: "text",
|
|
22
|
-
text:
|
|
130
|
+
text: responseText
|
|
23
131
|
}]
|
|
24
132
|
};
|
|
25
133
|
}
|
|
@@ -28,7 +136,10 @@ export async function executeAnalyzeCodeScanning({ owner, repo }) {
|
|
|
28
136
|
return {
|
|
29
137
|
content: [{
|
|
30
138
|
type: "text",
|
|
31
|
-
text:
|
|
139
|
+
text: JSON.stringify({
|
|
140
|
+
message: "Code scanning not configured on this repo",
|
|
141
|
+
...(warning ? { warning } : {})
|
|
142
|
+
}, null, 2)
|
|
32
143
|
}]
|
|
33
144
|
};
|
|
34
145
|
}
|
|
@@ -37,7 +148,7 @@ export async function executeAnalyzeCodeScanning({ owner, repo }) {
|
|
|
37
148
|
}
|
|
38
149
|
export function registerAnalyzeCodeScanning(server) {
|
|
39
150
|
server.registerTool("analyze_code_scanning", {
|
|
40
|
-
description: "Get Code Scanning alerts for a GitHub repository with CodeQL",
|
|
151
|
+
description: "Get open Code Scanning alerts (CodeQL) for a GitHub repository. Returns per alert: alert_number, rule_id, severity, rule_description, message_text (explains WHY the code is vulnerable), file path with start/end line, created_at, html_url. Use message_text to understand the specific vulnerability. Set trigger_scan=true to auto-discover and trigger the CodeQL workflow, poll for completion, then return fresh alerts.",
|
|
41
152
|
inputSchema
|
|
42
153
|
}, executeAnalyzeCodeScanning);
|
|
43
154
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-code-scanning.js","sourceRoot":"","sources":["../../src/tools/analyze-code-scanning.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEjD,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IACzB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,yBAAyB,CAAC;IACrD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"analyze-code-scanning.js","sourceRoot":"","sources":["../../src/tools/analyze-code-scanning.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,9 +9,14 @@ describe('executeAnalyzeCodeScanning', () => {
|
|
|
9
9
|
it('should return code scanning alerts', async () => {
|
|
10
10
|
const mockAlerts = [
|
|
11
11
|
{
|
|
12
|
+
number: 1,
|
|
13
|
+
created_at: '2026-08-28T10:00:00Z',
|
|
12
14
|
rule: { id: 'js/sql-injection', severity: 'error', description: 'SQL Injection' },
|
|
13
15
|
state: 'open',
|
|
14
|
-
most_recent_instance: {
|
|
16
|
+
most_recent_instance: {
|
|
17
|
+
message: { text: 'SQL query built from user input' },
|
|
18
|
+
location: { path: 'src/db.ts', start_line: 10, end_line: 15 }
|
|
19
|
+
},
|
|
15
20
|
html_url: 'https://github.com/test/repo/security/code-scanning/1'
|
|
16
21
|
}
|
|
17
22
|
];
|
|
@@ -19,19 +24,23 @@ describe('executeAnalyzeCodeScanning', () => {
|
|
|
19
24
|
vi.mocked(getOctokit).mockReturnValue({
|
|
20
25
|
codeScanning: { listAlertsForRepo: mockListAlerts }
|
|
21
26
|
});
|
|
22
|
-
const result = await executeAnalyzeCodeScanning({ owner: 'test', repo: 'repo' });
|
|
23
|
-
expect(mockListAlerts).toHaveBeenCalledWith({ owner: 'test', repo: 'repo' });
|
|
27
|
+
const result = await executeAnalyzeCodeScanning({ owner: 'test', repo: 'repo', trigger_scan: false, poll_timeout_seconds: 300, poll_interval_seconds: 15 });
|
|
28
|
+
expect(mockListAlerts).toHaveBeenCalledWith({ owner: 'test', repo: 'repo', state: 'open' });
|
|
24
29
|
expect(result.content).toHaveLength(1);
|
|
25
30
|
expect(result.content[0].type).toBe('text');
|
|
26
31
|
const parsedText = JSON.parse(result.content[0].text);
|
|
27
|
-
expect(parsedText).toHaveLength(1);
|
|
28
|
-
expect(parsedText[0]).toEqual({
|
|
32
|
+
expect(parsedText.alerts).toHaveLength(1);
|
|
33
|
+
expect(parsedText.alerts[0]).toEqual({
|
|
34
|
+
alert_number: 1,
|
|
29
35
|
rule_id: 'js/sql-injection',
|
|
30
36
|
severity: 'error',
|
|
31
37
|
rule_description: 'SQL Injection',
|
|
32
38
|
state: 'open',
|
|
39
|
+
message_text: 'SQL query built from user input',
|
|
33
40
|
most_recent_instance_path: 'src/db.ts',
|
|
34
41
|
most_recent_instance_start_line: 10,
|
|
42
|
+
end_line: 15,
|
|
43
|
+
created_at: '2026-08-28T10:00:00Z',
|
|
35
44
|
html_url: 'https://github.com/test/repo/security/code-scanning/1'
|
|
36
45
|
});
|
|
37
46
|
});
|
|
@@ -42,10 +51,11 @@ describe('executeAnalyzeCodeScanning', () => {
|
|
|
42
51
|
vi.mocked(getOctokit).mockReturnValue({
|
|
43
52
|
codeScanning: { listAlertsForRepo: mockListAlerts }
|
|
44
53
|
});
|
|
45
|
-
const result = await executeAnalyzeCodeScanning({ owner: 'test', repo: 'repo' });
|
|
54
|
+
const result = await executeAnalyzeCodeScanning({ owner: 'test', repo: 'repo', trigger_scan: false, poll_timeout_seconds: 300, poll_interval_seconds: 15 });
|
|
46
55
|
expect(result.content).toHaveLength(1);
|
|
47
56
|
expect(result.content[0].type).toBe('text');
|
|
48
|
-
|
|
57
|
+
const parsedText = JSON.parse(result.content[0].text);
|
|
58
|
+
expect(parsedText.message).toBe("Code scanning not configured on this repo");
|
|
49
59
|
});
|
|
50
60
|
it('should throw error for non-404 API failures', async () => {
|
|
51
61
|
const serverError = new Error('Server Error');
|
|
@@ -54,7 +64,7 @@ describe('executeAnalyzeCodeScanning', () => {
|
|
|
54
64
|
vi.mocked(getOctokit).mockReturnValue({
|
|
55
65
|
codeScanning: { listAlertsForRepo: mockListAlerts }
|
|
56
66
|
});
|
|
57
|
-
await expect(executeAnalyzeCodeScanning({ owner: 'test', repo: 'repo' }))
|
|
67
|
+
await expect(executeAnalyzeCodeScanning({ owner: 'test', repo: 'repo', trigger_scan: false, poll_timeout_seconds: 300, poll_interval_seconds: 15 }))
|
|
58
68
|
.rejects
|
|
59
69
|
.toThrow('Server Error');
|
|
60
70
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-code-scanning.test.js","sourceRoot":"","sources":["../../src/tools/analyze-code-scanning.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,0BAA0B,EAAE,MAAM,4BAA4B,CAAC;AAExE,EAAE,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;AAE/B,QAAQ,CAAC,4BAA4B,EAAE,GAAG,EAAE;IACxC,UAAU,CAAC,GAAG,EAAE;QACZ,EAAE,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QAChD,MAAM,UAAU,GAAG;YACf;gBACI,IAAI,EAAE,EAAE,EAAE,EAAE,kBAAkB,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE;gBACjF,KAAK,EAAE,MAAM;gBACb,oBAAoB,EAAE,EAAE,QAAQ,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"analyze-code-scanning.test.js","sourceRoot":"","sources":["../../src/tools/analyze-code-scanning.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "projectpulse-mcp",
|
|
3
3
|
"description": "MCP Server for GitHub repository health monitoring",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "vitest run",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "https://github.com/alexbypa/projectpulse-mcp.git"
|
|
23
|
+
"url": "https://github.com/alexbypa/github-projectpulse-mcp.git"
|
|
24
24
|
},
|
|
25
25
|
"bugs": {
|
|
26
|
-
"url": "https://github.com/alexbypa/projectpulse-mcp/issues"
|
|
26
|
+
"url": "https://github.com/alexbypa/github-projectpulse-mcp/issues"
|
|
27
27
|
},
|
|
28
28
|
"type": "module",
|
|
29
29
|
"dependencies": {
|