bundle-cop-vercel-plugin 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/dist/index.js ADDED
@@ -0,0 +1,616 @@
1
+ // src/index.ts
2
+ import { join as join5 } from "path";
3
+
4
+ // src/budgets.ts
5
+ import { readFileSync, existsSync } from "fs";
6
+ import { join } from "path";
7
+ var SIZE_RE = /^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i;
8
+ function parseSize(input) {
9
+ const match = input.trim().match(SIZE_RE);
10
+ if (!match) {
11
+ throw new Error(`Invalid size: ${input}`);
12
+ }
13
+ const value = Number(match[1]);
14
+ const unit = (match[2] || "b").toLowerCase();
15
+ switch (unit) {
16
+ case "b":
17
+ return value;
18
+ case "kb":
19
+ return Math.round(value * 1024);
20
+ case "mb":
21
+ return Math.round(value * 1024 * 1024);
22
+ case "gb":
23
+ return Math.round(value * 1024 * 1024 * 1024);
24
+ default:
25
+ throw new Error(`Invalid size unit: ${unit}`);
26
+ }
27
+ }
28
+ function loadConfig(projectDir) {
29
+ const path = join(projectDir, "bundle-cop.config.json");
30
+ if (!existsSync(path)) {
31
+ return {
32
+ budgets: [],
33
+ ignore: [],
34
+ githubComment: true,
35
+ suggestions: true
36
+ };
37
+ }
38
+ try {
39
+ return JSON.parse(readFileSync(path, "utf8"));
40
+ } catch (error) {
41
+ console.warn("[bundle-cop] Failed to parse bundle-cop.config.json:", error);
42
+ return { budgets: [], suggestions: true, githubComment: true };
43
+ }
44
+ }
45
+ function checkBudgets(report, config) {
46
+ const budgets = config.budgets ?? [];
47
+ return budgets.map((rule) => {
48
+ const maxBytes = parseSize(rule.maxSize);
49
+ const matching = report.chunks.filter((chunk) => {
50
+ if (rule.path === "/*" || rule.path === "/") return true;
51
+ const route = chunk.route || "";
52
+ return route === rule.path || route.startsWith(rule.path.replace(/\/$/, ""));
53
+ });
54
+ const actualBytes = matching.length > 0 ? matching.reduce((sum, c) => sum + c.size, 0) : report.totalBytes;
55
+ return {
56
+ path: rule.path,
57
+ maxBytes,
58
+ actualBytes,
59
+ enforce: rule.enforce,
60
+ exceeded: actualBytes > maxBytes
61
+ };
62
+ });
63
+ }
64
+ function enforceBudgets(results) {
65
+ const errors = results.filter((r) => r.exceeded && r.enforce === "error");
66
+ for (const warn of results.filter((r) => r.exceeded && r.enforce === "warn")) {
67
+ console.warn(
68
+ `[bundle-cop] Budget warn ${warn.path}: ${formatBytes(warn.actualBytes)} > ${formatBytes(warn.maxBytes)}`
69
+ );
70
+ }
71
+ if (errors.length === 0) return;
72
+ const details = errors.map(
73
+ (e) => `${e.path}: ${formatBytes(e.actualBytes)} exceeds ${formatBytes(e.maxBytes)}`
74
+ ).join("; ");
75
+ throw new Error(`[bundle-cop] Budget exceeded \u2014 ${details}`);
76
+ }
77
+ function formatBytes(bytes) {
78
+ if (bytes < 1024) return `${bytes} B`;
79
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`;
80
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
81
+ }
82
+
83
+ // src/find-next-dir.ts
84
+ import { existsSync as existsSync2, readdirSync, statSync } from "fs";
85
+ import { join as join2 } from "path";
86
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
87
+ "node_modules",
88
+ ".git",
89
+ "dist",
90
+ "coverage",
91
+ ".turbo",
92
+ ".vercel"
93
+ ]);
94
+ function findNextDir(startDir, preferredDir) {
95
+ if (preferredDir && existsSync2(preferredDir)) {
96
+ return preferredDir;
97
+ }
98
+ const direct = join2(startDir, ".next");
99
+ if (existsSync2(direct)) {
100
+ return direct;
101
+ }
102
+ let current = startDir;
103
+ for (let i = 0; i < 5; i++) {
104
+ const found = walkForNext(current, 4);
105
+ if (found) return found;
106
+ const parent = join2(current, "..");
107
+ if (parent === current) break;
108
+ current = parent;
109
+ }
110
+ return null;
111
+ }
112
+ function walkForNext(dir, depth) {
113
+ if (depth < 0) return null;
114
+ let entries;
115
+ try {
116
+ entries = readdirSync(dir);
117
+ } catch {
118
+ return null;
119
+ }
120
+ const nested = join2(dir, ".next");
121
+ if (existsSync2(nested)) return nested;
122
+ for (const entry of entries) {
123
+ if (SKIP_DIRS.has(entry) || entry.startsWith(".")) continue;
124
+ const full = join2(dir, entry);
125
+ try {
126
+ if (!statSync(full).isDirectory()) continue;
127
+ } catch {
128
+ continue;
129
+ }
130
+ const found = walkForNext(full, depth - 1);
131
+ if (found) return found;
132
+ }
133
+ return null;
134
+ }
135
+
136
+ // src/parser.ts
137
+ import {
138
+ existsSync as existsSync3,
139
+ readdirSync as readdirSync2,
140
+ readFileSync as readFileSync2,
141
+ statSync as statSync2
142
+ } from "fs";
143
+ import { join as join3, relative } from "path";
144
+
145
+ // src/attribution.ts
146
+ function isUserFile(filePath) {
147
+ const normalized = filePath.replace(/\\/g, "/");
148
+ if (!normalized) return false;
149
+ if (normalized.includes("node_modules/")) return false;
150
+ if (normalized.includes("/.next/")) return false;
151
+ if (normalized.startsWith(".next/")) return false;
152
+ return true;
153
+ }
154
+ function toPath(entry) {
155
+ if (typeof entry === "string") return entry;
156
+ return entry.path || entry.name || "";
157
+ }
158
+ function toName(entry) {
159
+ if (typeof entry === "string") return entry;
160
+ return entry.name || entry.path || "";
161
+ }
162
+ function findCulprit(module) {
163
+ const raw = module.issuerPath ?? [];
164
+ const chain = raw.map(toName).filter(Boolean);
165
+ for (let i = raw.length - 1; i >= 0; i--) {
166
+ const path = toPath(raw[i]);
167
+ if (isUserFile(path)) {
168
+ return { culpritFile: path, chain };
169
+ }
170
+ }
171
+ if (isUserFile(module.name)) {
172
+ return { culpritFile: module.name, chain };
173
+ }
174
+ return { culpritFile: "unknown", chain };
175
+ }
176
+ var ALTERNATIVES = {
177
+ moment: "date-fns (save ~270kb) -> npm i date-fns",
178
+ lodash: "lodash-es + tree-shake or individual imports",
179
+ "react-icons": "import directly: react-icons/fa/FaIcon",
180
+ "date-fns": "use subpath imports: date-fns/format"
181
+ };
182
+ function suggestAlternative(moduleName) {
183
+ const normalized = moduleName.replace(/\\/g, "/").toLowerCase();
184
+ for (const [key, suggestion] of Object.entries(ALTERNATIVES)) {
185
+ if (normalized === key || normalized.includes(`node_modules/${key}/`) || normalized.startsWith(`${key}/`) || normalized.includes(`/${key}/`)) {
186
+ return suggestion;
187
+ }
188
+ }
189
+ return null;
190
+ }
191
+
192
+ // src/parser.ts
193
+ var KNOWN_HEAVY = [
194
+ "moment",
195
+ "lodash",
196
+ "lodash-es",
197
+ "date-fns",
198
+ "react-icons",
199
+ "rxjs",
200
+ "antd",
201
+ "chart.js",
202
+ "recharts"
203
+ ];
204
+ async function parseStats(options) {
205
+ const {
206
+ nextDir,
207
+ projectDir,
208
+ commitSha = null,
209
+ withSuggestions = true
210
+ } = options;
211
+ const fromDiagnostics = tryParseDiagnostics(nextDir, withSuggestions);
212
+ if (fromDiagnostics && fromDiagnostics.modules.length > 0) {
213
+ return finalize(fromDiagnostics, commitSha);
214
+ }
215
+ const fromAnalyze = tryParseAnalyzeClient(nextDir, withSuggestions);
216
+ if (fromAnalyze && fromAnalyze.modules.length > 0) {
217
+ return finalize(fromAnalyze, commitSha);
218
+ }
219
+ const fromChunks = parseChunkFallback(nextDir);
220
+ const fromRouteStats = parseRouteBundleStats(nextDir);
221
+ const chunks = fromRouteStats.chunks.length > 0 ? fromRouteStats.chunks : fromChunks.chunks;
222
+ const totalBytes = fromRouteStats.totalBytes || fromChunks.totalBytes || chunks.reduce((s, c) => s + c.size, 0);
223
+ const modules = enrichWithKnownPackages({
224
+ nextDir,
225
+ projectDir: projectDir || join3(nextDir, ".."),
226
+ withSuggestions
227
+ });
228
+ return finalize(
229
+ {
230
+ totalBytes,
231
+ modules: sortBySize(modules),
232
+ chunks: sortBySize(chunks)
233
+ },
234
+ commitSha
235
+ );
236
+ }
237
+ function finalize(partial, commitSha) {
238
+ return {
239
+ version: 1,
240
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
241
+ commitSha,
242
+ ...partial
243
+ };
244
+ }
245
+ function tryParseDiagnostics(nextDir, withSuggestions) {
246
+ const dir = join3(nextDir, "diagnostics", "analyze");
247
+ if (!existsSync3(dir)) return null;
248
+ const modules = [];
249
+ const chunks = [];
250
+ for (const file of listJsonFiles(dir)) {
251
+ try {
252
+ const data = JSON.parse(readFileSync2(file, "utf8"));
253
+ absorbStats(data, modules, chunks, withSuggestions);
254
+ } catch {
255
+ }
256
+ }
257
+ if (modules.length === 0 && chunks.length === 0) return null;
258
+ const totalBytes = chunks.reduce((s, c) => s + c.size, 0) || modules.reduce((s, m) => s + m.size, 0);
259
+ return { totalBytes, modules: sortBySize(modules), chunks: sortBySize(chunks) };
260
+ }
261
+ function tryParseAnalyzeClient(nextDir, withSuggestions) {
262
+ const clientPath = join3(nextDir, "analyze", "client.json");
263
+ if (!existsSync3(clientPath)) return null;
264
+ try {
265
+ const data = JSON.parse(readFileSync2(clientPath, "utf8"));
266
+ const modules = [];
267
+ const chunks = [];
268
+ absorbStats(data, modules, chunks, withSuggestions);
269
+ const totalBytes = chunks.reduce((s, c) => s + c.size, 0) || modules.reduce((s, m) => s + m.size, 0);
270
+ return {
271
+ totalBytes,
272
+ modules: sortBySize(modules),
273
+ chunks: sortBySize(chunks)
274
+ };
275
+ } catch {
276
+ return null;
277
+ }
278
+ }
279
+ function parseRouteBundleStats(nextDir) {
280
+ const path = join3(nextDir, "diagnostics", "route-bundle-stats.json");
281
+ if (!existsSync3(path)) {
282
+ return { totalBytes: 0, modules: [], chunks: [] };
283
+ }
284
+ try {
285
+ const rows = JSON.parse(readFileSync2(path, "utf8"));
286
+ const chunks = rows.map((row) => ({
287
+ name: row.route || "unknown",
288
+ size: Number(row.firstLoadUncompressedJsBytes || 0),
289
+ route: row.route
290
+ }));
291
+ const totalBytes = Math.max(...chunks.map((c) => c.size), 0);
292
+ return { totalBytes, modules: [], chunks: sortBySize(chunks) };
293
+ } catch {
294
+ return { totalBytes: 0, modules: [], chunks: [] };
295
+ }
296
+ }
297
+ function parseChunkFallback(nextDir) {
298
+ const chunksDir = join3(nextDir, "static", "chunks");
299
+ const chunks = [];
300
+ if (existsSync3(chunksDir)) {
301
+ for (const file of walkFiles(chunksDir)) {
302
+ if (!file.endsWith(".js")) continue;
303
+ try {
304
+ const size = statSync2(file).size;
305
+ const name = file.slice(chunksDir.length + 1);
306
+ chunks.push({ name, size, route: guessRoute(name) });
307
+ } catch {
308
+ }
309
+ }
310
+ }
311
+ for (const manifestName of ["build-manifest.json", "app-build-manifest.json"]) {
312
+ const manifestPath = join3(nextDir, manifestName);
313
+ if (!existsSync3(manifestPath)) continue;
314
+ try {
315
+ const manifest = JSON.parse(readFileSync2(manifestPath, "utf8"));
316
+ for (const [route, files] of Object.entries(manifest.pages ?? {})) {
317
+ for (const file of files) {
318
+ const abs = join3(nextDir, file.replace(/^\//, ""));
319
+ if (!existsSync3(abs)) continue;
320
+ const size = statSync2(abs).size;
321
+ if (!chunks.some((c) => c.name === file)) {
322
+ chunks.push({ name: file, size, route });
323
+ }
324
+ }
325
+ }
326
+ } catch {
327
+ }
328
+ }
329
+ const totalBytes = chunks.reduce((s, c) => s + c.size, 0);
330
+ return { totalBytes, modules: [], chunks: sortBySize(chunks) };
331
+ }
332
+ function enrichWithKnownPackages(opts) {
333
+ const chunksDir = join3(opts.nextDir, "static", "chunks");
334
+ const serverDir = join3(opts.nextDir, "server");
335
+ const scanDirs = [chunksDir, serverDir].filter((d) => existsSync3(d));
336
+ if (scanDirs.length === 0) return [];
337
+ const present = /* @__PURE__ */ new Set();
338
+ for (const dir of scanDirs) {
339
+ for (const file of walkFiles(dir)) {
340
+ if (!file.endsWith(".js")) continue;
341
+ let text = "";
342
+ try {
343
+ const buf = readFileSync2(file);
344
+ text = buf.subarray(0, Math.min(buf.length, 2e6)).toString("utf8");
345
+ } catch {
346
+ continue;
347
+ }
348
+ for (const pkg of KNOWN_HEAVY) {
349
+ if (text.includes(`node_modules/${pkg}`) || text.includes(`/${pkg}/`) || text.includes(`"${pkg}"`) || text.includes(`'${pkg}'`)) {
350
+ present.add(pkg);
351
+ }
352
+ }
353
+ }
354
+ }
355
+ const modules = [];
356
+ for (const pkg of present) {
357
+ const size = estimatePackageChunkSize(scanDirs, pkg);
358
+ const culpritFile = findImportCulprit(opts.projectDir, pkg) || "unknown";
359
+ modules.push({
360
+ name: pkg,
361
+ size: size || 50 * 1024,
362
+ culpritFile,
363
+ chain: [pkg, culpritFile],
364
+ issuerPath: [pkg, culpritFile],
365
+ suggestion: opts.withSuggestions ? suggestAlternative(pkg) : null
366
+ });
367
+ }
368
+ return modules;
369
+ }
370
+ function estimatePackageChunkSize(dirs, pkg) {
371
+ let best = 0;
372
+ for (const dir of dirs) {
373
+ for (const file of walkFiles(dir)) {
374
+ if (!file.endsWith(".js")) continue;
375
+ try {
376
+ const buf = readFileSync2(file);
377
+ const text = buf.subarray(0, Math.min(buf.length, 5e5)).toString("utf8");
378
+ if (text.includes(pkg)) {
379
+ best = Math.max(best, statSync2(file).size);
380
+ }
381
+ } catch {
382
+ }
383
+ }
384
+ }
385
+ return best;
386
+ }
387
+ function findImportCulprit(projectDir, pkg) {
388
+ const roots = ["app", "pages", "src", "components"];
389
+ for (const root of roots) {
390
+ const dir = join3(projectDir, root);
391
+ if (!existsSync3(dir)) continue;
392
+ const hit = searchImport(dir, pkg, projectDir, 6);
393
+ if (hit) return hit;
394
+ }
395
+ return null;
396
+ }
397
+ function searchImport(dir, pkg, projectDir, depth) {
398
+ if (depth < 0) return null;
399
+ let entries;
400
+ try {
401
+ entries = readdirSync2(dir);
402
+ } catch {
403
+ return null;
404
+ }
405
+ for (const entry of entries) {
406
+ if (entry === "node_modules" || entry.startsWith(".")) continue;
407
+ const full = join3(dir, entry);
408
+ let st;
409
+ try {
410
+ st = statSync2(full);
411
+ } catch {
412
+ continue;
413
+ }
414
+ if (st.isDirectory()) {
415
+ const found = searchImport(full, pkg, projectDir, depth - 1);
416
+ if (found) return found;
417
+ continue;
418
+ }
419
+ if (!/\.(tsx?|jsx?|mjs|cjs)$/.test(entry)) continue;
420
+ try {
421
+ const text = readFileSync2(full, "utf8");
422
+ if (text.includes(`from '${pkg}'`) || text.includes(`from "${pkg}"`) || text.includes(`require('${pkg}')`) || text.includes(`require("${pkg}")`) || text.includes(`from '${pkg}/`) || text.includes(`from "${pkg}/`)) {
423
+ return relative(projectDir, full).replace(/\\/g, "/");
424
+ }
425
+ } catch {
426
+ }
427
+ }
428
+ return null;
429
+ }
430
+ function absorbStats(data, modules, chunks, withSuggestions) {
431
+ if (!data || typeof data !== "object") return;
432
+ const root = data;
433
+ if (Array.isArray(root.chunks)) {
434
+ for (const chunk of root.chunks) {
435
+ chunks.push({
436
+ name: String(chunk.name ?? chunk.id ?? "chunk"),
437
+ size: Number(chunk.size ?? chunk.parsedSize ?? 0),
438
+ route: typeof chunk.route === "string" ? chunk.route : void 0
439
+ });
440
+ }
441
+ }
442
+ const moduleList = [];
443
+ collectModules(root.modules, moduleList);
444
+ collectModules(root.tree, moduleList);
445
+ for (const mod of moduleList) {
446
+ const size = Number(mod.size ?? mod.gzipSize ?? 0);
447
+ if (!mod.name || size <= 0) continue;
448
+ const { culpritFile, chain } = findCulprit(mod);
449
+ modules.push({
450
+ name: mod.name,
451
+ size,
452
+ issuerPath: chain,
453
+ culpritFile,
454
+ chain,
455
+ suggestion: withSuggestions ? suggestAlternative(mod.name) : null
456
+ });
457
+ }
458
+ }
459
+ function collectModules(node, out) {
460
+ if (!node) return;
461
+ if (Array.isArray(node)) {
462
+ for (const item of node) collectModules(item, out);
463
+ return;
464
+ }
465
+ if (typeof node !== "object") return;
466
+ const obj = node;
467
+ if (typeof obj.name === "string" && (obj.size != null || obj.gzipSize != null)) {
468
+ out.push(obj);
469
+ }
470
+ if (Array.isArray(obj.children)) collectModules(obj.children, out);
471
+ if (Array.isArray(obj.modules)) collectModules(obj.modules, out);
472
+ }
473
+ function listJsonFiles(dir) {
474
+ return walkFiles(dir).filter((f) => f.endsWith(".json"));
475
+ }
476
+ function walkFiles(dir) {
477
+ const out = [];
478
+ let entries;
479
+ try {
480
+ entries = readdirSync2(dir);
481
+ } catch {
482
+ return out;
483
+ }
484
+ for (const entry of entries) {
485
+ const full = join3(dir, entry);
486
+ try {
487
+ const st = statSync2(full);
488
+ if (st.isDirectory()) out.push(...walkFiles(full));
489
+ else out.push(full);
490
+ } catch {
491
+ }
492
+ }
493
+ return out;
494
+ }
495
+ function guessRoute(chunkName) {
496
+ const match = chunkName.match(/(?:^|\/)app(\/.*)\/(?:page|layout|route)/);
497
+ if (match?.[1]) return match[1] || "/";
498
+ return void 0;
499
+ }
500
+ function sortBySize(items) {
501
+ return [...items].sort((a, b) => b.size - a.size);
502
+ }
503
+
504
+ // src/uploader.ts
505
+ import { mkdirSync, writeFileSync } from "fs";
506
+ import { dirname, join as join4 } from "path";
507
+ function writeLocalReport(projectDir, report) {
508
+ const outPaths = [
509
+ join4(projectDir, "bundle-report.json"),
510
+ join4(projectDir, ".vercel", "output", "static", "bundle-report.json")
511
+ ];
512
+ let primary = outPaths[0];
513
+ for (const outPath of outPaths) {
514
+ try {
515
+ mkdirSync(dirname(outPath), { recursive: true });
516
+ writeFileSync(outPath, JSON.stringify(report, null, 2));
517
+ primary = outPath;
518
+ } catch (error) {
519
+ console.warn(`[bundle-cop] Could not write ${outPath}:`, error);
520
+ }
521
+ }
522
+ return primary;
523
+ }
524
+ async function uploadReport(report) {
525
+ const token = process.env.BLOB_READ_WRITE_TOKEN;
526
+ const sha = report.commitSha || process.env.VERCEL_GIT_COMMIT_SHA || process.env.GITHUB_SHA || "local";
527
+ if (!token) {
528
+ console.warn(
529
+ "[bundle-cop] BLOB_READ_WRITE_TOKEN not set \u2014 skipping Blob upload"
530
+ );
531
+ return null;
532
+ }
533
+ try {
534
+ const { put } = await import("@vercel/blob");
535
+ const pathname = `bundle-reports/${sha}.json`;
536
+ const result = await put(pathname, JSON.stringify(report), {
537
+ access: "private",
538
+ contentType: "application/json",
539
+ addRandomSuffix: false,
540
+ allowOverwrite: true,
541
+ token
542
+ });
543
+ console.log(`[bundle-cop] Uploaded report to Blob: ${pathname}`);
544
+ return result.url;
545
+ } catch (error) {
546
+ console.warn("[bundle-cop] Blob upload failed:", error);
547
+ return null;
548
+ }
549
+ }
550
+
551
+ // src/index.ts
552
+ async function runBundleCop(ctx) {
553
+ const nextDir = findNextDir(ctx.projectDir, ctx.distDir) || join5(ctx.projectDir, ".next");
554
+ const config = loadConfig(ctx.projectDir);
555
+ const report = await parseStats({
556
+ nextDir,
557
+ projectDir: ctx.projectDir,
558
+ commitSha: process.env.VERCEL_GIT_COMMIT_SHA || process.env.GITHUB_SHA || null,
559
+ withSuggestions: config.suggestions !== false
560
+ });
561
+ const budgetResults = checkBudgets(report, config);
562
+ report.budgetResults = budgetResults;
563
+ writeLocalReport(ctx.projectDir, report);
564
+ await uploadReport(report);
565
+ enforceBudgets(budgetResults);
566
+ console.log(
567
+ `[bundle-cop] Report total=${report.totalBytes}B modules=${report.modules.length} chunks=${report.chunks.length}`
568
+ );
569
+ return report;
570
+ }
571
+ var adapter = {
572
+ name: "bundle-cop",
573
+ async modifyConfig(config, ctx) {
574
+ if (ctx.phase !== "phase-production-build") {
575
+ return config;
576
+ }
577
+ if (!process.env.BUNDLE_COP_ANALYZE) {
578
+ process.env.BUNDLE_COP_ANALYZE = "1";
579
+ }
580
+ const experimental = typeof config.experimental === "object" && config.experimental ? { ...config.experimental } : {};
581
+ return {
582
+ ...config,
583
+ experimental
584
+ };
585
+ },
586
+ async onBuildComplete(ctx) {
587
+ const started = Date.now();
588
+ try {
589
+ await runBundleCop({
590
+ projectDir: ctx.projectDir || ctx.repoRoot,
591
+ distDir: ctx.distDir
592
+ });
593
+ } catch (error) {
594
+ if (error instanceof Error && error.message.startsWith("[bundle-cop] Budget exceeded")) {
595
+ throw error;
596
+ }
597
+ console.warn("[bundle-cop] onBuildComplete error:", error);
598
+ } finally {
599
+ console.log(`[bundle-cop] finished in ${Date.now() - started}ms`);
600
+ }
601
+ }
602
+ };
603
+ var index_default = adapter;
604
+ export {
605
+ adapter,
606
+ checkBudgets,
607
+ index_default as default,
608
+ findCulprit,
609
+ findNextDir,
610
+ formatBytes,
611
+ loadConfig,
612
+ parseSize,
613
+ parseStats,
614
+ runBundleCop,
615
+ suggestAlternative
616
+ };
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Next.js loads adapters via `import(pathToFileURL(require.resolve(...)))`
5
+ * and then `interopDefault`. Exporting the adapter object as `module.exports`
6
+ * (not a namespace with `.default`) makes `onBuildComplete` available.
7
+ */
8
+ const mod = require('./dist/index.cjs')
9
+ module.exports = mod.adapter || mod.default || mod
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "bundle-cop-vercel-plugin",
3
+ "version": "0.1.0",
4
+ "description": "Next.js adapter that attributes bundle cost to source files and enforces budgets",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs",
14
+ "default": "./dist/index.js"
15
+ },
16
+ "./adapter": {
17
+ "types": "./dist/index.d.ts",
18
+ "require": "./next-adapter.cjs",
19
+ "import": "./next-adapter.cjs",
20
+ "default": "./next-adapter.cjs"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "next-adapter.cjs",
26
+ "README.md"
27
+ ],
28
+ "scripts": {
29
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
30
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
31
+ "typecheck": "tsc --noEmit",
32
+ "prepublishOnly": "pnpm run build"
33
+ },
34
+ "engines": {
35
+ "node": ">=20"
36
+ },
37
+ "peerDependencies": {
38
+ "next": ">=15"
39
+ },
40
+ "dependencies": {
41
+ "@vercel/blob": "^2.8.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.13.10",
45
+ "next": "^16.3.3",
46
+ "tsup": "^8.4.0",
47
+ "typescript": "^5.8.2"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "keywords": [
53
+ "nextjs",
54
+ "vercel",
55
+ "bundle",
56
+ "webpack",
57
+ "turbopack",
58
+ "adapter",
59
+ "budget"
60
+ ],
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "git+https://github.com/eboxnadeem/bundle-cop.git",
64
+ "directory": "packages/vercel-plugin"
65
+ },
66
+ "bugs": {
67
+ "url": "https://github.com/eboxnadeem/bundle-cop/issues"
68
+ },
69
+ "homepage": "https://github.com/eboxnadeem/bundle-cop#readme"
70
+ }