@perfonext/build-mcp 0.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/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/format.d.ts +4 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +19 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/analysis.d.ts +15 -0
- package/dist/parser/analysis.d.ts.map +1 -0
- package/dist/parser/analysis.js +110 -0
- package/dist/parser/analysis.js.map +1 -0
- package/dist/parser/build-stats.d.ts +4 -0
- package/dist/parser/build-stats.d.ts.map +1 -0
- package/dist/parser/build-stats.js +201 -0
- package/dist/parser/build-stats.js.map +1 -0
- package/dist/parser/types.d.ts +86 -0
- package/dist/parser/types.d.ts.map +1 -0
- package/dist/parser/types.js +2 -0
- package/dist/parser/types.js.map +1 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +18 -0
- package/dist/store.js.map +1 -0
- package/dist/tools/compare-builds.d.ts +3 -0
- package/dist/tools/compare-builds.d.ts.map +1 -0
- package/dist/tools/compare-builds.js +86 -0
- package/dist/tools/compare-builds.js.map +1 -0
- package/dist/tools/get-largest-routes.d.ts +3 -0
- package/dist/tools/get-largest-routes.d.ts.map +1 -0
- package/dist/tools/get-largest-routes.js +51 -0
- package/dist/tools/get-largest-routes.js.map +1 -0
- package/dist/tools/get-shared-chunks.d.ts +3 -0
- package/dist/tools/get-shared-chunks.d.ts.map +1 -0
- package/dist/tools/get-shared-chunks.js +48 -0
- package/dist/tools/get-shared-chunks.js.map +1 -0
- package/dist/tools/load-build-stats.d.ts +3 -0
- package/dist/tools/load-build-stats.d.ts.map +1 -0
- package/dist/tools/load-build-stats.js +31 -0
- package/dist/tools/load-build-stats.js.map +1 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Perfonext
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# perfonext-build-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@perfonext/build-mcp)
|
|
4
|
+
|
|
5
|
+
`perfonext-build-mcp` is an MCP server for loading and analyzing Next.js build artifacts. It gives GitHub Copilot and other MCP clients structured bundle and route-size data they can reason over instead of forcing the model to inspect raw `.next` manifests.
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
- loads Next.js build artifacts from a `.next` directory
|
|
10
|
+
- ranks the largest user-facing routes by emitted bundle footprint
|
|
11
|
+
- identifies the heaviest shared chunks that affect multiple routes
|
|
12
|
+
- keeps loaded build snapshots in memory so an MCP client can inspect them without re-reading the same build
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
The MVP reads build artifacts developers already have after running `next build`:
|
|
17
|
+
|
|
18
|
+
- `.next/build-manifest.json`
|
|
19
|
+
- `.next/prerender-manifest.json` when present
|
|
20
|
+
- `.next/app-build-manifest.json` when present
|
|
21
|
+
- optional captured `next build` output text to derive build duration
|
|
22
|
+
|
|
23
|
+
## Tools
|
|
24
|
+
|
|
25
|
+
| Tool | Description |
|
|
26
|
+
|------|-------------|
|
|
27
|
+
| `load_build_stats` | Parse a Next.js `.next` directory and load the build snapshot into memory |
|
|
28
|
+
| `get_largest_routes` | Rank the heaviest user-facing routes by total emitted chunk bytes |
|
|
29
|
+
| `get_shared_chunks` | Rank shared chunks by size and show which routes depend on them |
|
|
30
|
+
| `compare_builds` | Compare a baseline and current build snapshot to show which routes and chunks grew or shrank |
|
|
31
|
+
|
|
32
|
+
The output stays machine-readable and includes raw byte counts so Copilot can explain regressions, prioritise fixes, and suggest concrete dependency or import-level follow-up.
|
|
33
|
+
|
|
34
|
+
## Install
|
|
35
|
+
|
|
36
|
+
Run directly with `npx`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx -y @perfonext/build-mcp
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or install globally:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -g @perfonext/build-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The executable command remains `perfonext-build-mcp` after installation.
|
|
49
|
+
|
|
50
|
+
## MCP Configuration
|
|
51
|
+
|
|
52
|
+
Add this server to VS Code settings:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcp": {
|
|
57
|
+
"servers": {
|
|
58
|
+
"perfonext-build": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "@perfonext/build-mcp"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Example Copilot Prompts
|
|
68
|
+
|
|
69
|
+
- "Load the Next.js build in `./.next` and show me the largest routes."
|
|
70
|
+
- "Which shared chunks are affecting the most routes in this build?"
|
|
71
|
+
- "Summarize the build footprint and tell me which routes ship the most JavaScript."
|
|
72
|
+
- "Compare my baseline and current `.next` builds and show me which routes or shared chunks grew the most."
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install
|
|
78
|
+
npm run build
|
|
79
|
+
npm test
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Sample fixtures for local validation live under `tests/fixtures/`.
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAUjD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAM5D;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function formatBytes(bytes) {
|
|
2
|
+
if (bytes < 1024) {
|
|
3
|
+
return `${bytes} B`;
|
|
4
|
+
}
|
|
5
|
+
if (bytes < 1024 * 1024) {
|
|
6
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
7
|
+
}
|
|
8
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
9
|
+
}
|
|
10
|
+
export function formatMs(value) {
|
|
11
|
+
if (value === null) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
return `${value.toFixed(2)}ms`;
|
|
15
|
+
}
|
|
16
|
+
export function formatPct(value) {
|
|
17
|
+
return `${(value * 100).toFixed(1)}%`;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,KAAK,IAAI,CAAC;IACtB,CAAC;IAED,IAAI,KAAK,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QACxB,OAAO,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;IAC3C,CAAC;IAED,OAAO,GAAG,CAAC,KAAK,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAoB;IAC3C,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACxC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { registerLoadBuildStats } from './tools/load-build-stats.js';
|
|
5
|
+
import { registerGetLargestRoutes } from './tools/get-largest-routes.js';
|
|
6
|
+
import { registerGetSharedChunks } from './tools/get-shared-chunks.js';
|
|
7
|
+
import { registerCompareBuilds } from './tools/compare-builds.js';
|
|
8
|
+
const server = new McpServer({
|
|
9
|
+
name: 'perfonext-build-mcp',
|
|
10
|
+
version: '0.1.0',
|
|
11
|
+
});
|
|
12
|
+
registerLoadBuildStats(server);
|
|
13
|
+
registerGetLargestRoutes(server);
|
|
14
|
+
registerGetSharedChunks(server);
|
|
15
|
+
registerCompareBuilds(server);
|
|
16
|
+
const transport = new StdioServerTransport();
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAElE,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,uBAAuB,CAAC,MAAM,CAAC,CAAC;AAChC,qBAAqB,CAAC,MAAM,CAAC,CAAC;AAE9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { BuildComparison, ParsedBuildStats, RouteSummaryEntry, SharedChunkSummaryEntry } from './types.js';
|
|
2
|
+
export declare function getBuildSummary(build: ParsedBuildStats): {
|
|
3
|
+
buildId: string;
|
|
4
|
+
buildDir: string;
|
|
5
|
+
routeCount: number;
|
|
6
|
+
chunkCount: number;
|
|
7
|
+
sharedChunkCount: number;
|
|
8
|
+
totalChunkBytes: number;
|
|
9
|
+
sharedChunkBytes: number;
|
|
10
|
+
buildTimeMs: number | null;
|
|
11
|
+
};
|
|
12
|
+
export declare function getLargestRoutes(build: ParsedBuildStats, limit: number): RouteSummaryEntry[];
|
|
13
|
+
export declare function getSharedChunks(build: ParsedBuildStats, limit: number): SharedChunkSummaryEntry[];
|
|
14
|
+
export declare function compareBuilds(baseline: ParsedBuildStats, current: ParsedBuildStats, limit: number): BuildComparison;
|
|
15
|
+
//# sourceMappingURL=analysis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis.d.ts","sourceRoot":"","sources":["../../src/parser/analysis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,gBAAgB,EAEhB,iBAAiB,EACjB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAUpB,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAWA;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAc5F;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,uBAAuB,EAAE,CAWjG;AAED,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,gBAAgB,EACzB,KAAK,EAAE,MAAM,GACZ,eAAe,CAqEjB"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
function calculateDeltaRatio(baseline, current) {
|
|
2
|
+
if (baseline === 0) {
|
|
3
|
+
return current === 0 ? 0 : null;
|
|
4
|
+
}
|
|
5
|
+
return (current - baseline) / baseline;
|
|
6
|
+
}
|
|
7
|
+
export function getBuildSummary(build) {
|
|
8
|
+
return {
|
|
9
|
+
buildId: build.id,
|
|
10
|
+
buildDir: build.buildDir,
|
|
11
|
+
routeCount: build.routes.length,
|
|
12
|
+
chunkCount: build.chunks.length,
|
|
13
|
+
sharedChunkCount: build.chunks.filter(chunk => chunk.isShared).length,
|
|
14
|
+
totalChunkBytes: build.totalChunkBytes,
|
|
15
|
+
sharedChunkBytes: build.sharedChunkBytes,
|
|
16
|
+
buildTimeMs: build.buildTimeMs,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function getLargestRoutes(build, limit) {
|
|
20
|
+
return build.routes.slice(0, limit).map(route => ({
|
|
21
|
+
path: route.path,
|
|
22
|
+
type: route.type,
|
|
23
|
+
prerenderBlockedReason: route.prerenderBlockedReason,
|
|
24
|
+
totalBytes: route.totalBytes,
|
|
25
|
+
initialLoadBytes: route.initialLoadBytes,
|
|
26
|
+
sharedChunkBytes: route.sharedChunkBytes,
|
|
27
|
+
exclusiveChunkBytes: route.exclusiveChunkBytes,
|
|
28
|
+
sharedRatio: route.totalBytes === 0 ? 0 : route.sharedChunkBytes / route.totalBytes,
|
|
29
|
+
chunkCount: route.chunkPaths.length,
|
|
30
|
+
isPrerendered: route.isPrerendered,
|
|
31
|
+
isAppRoute: route.isAppRoute,
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
export function getSharedChunks(build, limit) {
|
|
35
|
+
return build.chunks
|
|
36
|
+
.filter(chunk => chunk.isShared)
|
|
37
|
+
.slice(0, limit)
|
|
38
|
+
.map(chunk => ({
|
|
39
|
+
chunkPath: chunk.chunkPath,
|
|
40
|
+
sizeBytes: chunk.sizeBytes,
|
|
41
|
+
routeCount: chunk.routeCount,
|
|
42
|
+
sharedByRoutes: chunk.sharedByRoutes,
|
|
43
|
+
shareOfAllChunkBytes: build.totalChunkBytes === 0 ? 0 : chunk.sizeBytes / build.totalChunkBytes,
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
export function compareBuilds(baseline, current, limit) {
|
|
47
|
+
const baselineRoutes = new Map(baseline.routes.map(route => [route.path, route]));
|
|
48
|
+
const currentRoutes = new Map(current.routes.map(route => [route.path, route]));
|
|
49
|
+
const allRoutePaths = Array.from(new Set([...baselineRoutes.keys(), ...currentRoutes.keys()]));
|
|
50
|
+
const routeDeltas = allRoutePaths
|
|
51
|
+
.map(path => {
|
|
52
|
+
const baselineRoute = baselineRoutes.get(path);
|
|
53
|
+
const currentRoute = currentRoutes.get(path);
|
|
54
|
+
const baselineBytes = baselineRoute?.totalBytes ?? 0;
|
|
55
|
+
const currentBytes = currentRoute?.totalBytes ?? 0;
|
|
56
|
+
return {
|
|
57
|
+
path,
|
|
58
|
+
baselineBytes,
|
|
59
|
+
currentBytes,
|
|
60
|
+
deltaBytes: currentBytes - baselineBytes,
|
|
61
|
+
deltaRatio: calculateDeltaRatio(baselineBytes, currentBytes),
|
|
62
|
+
};
|
|
63
|
+
})
|
|
64
|
+
.sort((left, right) => Math.abs(right.deltaBytes) - Math.abs(left.deltaBytes))
|
|
65
|
+
.slice(0, limit);
|
|
66
|
+
const baselineChunks = new Map(baseline.chunks.map(chunk => [chunk.chunkPath, chunk]));
|
|
67
|
+
const currentChunks = new Map(current.chunks.map(chunk => [chunk.chunkPath, chunk]));
|
|
68
|
+
const allChunkPaths = Array.from(new Set([...baselineChunks.keys(), ...currentChunks.keys()]));
|
|
69
|
+
const chunkDeltas = allChunkPaths
|
|
70
|
+
.map(chunkPath => {
|
|
71
|
+
const baselineChunk = baselineChunks.get(chunkPath);
|
|
72
|
+
const currentChunk = currentChunks.get(chunkPath);
|
|
73
|
+
const baselineBytes = baselineChunk?.sizeBytes ?? 0;
|
|
74
|
+
const currentBytes = currentChunk?.sizeBytes ?? 0;
|
|
75
|
+
return {
|
|
76
|
+
chunkPath,
|
|
77
|
+
baselineBytes,
|
|
78
|
+
currentBytes,
|
|
79
|
+
deltaBytes: currentBytes - baselineBytes,
|
|
80
|
+
deltaRatio: calculateDeltaRatio(baselineBytes, currentBytes),
|
|
81
|
+
baselineRouteCount: baselineChunk?.routeCount ?? 0,
|
|
82
|
+
currentRouteCount: currentChunk?.routeCount ?? 0,
|
|
83
|
+
};
|
|
84
|
+
})
|
|
85
|
+
.sort((left, right) => Math.abs(right.deltaBytes) - Math.abs(left.deltaBytes))
|
|
86
|
+
.slice(0, limit);
|
|
87
|
+
return {
|
|
88
|
+
baselineBuildId: baseline.id,
|
|
89
|
+
currentBuildId: current.id,
|
|
90
|
+
baselineTotalChunkBytes: baseline.totalChunkBytes,
|
|
91
|
+
currentTotalChunkBytes: current.totalChunkBytes,
|
|
92
|
+
totalChunkDeltaBytes: current.totalChunkBytes - baseline.totalChunkBytes,
|
|
93
|
+
totalChunkDeltaRatio: calculateDeltaRatio(baseline.totalChunkBytes, current.totalChunkBytes),
|
|
94
|
+
baselineSharedChunkBytes: baseline.sharedChunkBytes,
|
|
95
|
+
currentSharedChunkBytes: current.sharedChunkBytes,
|
|
96
|
+
sharedChunkDeltaBytes: current.sharedChunkBytes - baseline.sharedChunkBytes,
|
|
97
|
+
sharedChunkDeltaRatio: calculateDeltaRatio(baseline.sharedChunkBytes, current.sharedChunkBytes),
|
|
98
|
+
baselineBuildTimeMs: baseline.buildTimeMs,
|
|
99
|
+
currentBuildTimeMs: current.buildTimeMs,
|
|
100
|
+
buildTimeDeltaMs: baseline.buildTimeMs === null || current.buildTimeMs === null
|
|
101
|
+
? null
|
|
102
|
+
: current.buildTimeMs - baseline.buildTimeMs,
|
|
103
|
+
buildTimeDeltaRatio: baseline.buildTimeMs === null || current.buildTimeMs === null
|
|
104
|
+
? null
|
|
105
|
+
: calculateDeltaRatio(baseline.buildTimeMs, current.buildTimeMs),
|
|
106
|
+
routeDeltas,
|
|
107
|
+
chunkDeltas,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=analysis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analysis.js","sourceRoot":"","sources":["../../src/parser/analysis.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ParsedBuildStats } from './types.js';
|
|
2
|
+
export declare function parseBuildDurationMs(output: string): number | null;
|
|
3
|
+
export declare function parseBuildStats(buildDirPath: string, buildOutputPath?: string): Promise<ParsedBuildStats>;
|
|
4
|
+
//# sourceMappingURL=build-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-stats.d.ts","sourceRoot":"","sources":["../../src/parser/build-stats.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAA0B,gBAAgB,EAAqC,MAAM,YAAY,CAAC;AAmH9G,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAqBlE;AAED,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,gBAAgB,CAAC,CA+H3B"}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return value !== null && typeof value === 'object';
|
|
6
|
+
}
|
|
7
|
+
async function readJsonIfPresent(filePath) {
|
|
8
|
+
try {
|
|
9
|
+
const content = await readFile(filePath, 'utf-8');
|
|
10
|
+
return JSON.parse(content);
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code === 'ENOENT') {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function getFileSizeBytes(filePath) {
|
|
20
|
+
try {
|
|
21
|
+
const result = await stat(filePath);
|
|
22
|
+
return result.size;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error.code === 'ENOENT') {
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function parseRouteMap(raw) {
|
|
32
|
+
if (!isRecord(raw)) {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
const routeMap = {};
|
|
36
|
+
for (const [route, files] of Object.entries(raw)) {
|
|
37
|
+
if (!Array.isArray(files)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
routeMap[route] = files.filter((file) => typeof file === 'string');
|
|
41
|
+
}
|
|
42
|
+
return routeMap;
|
|
43
|
+
}
|
|
44
|
+
function shouldIncludeRoute(route) {
|
|
45
|
+
return !route.startsWith('/_');
|
|
46
|
+
}
|
|
47
|
+
function getRouteType(route, prerenderRoutes, dynamicRoutes) {
|
|
48
|
+
if (route in dynamicRoutes || route.includes('[')) {
|
|
49
|
+
return 'dynamic';
|
|
50
|
+
}
|
|
51
|
+
const prerenderRoute = prerenderRoutes[route];
|
|
52
|
+
if (prerenderRoute &&
|
|
53
|
+
typeof prerenderRoute.initialRevalidateSeconds === 'number' &&
|
|
54
|
+
Number.isFinite(prerenderRoute.initialRevalidateSeconds) &&
|
|
55
|
+
prerenderRoute.initialRevalidateSeconds > 0) {
|
|
56
|
+
return 'isr';
|
|
57
|
+
}
|
|
58
|
+
return 'static';
|
|
59
|
+
}
|
|
60
|
+
function getPrerenderBlockedReason(route, prerenderRoutes, dynamicRoutes) {
|
|
61
|
+
if (route in prerenderRoutes) {
|
|
62
|
+
const revalidate = prerenderRoutes[route].initialRevalidateSeconds;
|
|
63
|
+
if (typeof revalidate === 'number' && Number.isFinite(revalidate) && revalidate > 0) {
|
|
64
|
+
return 'isr';
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
// In dynamicRoutes: has getStaticPaths but some or all paths render on-demand
|
|
69
|
+
if (route in dynamicRoutes) {
|
|
70
|
+
return 'dynamic-params';
|
|
71
|
+
}
|
|
72
|
+
// Not prerendered at all
|
|
73
|
+
if (route.includes('[')) {
|
|
74
|
+
return 'dynamic-params';
|
|
75
|
+
}
|
|
76
|
+
return 'server-side-props';
|
|
77
|
+
}
|
|
78
|
+
export function parseBuildDurationMs(output) {
|
|
79
|
+
const match = output.match(/(?:compiled(?:\s+\w+)*\s+in|done\s+in)\s+(\d+(?:\.\d+)?)\s*(ms|s|m)\b/i);
|
|
80
|
+
if (!match) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const value = Number(match[1]);
|
|
84
|
+
const unit = match[2].toLowerCase();
|
|
85
|
+
if (!Number.isFinite(value)) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
if (unit === 'ms') {
|
|
89
|
+
return Math.round(value);
|
|
90
|
+
}
|
|
91
|
+
if (unit === 's') {
|
|
92
|
+
return Math.round(value * 1000);
|
|
93
|
+
}
|
|
94
|
+
return Math.round(value * 60 * 1000);
|
|
95
|
+
}
|
|
96
|
+
export async function parseBuildStats(buildDirPath, buildOutputPath) {
|
|
97
|
+
const buildDir = resolve(buildDirPath);
|
|
98
|
+
const buildManifestPath = join(buildDir, 'build-manifest.json');
|
|
99
|
+
const prerenderManifestPath = join(buildDir, 'prerender-manifest.json');
|
|
100
|
+
const appBuildManifestPath = join(buildDir, 'app-build-manifest.json');
|
|
101
|
+
const buildManifest = await readJsonIfPresent(buildManifestPath);
|
|
102
|
+
if (!buildManifest) {
|
|
103
|
+
throw new Error(`Could not find build-manifest.json in ${buildDir}. Run next build first and point this tool at the .next directory.`);
|
|
104
|
+
}
|
|
105
|
+
const prerenderManifest = await readJsonIfPresent(prerenderManifestPath);
|
|
106
|
+
const appBuildManifest = await readJsonIfPresent(appBuildManifestPath);
|
|
107
|
+
const pagesRouteMap = parseRouteMap(buildManifest.pages);
|
|
108
|
+
const appRouteMap = parseRouteMap(appBuildManifest?.pages);
|
|
109
|
+
const prerenderRoutes = isRecord(prerenderManifest?.routes)
|
|
110
|
+
? prerenderManifest.routes
|
|
111
|
+
: {};
|
|
112
|
+
const dynamicRoutes = isRecord(prerenderManifest?.dynamicRoutes)
|
|
113
|
+
? prerenderManifest.dynamicRoutes
|
|
114
|
+
: {};
|
|
115
|
+
const allRoutes = new Map();
|
|
116
|
+
for (const [route, chunkPaths] of Object.entries(pagesRouteMap)) {
|
|
117
|
+
if (!shouldIncludeRoute(route)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
allRoutes.set(route, {
|
|
121
|
+
chunkPaths: Array.from(new Set(chunkPaths)),
|
|
122
|
+
isAppRoute: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
for (const [route, chunkPaths] of Object.entries(appRouteMap)) {
|
|
126
|
+
if (!shouldIncludeRoute(route)) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const existing = allRoutes.get(route);
|
|
130
|
+
allRoutes.set(route, {
|
|
131
|
+
chunkPaths: Array.from(new Set([...(existing?.chunkPaths ?? []), ...chunkPaths])),
|
|
132
|
+
isAppRoute: true,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const chunkRouteMap = new Map();
|
|
136
|
+
for (const [route, details] of allRoutes) {
|
|
137
|
+
for (const chunkPath of details.chunkPaths) {
|
|
138
|
+
if (!chunkRouteMap.has(chunkPath)) {
|
|
139
|
+
chunkRouteMap.set(chunkPath, new Set());
|
|
140
|
+
}
|
|
141
|
+
chunkRouteMap.get(chunkPath).add(route);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const chunkSizeEntries = await Promise.all(Array.from(chunkRouteMap.keys()).map(async (chunkPath) => {
|
|
145
|
+
const normalized = chunkPath.replace(/^\//, '');
|
|
146
|
+
const sizeBytes = await getFileSizeBytes(join(buildDir, normalized));
|
|
147
|
+
return [chunkPath, sizeBytes];
|
|
148
|
+
}));
|
|
149
|
+
const chunkSizeMap = new Map(chunkSizeEntries);
|
|
150
|
+
const chunks = Array.from(chunkRouteMap.entries())
|
|
151
|
+
.map(([chunkPath, routeSet]) => ({
|
|
152
|
+
chunkPath,
|
|
153
|
+
sizeBytes: chunkSizeMap.get(chunkPath) ?? 0,
|
|
154
|
+
routeCount: routeSet.size,
|
|
155
|
+
sharedByRoutes: Array.from(routeSet).sort(),
|
|
156
|
+
isShared: routeSet.size > 1,
|
|
157
|
+
}))
|
|
158
|
+
.sort((left, right) => right.sizeBytes - left.sizeBytes);
|
|
159
|
+
const routes = Array.from(allRoutes.entries())
|
|
160
|
+
.map(([route, details]) => {
|
|
161
|
+
const totalBytes = details.chunkPaths.reduce((sum, chunkPath) => sum + (chunkSizeMap.get(chunkPath) ?? 0), 0);
|
|
162
|
+
const sharedChunkBytes = details.chunkPaths.reduce((sum, chunkPath) => {
|
|
163
|
+
const routeCount = chunkRouteMap.get(chunkPath)?.size ?? 0;
|
|
164
|
+
if (routeCount > 1) {
|
|
165
|
+
return sum + (chunkSizeMap.get(chunkPath) ?? 0);
|
|
166
|
+
}
|
|
167
|
+
return sum;
|
|
168
|
+
}, 0);
|
|
169
|
+
return {
|
|
170
|
+
path: route,
|
|
171
|
+
type: getRouteType(route, prerenderRoutes, dynamicRoutes),
|
|
172
|
+
prerenderBlockedReason: getPrerenderBlockedReason(route, prerenderRoutes, dynamicRoutes),
|
|
173
|
+
chunkPaths: details.chunkPaths,
|
|
174
|
+
totalBytes,
|
|
175
|
+
initialLoadBytes: totalBytes,
|
|
176
|
+
sharedChunkBytes,
|
|
177
|
+
exclusiveChunkBytes: totalBytes - sharedChunkBytes,
|
|
178
|
+
isPrerendered: route in prerenderRoutes,
|
|
179
|
+
isAppRoute: details.isAppRoute,
|
|
180
|
+
};
|
|
181
|
+
})
|
|
182
|
+
.sort((left, right) => right.totalBytes - left.totalBytes);
|
|
183
|
+
let buildTimeMs = null;
|
|
184
|
+
let resolvedBuildOutputPath = null;
|
|
185
|
+
if (buildOutputPath) {
|
|
186
|
+
resolvedBuildOutputPath = resolve(buildOutputPath);
|
|
187
|
+
const output = await readFile(resolvedBuildOutputPath, 'utf-8');
|
|
188
|
+
buildTimeMs = parseBuildDurationMs(output);
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
id: randomUUID(),
|
|
192
|
+
buildDir,
|
|
193
|
+
buildOutputPath: resolvedBuildOutputPath,
|
|
194
|
+
routes,
|
|
195
|
+
chunks,
|
|
196
|
+
totalChunkBytes: chunks.reduce((sum, chunk) => sum + chunk.sizeBytes, 0),
|
|
197
|
+
sharedChunkBytes: chunks.reduce((sum, chunk) => sum + (chunk.isShared ? chunk.sizeBytes : 0), 0),
|
|
198
|
+
buildTimeMs,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=build-stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-stats.js","sourceRoot":"","sources":["../../src/parser/build-stats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export type RouteType = 'static' | 'dynamic' | 'isr';
|
|
2
|
+
export type PrerenderBlockedReason = 'isr' | 'dynamic-params' | 'server-side-props' | null;
|
|
3
|
+
export interface BuildRoute {
|
|
4
|
+
path: string;
|
|
5
|
+
type: RouteType;
|
|
6
|
+
prerenderBlockedReason: PrerenderBlockedReason;
|
|
7
|
+
chunkPaths: string[];
|
|
8
|
+
totalBytes: number;
|
|
9
|
+
initialLoadBytes: number;
|
|
10
|
+
sharedChunkBytes: number;
|
|
11
|
+
exclusiveChunkBytes: number;
|
|
12
|
+
isPrerendered: boolean;
|
|
13
|
+
isAppRoute: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface BuildChunk {
|
|
16
|
+
chunkPath: string;
|
|
17
|
+
sizeBytes: number;
|
|
18
|
+
routeCount: number;
|
|
19
|
+
sharedByRoutes: string[];
|
|
20
|
+
isShared: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface ParsedBuildStats {
|
|
23
|
+
id: string;
|
|
24
|
+
buildDir: string;
|
|
25
|
+
buildOutputPath: string | null;
|
|
26
|
+
routes: BuildRoute[];
|
|
27
|
+
chunks: BuildChunk[];
|
|
28
|
+
totalChunkBytes: number;
|
|
29
|
+
sharedChunkBytes: number;
|
|
30
|
+
buildTimeMs: number | null;
|
|
31
|
+
}
|
|
32
|
+
export interface RouteSummaryEntry {
|
|
33
|
+
path: string;
|
|
34
|
+
type: RouteType;
|
|
35
|
+
prerenderBlockedReason: PrerenderBlockedReason;
|
|
36
|
+
totalBytes: number;
|
|
37
|
+
initialLoadBytes: number;
|
|
38
|
+
sharedChunkBytes: number;
|
|
39
|
+
exclusiveChunkBytes: number;
|
|
40
|
+
sharedRatio: number;
|
|
41
|
+
chunkCount: number;
|
|
42
|
+
isPrerendered: boolean;
|
|
43
|
+
isAppRoute: boolean;
|
|
44
|
+
}
|
|
45
|
+
export interface SharedChunkSummaryEntry {
|
|
46
|
+
chunkPath: string;
|
|
47
|
+
sizeBytes: number;
|
|
48
|
+
routeCount: number;
|
|
49
|
+
sharedByRoutes: string[];
|
|
50
|
+
shareOfAllChunkBytes: number;
|
|
51
|
+
}
|
|
52
|
+
export interface RouteDeltaEntry {
|
|
53
|
+
path: string;
|
|
54
|
+
baselineBytes: number;
|
|
55
|
+
currentBytes: number;
|
|
56
|
+
deltaBytes: number;
|
|
57
|
+
deltaRatio: number | null;
|
|
58
|
+
}
|
|
59
|
+
export interface ChunkDeltaEntry {
|
|
60
|
+
chunkPath: string;
|
|
61
|
+
baselineBytes: number;
|
|
62
|
+
currentBytes: number;
|
|
63
|
+
deltaBytes: number;
|
|
64
|
+
deltaRatio: number | null;
|
|
65
|
+
baselineRouteCount: number;
|
|
66
|
+
currentRouteCount: number;
|
|
67
|
+
}
|
|
68
|
+
export interface BuildComparison {
|
|
69
|
+
baselineBuildId: string;
|
|
70
|
+
currentBuildId: string;
|
|
71
|
+
baselineTotalChunkBytes: number;
|
|
72
|
+
currentTotalChunkBytes: number;
|
|
73
|
+
totalChunkDeltaBytes: number;
|
|
74
|
+
totalChunkDeltaRatio: number | null;
|
|
75
|
+
baselineSharedChunkBytes: number;
|
|
76
|
+
currentSharedChunkBytes: number;
|
|
77
|
+
sharedChunkDeltaBytes: number;
|
|
78
|
+
sharedChunkDeltaRatio: number | null;
|
|
79
|
+
baselineBuildTimeMs: number | null;
|
|
80
|
+
currentBuildTimeMs: number | null;
|
|
81
|
+
buildTimeDeltaMs: number | null;
|
|
82
|
+
buildTimeDeltaRatio: number | null;
|
|
83
|
+
routeDeltas: RouteDeltaEntry[];
|
|
84
|
+
chunkDeltas: ChunkDeltaEntry[];
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAC;AAErD,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,IAAI,CAAC;AAE3F,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,sBAAsB,EAAE,sBAAsB,CAAC;IAC/C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,sBAAsB,EAAE,sBAAsB,CAAC;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,wBAAwB,EAAE,MAAM,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;IAChC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,WAAW,EAAE,eAAe,EAAE,CAAC;IAC/B,WAAW,EAAE,eAAe,EAAE,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/parser/types.ts"],"names":[],"mappings":""}
|
package/dist/store.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ParsedBuildStats } from './parser/types.js';
|
|
2
|
+
export declare function storeBuildStats(build: ParsedBuildStats): void;
|
|
3
|
+
export declare function getBuildStats(id: string): ParsedBuildStats | undefined;
|
|
4
|
+
export declare function listBuildStats(): Array<{
|
|
5
|
+
id: string;
|
|
6
|
+
buildDir: string;
|
|
7
|
+
routeCount: number;
|
|
8
|
+
chunkCount: number;
|
|
9
|
+
totalChunkBytes: number;
|
|
10
|
+
buildTimeMs: number | null;
|
|
11
|
+
}>;
|
|
12
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAI1D,wBAAgB,eAAe,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAE7D;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAEtE;AAED,wBAAgB,cAAc,IAAI,KAAK,CAAC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC,CASD"}
|
package/dist/store.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const builds = new Map();
|
|
2
|
+
export function storeBuildStats(build) {
|
|
3
|
+
builds.set(build.id, build);
|
|
4
|
+
}
|
|
5
|
+
export function getBuildStats(id) {
|
|
6
|
+
return builds.get(id);
|
|
7
|
+
}
|
|
8
|
+
export function listBuildStats() {
|
|
9
|
+
return Array.from(builds.values()).map(build => ({
|
|
10
|
+
id: build.id,
|
|
11
|
+
buildDir: build.buildDir,
|
|
12
|
+
routeCount: build.routes.length,
|
|
13
|
+
chunkCount: build.chunks.length,
|
|
14
|
+
totalChunkBytes: build.totalChunkBytes,
|
|
15
|
+
buildTimeMs: build.buildTimeMs,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;AAEnD,MAAM,UAAU,eAAe,CAAC,KAAuB;IACrD,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,EAAU;IACtC,OAAO,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,cAAc;IAQ5B,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC/C,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC/B,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC/B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,WAAW,EAAE,KAAK,CAAC,WAAW;KAC/B,CAAC,CAAC,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-builds.d.ts","sourceRoot":"","sources":["../../src/tools/compare-builds.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAWpE,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkF7D"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatBytes, formatMs, formatPct } from '../format.js';
|
|
3
|
+
import { compareBuilds as compareLoadedBuilds } from '../parser/analysis.js';
|
|
4
|
+
import { getBuildStats, listBuildStats } from '../store.js';
|
|
5
|
+
function formatNullableRatio(value) {
|
|
6
|
+
return value === null ? null : formatPct(value);
|
|
7
|
+
}
|
|
8
|
+
export function registerCompareBuilds(server) {
|
|
9
|
+
server.registerTool('compare_builds', {
|
|
10
|
+
title: 'Compare Builds',
|
|
11
|
+
description: 'Compare two loaded Next.js builds and show which routes and chunks grew or shrank the most.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
baselineBuildId: z.string().optional().describe('Baseline build ID from load_build_stats. Omit to list loaded builds.'),
|
|
14
|
+
currentBuildId: z.string().optional().describe('Current build ID from load_build_stats. Omit to list loaded builds.'),
|
|
15
|
+
limit: z.number().int().positive().max(25).optional().describe('How many route and chunk deltas to include. Defaults to 10.'),
|
|
16
|
+
},
|
|
17
|
+
}, async ({ baselineBuildId, currentBuildId, limit }) => {
|
|
18
|
+
if (!baselineBuildId || !currentBuildId) {
|
|
19
|
+
return {
|
|
20
|
+
content: [{
|
|
21
|
+
type: 'text',
|
|
22
|
+
text: JSON.stringify({
|
|
23
|
+
builds: listBuildStats().map(build => ({
|
|
24
|
+
id: build.id,
|
|
25
|
+
buildDir: build.buildDir,
|
|
26
|
+
routeCount: build.routeCount,
|
|
27
|
+
chunkCount: build.chunkCount,
|
|
28
|
+
totalChunkBytes: build.totalChunkBytes,
|
|
29
|
+
totalChunkBytesText: formatBytes(build.totalChunkBytes),
|
|
30
|
+
buildTimeText: formatMs(build.buildTimeMs),
|
|
31
|
+
})),
|
|
32
|
+
}, null, 2),
|
|
33
|
+
}],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const baseline = getBuildStats(baselineBuildId);
|
|
37
|
+
if (!baseline) {
|
|
38
|
+
throw new Error(`Build "${baselineBuildId}" not found. Call compare_builds without IDs to list loaded builds.`);
|
|
39
|
+
}
|
|
40
|
+
const current = getBuildStats(currentBuildId);
|
|
41
|
+
if (!current) {
|
|
42
|
+
throw new Error(`Build "${currentBuildId}" not found. Call compare_builds without IDs to list loaded builds.`);
|
|
43
|
+
}
|
|
44
|
+
const comparison = compareLoadedBuilds(baseline, current, limit ?? 10);
|
|
45
|
+
return {
|
|
46
|
+
content: [{
|
|
47
|
+
type: 'text',
|
|
48
|
+
text: JSON.stringify({
|
|
49
|
+
...comparison,
|
|
50
|
+
baselineTotalChunkBytesText: formatBytes(comparison.baselineTotalChunkBytes),
|
|
51
|
+
currentTotalChunkBytesText: formatBytes(comparison.currentTotalChunkBytes),
|
|
52
|
+
totalChunkDeltaBytesText: formatBytes(Math.abs(comparison.totalChunkDeltaBytes)),
|
|
53
|
+
totalChunkDeltaDirection: comparison.totalChunkDeltaBytes >= 0 ? 'growth' : 'shrink',
|
|
54
|
+
totalChunkDeltaRatioText: formatNullableRatio(comparison.totalChunkDeltaRatio),
|
|
55
|
+
baselineSharedChunkBytesText: formatBytes(comparison.baselineSharedChunkBytes),
|
|
56
|
+
currentSharedChunkBytesText: formatBytes(comparison.currentSharedChunkBytes),
|
|
57
|
+
sharedChunkDeltaBytesText: formatBytes(Math.abs(comparison.sharedChunkDeltaBytes)),
|
|
58
|
+
sharedChunkDeltaDirection: comparison.sharedChunkDeltaBytes >= 0 ? 'growth' : 'shrink',
|
|
59
|
+
sharedChunkDeltaRatioText: formatNullableRatio(comparison.sharedChunkDeltaRatio),
|
|
60
|
+
baselineBuildTimeText: formatMs(comparison.baselineBuildTimeMs),
|
|
61
|
+
currentBuildTimeText: formatMs(comparison.currentBuildTimeMs),
|
|
62
|
+
buildTimeDeltaText: comparison.buildTimeDeltaMs === null ? null : formatMs(Math.abs(comparison.buildTimeDeltaMs)),
|
|
63
|
+
buildTimeDeltaDirection: comparison.buildTimeDeltaMs === null ? null : comparison.buildTimeDeltaMs >= 0 ? 'slower' : 'faster',
|
|
64
|
+
buildTimeDeltaRatioText: formatNullableRatio(comparison.buildTimeDeltaRatio),
|
|
65
|
+
routeDeltas: comparison.routeDeltas.map(route => ({
|
|
66
|
+
...route,
|
|
67
|
+
baselineBytesText: formatBytes(route.baselineBytes),
|
|
68
|
+
currentBytesText: formatBytes(route.currentBytes),
|
|
69
|
+
deltaBytesText: formatBytes(Math.abs(route.deltaBytes)),
|
|
70
|
+
deltaDirection: route.deltaBytes >= 0 ? 'growth' : 'shrink',
|
|
71
|
+
deltaRatioText: formatNullableRatio(route.deltaRatio),
|
|
72
|
+
})),
|
|
73
|
+
chunkDeltas: comparison.chunkDeltas.map(chunk => ({
|
|
74
|
+
...chunk,
|
|
75
|
+
baselineBytesText: formatBytes(chunk.baselineBytes),
|
|
76
|
+
currentBytesText: formatBytes(chunk.currentBytes),
|
|
77
|
+
deltaBytesText: formatBytes(Math.abs(chunk.deltaBytes)),
|
|
78
|
+
deltaDirection: chunk.deltaBytes >= 0 ? 'growth' : 'shrink',
|
|
79
|
+
deltaRatioText: formatNullableRatio(chunk.deltaRatio),
|
|
80
|
+
})),
|
|
81
|
+
}, null, 2),
|
|
82
|
+
}],
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=compare-builds.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare-builds.js","sourceRoot":"","sources":["../../src/tools/compare-builds.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,aAAa,IAAI,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,SAAS,mBAAmB,CAAC,KAAoB;IAC/C,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,MAAiB;IACrD,MAAM,CAAC,YAAY,CAAC,gBAAgB,EAAE;QACpC,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,6FAA6F;QAC1G,WAAW,EAAE;YACX,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sEAAsE,CAAC;YACvH,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qEAAqE,CAAC;YACrH,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6DAA6D,CAAC;SAC9H;KACF,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,cAAc,EAAE,KAAK,EAAE,EAAE,EAAE;QACtD,IAAI,CAAC,eAAe,IAAI,CAAC,cAAc,EAAE,CAAC;YACxC,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,MAAM,EAAE,cAAc,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gCACrC,EAAE,EAAE,KAAK,CAAC,EAAE;gCACZ,QAAQ,EAAE,KAAK,CAAC,QAAQ;gCACxB,UAAU,EAAE,KAAK,CAAC,UAAU;gCAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;gCAC5B,eAAe,EAAE,KAAK,CAAC,eAAe;gCACtC,mBAAmB,EAAE,WAAW,CAAC,KAAK,CAAC,eAAe,CAAC;gCACvD,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;6BAC3C,CAAC,CAAC;yBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;qBACZ,CAAC;aACH,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,eAAe,CAAC,CAAC;QAChD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,UAAU,eAAe,qEAAqE,CAAC,CAAC;QAClH,CAAC;QAED,MAAM,OAAO,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,UAAU,cAAc,qEAAqE,CAAC,CAAC;QACjH,CAAC;QAED,MAAM,UAAU,GAAG,mBAAmB,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACvE,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,GAAG,UAAU;wBACb,2BAA2B,EAAE,WAAW,CAAC,UAAU,CAAC,uBAAuB,CAAC;wBAC5E,0BAA0B,EAAE,WAAW,CAAC,UAAU,CAAC,sBAAsB,CAAC;wBAC1E,wBAAwB,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,oBAAoB,CAAC,CAAC;wBAChF,wBAAwB,EAAE,UAAU,CAAC,oBAAoB,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;wBACpF,wBAAwB,EAAE,mBAAmB,CAAC,UAAU,CAAC,oBAAoB,CAAC;wBAC9E,4BAA4B,EAAE,WAAW,CAAC,UAAU,CAAC,wBAAwB,CAAC;wBAC9E,2BAA2B,EAAE,WAAW,CAAC,UAAU,CAAC,uBAAuB,CAAC;wBAC5E,yBAAyB,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC;wBAClF,yBAAyB,EAAE,UAAU,CAAC,qBAAqB,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;wBACtF,yBAAyB,EAAE,mBAAmB,CAAC,UAAU,CAAC,qBAAqB,CAAC;wBAChF,qBAAqB,EAAE,QAAQ,CAAC,UAAU,CAAC,mBAAmB,CAAC;wBAC/D,oBAAoB,EAAE,QAAQ,CAAC,UAAU,CAAC,kBAAkB,CAAC;wBAC7D,kBAAkB,EAChB,UAAU,CAAC,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;wBAC/F,uBAAuB,EACrB,UAAU,CAAC,gBAAgB,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;wBACtG,uBAAuB,EAAE,mBAAmB,CAAC,UAAU,CAAC,mBAAmB,CAAC;wBAC5E,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BAChD,GAAG,KAAK;4BACR,iBAAiB,EAAE,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC;4BACnD,gBAAgB,EAAE,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC;4BACjD,cAAc,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;4BACvD,cAAc,EAAE,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;4BAC3D,cAAc,EAAE,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC;yBACtD,CAAC,CAAC;wBACH,WAAW,EAAE,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BAChD,GAAG,KAAK;4BACR,iBAAiB,EAAE,WAAW,CAAC,KAAK,CAAC,aAAa,CAAC;4BACnD,gBAAgB,EAAE,WAAW,CAAC,KAAK,CAAC,YAAY,CAAC;4BACjD,cAAc,EAAE,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;4BACvD,cAAc,EAAE,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;4BAC3D,cAAc,EAAE,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC;yBACtD,CAAC,CAAC;qBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-largest-routes.d.ts","sourceRoot":"","sources":["../../src/tools/get-largest-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA+ChE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatBytes, formatMs, formatPct } from '../format.js';
|
|
3
|
+
import { getLargestRoutes } from '../parser/analysis.js';
|
|
4
|
+
import { getBuildStats, listBuildStats } from '../store.js';
|
|
5
|
+
export function registerGetLargestRoutes(server) {
|
|
6
|
+
server.registerTool('get_largest_routes', {
|
|
7
|
+
title: 'Get Largest Routes',
|
|
8
|
+
description: 'Rank the heaviest user-facing routes in a loaded Next.js build by emitted chunk bytes.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
buildId: z.string().optional().describe('Build ID returned from load_build_stats. Omit to list loaded builds.'),
|
|
11
|
+
limit: z.number().int().positive().max(25).optional().describe('How many routes to include. Defaults to 10.'),
|
|
12
|
+
},
|
|
13
|
+
}, async ({ buildId, limit }) => {
|
|
14
|
+
if (!buildId) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{
|
|
17
|
+
type: 'text',
|
|
18
|
+
text: JSON.stringify({
|
|
19
|
+
builds: listBuildStats().map(build => ({
|
|
20
|
+
...build,
|
|
21
|
+
totalChunkBytesText: formatBytes(build.totalChunkBytes),
|
|
22
|
+
buildTimeText: formatMs(build.buildTimeMs),
|
|
23
|
+
})),
|
|
24
|
+
}, null, 2),
|
|
25
|
+
}],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const build = getBuildStats(buildId);
|
|
29
|
+
if (!build) {
|
|
30
|
+
throw new Error(`Build "${buildId}" not found. Call get_largest_routes without a buildId to list all loaded builds.`);
|
|
31
|
+
}
|
|
32
|
+
const routes = getLargestRoutes(build, limit ?? 10);
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: JSON.stringify({
|
|
37
|
+
buildId: build.id,
|
|
38
|
+
routes: routes.map(route => ({
|
|
39
|
+
...route,
|
|
40
|
+
totalBytesText: formatBytes(route.totalBytes),
|
|
41
|
+
initialLoadBytesText: formatBytes(route.initialLoadBytes),
|
|
42
|
+
sharedChunkBytesText: formatBytes(route.sharedChunkBytes),
|
|
43
|
+
exclusiveChunkBytesText: formatBytes(route.exclusiveChunkBytes),
|
|
44
|
+
sharedRatioText: formatPct(route.sharedRatio),
|
|
45
|
+
})),
|
|
46
|
+
}, null, 2),
|
|
47
|
+
}],
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=get-largest-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-largest-routes.js","sourceRoot":"","sources":["../../src/tools/get-largest-routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,UAAU,wBAAwB,CAAC,MAAiB;IACxD,MAAM,CAAC,YAAY,CAAC,oBAAoB,EAAE;QACxC,KAAK,EAAE,oBAAoB;QAC3B,WAAW,EAAE,wFAAwF;QACrG,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sEAAsE,CAAC;YAC/G,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,6CAA6C,CAAC;SAC9G;KACF,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,MAAM,EAAE,cAAc,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gCACrC,GAAG,KAAK;gCACR,mBAAmB,EAAE,WAAW,CAAC,KAAK,CAAC,eAAe,CAAC;gCACvD,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;6BAC3C,CAAC,CAAC;yBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;qBACZ,CAAC;aACH,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,mFAAmF,CAAC,CAAC;QACxH,CAAC;QAED,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACpD,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,OAAO,EAAE,KAAK,CAAC,EAAE;wBACjB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BAC3B,GAAG,KAAK;4BACR,cAAc,EAAE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC;4BAC7C,oBAAoB,EAAE,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC;4BACzD,oBAAoB,EAAE,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC;4BACzD,uBAAuB,EAAE,WAAW,CAAC,KAAK,CAAC,mBAAmB,CAAC;4BAC/D,eAAe,EAAE,SAAS,CAAC,KAAK,CAAC,WAAW,CAAC;yBAC9C,CAAC,CAAC;qBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-shared-chunks.d.ts","sourceRoot":"","sources":["../../src/tools/get-shared-chunks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAOpE,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4C/D"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatBytes, formatMs, formatPct } from '../format.js';
|
|
3
|
+
import { getSharedChunks } from '../parser/analysis.js';
|
|
4
|
+
import { getBuildStats, listBuildStats } from '../store.js';
|
|
5
|
+
export function registerGetSharedChunks(server) {
|
|
6
|
+
server.registerTool('get_shared_chunks', {
|
|
7
|
+
title: 'Get Shared Chunks',
|
|
8
|
+
description: 'Rank the heaviest shared chunks in a loaded Next.js build and show which routes depend on them.',
|
|
9
|
+
inputSchema: {
|
|
10
|
+
buildId: z.string().optional().describe('Build ID returned from load_build_stats. Omit to list loaded builds.'),
|
|
11
|
+
limit: z.number().int().positive().max(25).optional().describe('How many shared chunks to include. Defaults to 10.'),
|
|
12
|
+
},
|
|
13
|
+
}, async ({ buildId, limit }) => {
|
|
14
|
+
if (!buildId) {
|
|
15
|
+
return {
|
|
16
|
+
content: [{
|
|
17
|
+
type: 'text',
|
|
18
|
+
text: JSON.stringify({
|
|
19
|
+
builds: listBuildStats().map(build => ({
|
|
20
|
+
...build,
|
|
21
|
+
totalChunkBytesText: formatBytes(build.totalChunkBytes),
|
|
22
|
+
buildTimeText: formatMs(build.buildTimeMs),
|
|
23
|
+
})),
|
|
24
|
+
}, null, 2),
|
|
25
|
+
}],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const build = getBuildStats(buildId);
|
|
29
|
+
if (!build) {
|
|
30
|
+
throw new Error(`Build "${buildId}" not found. Call get_shared_chunks without a buildId to list all loaded builds.`);
|
|
31
|
+
}
|
|
32
|
+
const chunks = getSharedChunks(build, limit ?? 10);
|
|
33
|
+
return {
|
|
34
|
+
content: [{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: JSON.stringify({
|
|
37
|
+
buildId: build.id,
|
|
38
|
+
chunks: chunks.map(chunk => ({
|
|
39
|
+
...chunk,
|
|
40
|
+
sizeBytesText: formatBytes(chunk.sizeBytes),
|
|
41
|
+
shareOfAllChunkBytesText: formatPct(chunk.shareOfAllChunkBytes),
|
|
42
|
+
})),
|
|
43
|
+
}, null, 2),
|
|
44
|
+
}],
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=get-shared-chunks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-shared-chunks.js","sourceRoot":"","sources":["../../src/tools/get-shared-chunks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE5D,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,YAAY,CAAC,mBAAmB,EAAE;QACvC,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,iGAAiG;QAC9G,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sEAAsE,CAAC;YAC/G,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,oDAAoD,CAAC;SACrH;KACF,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE;QAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE,CAAC;wBACR,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;4BACnB,MAAM,EAAE,cAAc,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gCACrC,GAAG,KAAK;gCACR,mBAAmB,EAAE,WAAW,CAAC,KAAK,CAAC,eAAe,CAAC;gCACvD,aAAa,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC;6BAC3C,CAAC,CAAC;yBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;qBACZ,CAAC;aACH,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,UAAU,OAAO,kFAAkF,CAAC,CAAC;QACvH,CAAC;QAED,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QACnD,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,OAAO,EAAE,KAAK,CAAC,EAAE;wBACjB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;4BAC3B,GAAG,KAAK;4BACR,aAAa,EAAE,WAAW,CAAC,KAAK,CAAC,SAAS,CAAC;4BAC3C,wBAAwB,EAAE,SAAS,CAAC,KAAK,CAAC,oBAAoB,CAAC;yBAChE,CAAC,CAAC;qBACJ,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-build-stats.d.ts","sourceRoot":"","sources":["../../src/tools/load-build-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQpE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAyB9D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { formatBytes, formatMs } from '../format.js';
|
|
3
|
+
import { getBuildSummary } from '../parser/analysis.js';
|
|
4
|
+
import { parseBuildStats } from '../parser/build-stats.js';
|
|
5
|
+
import { storeBuildStats } from '../store.js';
|
|
6
|
+
export function registerLoadBuildStats(server) {
|
|
7
|
+
server.registerTool('load_build_stats', {
|
|
8
|
+
title: 'Load Build Stats',
|
|
9
|
+
description: 'Parse a Next.js .next directory and load route and chunk footprint data for later analysis.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
buildDir: z.string().describe('Absolute or relative path to the Next.js .next build directory'),
|
|
12
|
+
buildOutputPath: z.string().optional().describe('Optional path to captured next build terminal output for deriving build duration'),
|
|
13
|
+
},
|
|
14
|
+
}, async ({ buildDir, buildOutputPath }) => {
|
|
15
|
+
const build = await parseBuildStats(buildDir, buildOutputPath);
|
|
16
|
+
storeBuildStats(build);
|
|
17
|
+
const summary = getBuildSummary(build);
|
|
18
|
+
return {
|
|
19
|
+
content: [{
|
|
20
|
+
type: 'text',
|
|
21
|
+
text: JSON.stringify({
|
|
22
|
+
...summary,
|
|
23
|
+
totalChunkBytesText: formatBytes(summary.totalChunkBytes),
|
|
24
|
+
sharedChunkBytesText: formatBytes(summary.sharedChunkBytes),
|
|
25
|
+
buildTimeText: formatMs(summary.buildTimeMs),
|
|
26
|
+
}, null, 2),
|
|
27
|
+
}],
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=load-build-stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-build-stats.js","sourceRoot":"","sources":["../../src/tools/load-build-stats.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,UAAU,sBAAsB,CAAC,MAAiB;IACtD,MAAM,CAAC,YAAY,CAAC,kBAAkB,EAAE;QACtC,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE,6FAA6F;QAC1G,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,gEAAgE,CAAC;YAC/F,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kFAAkF,CAAC;SACpI;KACF,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,EAAE;QACzC,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QAC/D,eAAe,CAAC,KAAK,CAAC,CAAC;QAEvB,MAAM,OAAO,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC;QACvC,OAAO;YACL,OAAO,EAAE,CAAC;oBACR,IAAI,EAAE,MAAe;oBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,GAAG,OAAO;wBACV,mBAAmB,EAAE,WAAW,CAAC,OAAO,CAAC,eAAe,CAAC;wBACzD,oBAAoB,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;wBAC3D,aAAa,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,CAAC;qBAC7C,EAAE,IAAI,EAAE,CAAC,CAAC;iBACZ,CAAC;SACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@perfonext/build-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for analyzing Next.js build artifacts, route bundle footprint, and shared chunks",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"perfonext-build-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
23
|
+
"dev": "tsc --watch",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"inspector": "npx @modelcontextprotocol/inspector node dist/index.js",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"github-copilot",
|
|
31
|
+
"nextjs",
|
|
32
|
+
"build",
|
|
33
|
+
"bundle",
|
|
34
|
+
"performance",
|
|
35
|
+
"route-size",
|
|
36
|
+
"chunk-analysis"
|
|
37
|
+
],
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/souvikxcoder/perfonext-build-mcp.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/souvikxcoder/perfonext-build-mcp/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/souvikxcoder/perfonext-build-mcp#readme",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
49
|
+
"zod": "^4.4.1"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^25.6.0",
|
|
53
|
+
"typescript": "^6.0.3",
|
|
54
|
+
"vitest": "^3.2.4"
|
|
55
|
+
}
|
|
56
|
+
}
|