@node-cli/bundlecheck 1.2.0 → 1.4.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 CHANGED
@@ -13,8 +13,11 @@ A CLI tool to check the bundle size of npm packages, similar to [bundlephobia.co
13
13
  - Support for checking specific exports (tree-shaking)
14
14
  - Automatic externalization of React and React-DOM
15
15
  - Raw and gzip sizes with configurable compression level
16
+ - **Platform support**: target `browser` (default) or `node` with smart auto-detection
16
17
  - Custom npm registry support (for private registries)
17
18
  - Fast bundling using esbuild (with pnpm support)
19
+ - **Local caching** for faster repeated lookups (SQLite-based, max 100 entries)
20
+ - **Library API** for programmatic usage in Node.js applications
18
21
 
19
22
  ## Installation
20
23
 
@@ -92,17 +95,19 @@ bundlecheck lodash "debounce,throttle"
92
95
 
93
96
  ### Options
94
97
 
95
- | Flag | Short | Description |
96
- | ------------------- | ----------- | ------------------------------------------------------- |
97
- | `--help` | `-h` | Display help instructions |
98
- | `--version` | `-v` | Output the current version |
99
- | `--versions` | `-V` | Choose from available package versions interactively |
100
- | `--trend [N]` | `-t [N]` | Show bundle size trend for N versions (default: 5) |
101
- | `--boring` | `-b` | Do not use color output |
102
- | `--gzipLevel <n>` | `-g <n>` | Gzip compression level (1-9, default: 5) |
103
- | `--external <pkgs>` | `-e <pkgs>` | Comma-separated additional packages to mark as external |
104
- | `--noExternal` | `-n` | Do not mark any packages as external |
105
- | `--registry <url>` | `-r <url>` | Custom npm registry URL (default: registry.npmjs.org) |
98
+ | Flag | Short | Description |
99
+ | ------------------- | ----------- | ---------------------------------------------------------------- |
100
+ | `--help` | `-h` | Display help instructions |
101
+ | `--version` | `-v` | Output the current version |
102
+ | `--versions` | `-V` | Choose from available package versions interactively |
103
+ | `--trend [N]` | `-t [N]` | Show bundle size trend for N versions (default: 5) |
104
+ | `--boring` | `-b` | Do not use color output |
105
+ | `--gzipLevel <n>` | `-g <n>` | Gzip compression level (1-9, default: 5) |
106
+ | `--external <pkgs>` | `-e <pkgs>` | Comma-separated additional packages to mark as external |
107
+ | `--noExternal` | `-n` | Do not mark any packages as external |
108
+ | `--registry <url>` | `-r <url>` | Custom npm registry URL (default: registry.npmjs.org) |
109
+ | `--platform <name>` | `-p <name>` | Target platform: `auto` (default), `browser`, or `node` |
110
+ | `--force` | `-f` | Bypass cache and force re-fetch/re-calculation |
106
111
 
107
112
  ### Examples
108
113
 
@@ -137,6 +142,216 @@ bundlecheck lodash --trend 3
137
142
 
138
143
  # Use a custom npm registry
139
144
  bundlecheck @myorg/private-pkg --registry https://npm.mycompany.com
145
+
146
+ # Check a Node.js package (explicit platform)
147
+ bundlecheck express --platform node
148
+
149
+ # Auto-detect platform (default behavior)
150
+ bundlecheck express # auto-detects "node" from package.json engines
151
+
152
+ # Bypass cache and force re-fetch
153
+ bundlecheck lodash --force
154
+ ```
155
+
156
+ ## Programmatic Usage (Library API)
157
+
158
+ In addition to the CLI, `@node-cli/bundlecheck` can be used as a library in your Node.js code.
159
+
160
+ ### Installation
161
+
162
+ ```bash
163
+ npm install @node-cli/bundlecheck
164
+ ```
165
+
166
+ ### Basic Usage
167
+
168
+ ```js
169
+ import { getBundleStats, getBundleTrend, getPackageVersions } from "@node-cli/bundlecheck";
170
+
171
+ // Get bundle stats for a single package
172
+ const stats = await getBundleStats({
173
+ package: "@mantine/core@7.0.0",
174
+ });
175
+ console.log(stats);
176
+ // {
177
+ // packageName: "@mantine/core",
178
+ // packageVersion: "7.0.0",
179
+ // rawSize: 234567,
180
+ // gzipSize: 45678,
181
+ // rawSizeFormatted: "229.07 kB",
182
+ // gzipSizeFormatted: "44.61 kB",
183
+ // exports: [],
184
+ // externals: ["react", "react-dom"],
185
+ // dependencies: ["@floating-ui/react", ...],
186
+ // platform: "browser",
187
+ // gzipLevel: 5,
188
+ // fromCache: false
189
+ // }
190
+
191
+ // Check specific exports (tree-shaking)
192
+ const buttonStats = await getBundleStats({
193
+ package: "@mantine/core",
194
+ exports: ["Button", "Input"],
195
+ });
196
+ console.log(buttonStats.gzipSizeFormatted); // "12.3 kB"
197
+
198
+ // Get bundle size trend across versions
199
+ const trend = await getBundleTrend({
200
+ package: "@mantine/core",
201
+ versionCount: 5,
202
+ });
203
+ console.log(trend);
204
+ // {
205
+ // packageName: "@mantine/core",
206
+ // versions: [
207
+ // { version: "7.0.0", rawSize: 234567, gzipSize: 45678, rawSizeFormatted: "229.07 kB", ... },
208
+ // { version: "6.0.0", rawSize: 220000, gzipSize: 42000, ... },
209
+ // ...
210
+ // ],
211
+ // change: {
212
+ // fromVersion: "5.0.0",
213
+ // toVersion: "7.0.0",
214
+ // rawDiff: 14567,
215
+ // rawPercent: 6.6,
216
+ // rawDiffFormatted: "+14.23 kB",
217
+ // gzipDiff: 3678,
218
+ // gzipPercent: 8.7,
219
+ // gzipDiffFormatted: "+3.59 kB"
220
+ // }
221
+ // }
222
+
223
+ // Get available versions for a package
224
+ const versions = await getPackageVersions({
225
+ package: "@mantine/core",
226
+ });
227
+ console.log(versions.tags.latest); // "7.0.0"
228
+ console.log(versions.versions.slice(0, 5)); // ["7.0.0", "6.0.21", "6.0.20", ...]
229
+ ```
230
+
231
+ ### API Reference
232
+
233
+ #### `getBundleStats(options)`
234
+
235
+ Get bundle size statistics for a single package.
236
+
237
+ **Options:**
238
+
239
+ | Option | Type | Default | Description |
240
+ | ------------ | ----------------------------- | ---------- | -------------------------------------------------------- |
241
+ | `package` | `string` | (required) | Package name with optional version (e.g., `lodash@4.17.0`) |
242
+ | `exports` | `string[]` | `undefined`| Specific exports to measure (tree-shaking) |
243
+ | `external` | `string[]` | `undefined`| Additional packages to mark as external |
244
+ | `noExternal` | `boolean` | `false` | Bundle everything including default externals |
245
+ | `gzipLevel` | `number` | `5` | Gzip compression level (1-9) |
246
+ | `registry` | `string` | `undefined`| Custom npm registry URL |
247
+ | `platform` | `"browser" \| "node" \| "auto"` | `"auto"` | Target platform |
248
+ | `force` | `boolean` | `false` | Bypass cache |
249
+
250
+ **Returns:** `Promise<BundleStats>`
251
+
252
+ ```ts
253
+ type BundleStats = {
254
+ packageName: string; // Display name (may include subpath)
255
+ packageVersion: string; // Resolved version
256
+ exports: string[]; // Exports analyzed
257
+ rawSize: number; // Raw size in bytes
258
+ gzipSize: number | null; // Gzip size in bytes (null for node platform)
259
+ gzipLevel: number; // Compression level used
260
+ externals: string[]; // External packages
261
+ dependencies: string[]; // Package dependencies
262
+ platform: "browser" | "node";
263
+ rawSizeFormatted: string; // Human-readable (e.g., "45.2 kB")
264
+ gzipSizeFormatted: string | null;
265
+ fromCache: boolean; // Whether result was from cache
266
+ };
267
+ ```
268
+
269
+ #### `getBundleTrend(options)`
270
+
271
+ Get bundle size trend across multiple versions.
272
+
273
+ **Options:**
274
+
275
+ | Option | Type | Default | Description |
276
+ | -------------- | ----------------------------- | ---------- | -------------------------------------------------------- |
277
+ | `package` | `string` | (required) | Package name (version ignored if provided) |
278
+ | `versionCount` | `number` | `5` | Number of versions to analyze |
279
+ | `exports` | `string[]` | `undefined`| Specific exports to measure |
280
+ | `external` | `string[]` | `undefined`| Additional packages to mark as external |
281
+ | `noExternal` | `boolean` | `false` | Bundle everything including default externals |
282
+ | `gzipLevel` | `number` | `5` | Gzip compression level (1-9) |
283
+ | `registry` | `string` | `undefined`| Custom npm registry URL |
284
+ | `platform` | `"browser" \| "node" \| "auto"` | `"auto"` | Target platform |
285
+ | `force` | `boolean` | `false` | Bypass cache |
286
+
287
+ **Returns:** `Promise<BundleTrend>`
288
+
289
+ ```ts
290
+ type BundleTrend = {
291
+ packageName: string;
292
+ versions: TrendVersionResult[]; // Results for each version (newest first)
293
+ change: TrendChange | null; // Change between oldest and newest
294
+ };
295
+
296
+ type TrendVersionResult = {
297
+ version: string;
298
+ rawSize: number;
299
+ gzipSize: number | null;
300
+ rawSizeFormatted: string;
301
+ gzipSizeFormatted: string | null;
302
+ };
303
+
304
+ type TrendChange = {
305
+ fromVersion: string;
306
+ toVersion: string;
307
+ rawDiff: number; // Positive = increase, negative = decrease
308
+ rawPercent: number | null; // null if oldest size was 0
309
+ rawDiffFormatted: string; // e.g., "+5.2 kB" or "-1.3 kB"
310
+ gzipDiff: number | null;
311
+ gzipPercent: number | null; // null if not applicable or oldest size was 0
312
+ gzipDiffFormatted: string | null;
313
+ };
314
+ ```
315
+
316
+ #### `getPackageVersions(options)`
317
+
318
+ Get available versions for an npm package.
319
+
320
+ **Options:**
321
+
322
+ | Option | Type | Default | Description |
323
+ | ---------- | -------- | ---------- | ---------------------------- |
324
+ | `package` | `string` | (required) | Package name |
325
+ | `registry` | `string` | `undefined`| Custom npm registry URL |
326
+
327
+ **Returns:** `Promise<PackageVersions>`
328
+
329
+ ```ts
330
+ type PackageVersions = {
331
+ versions: string[]; // All versions (newest first)
332
+ tags: Record<string, string>; // Dist tags (e.g., { latest: "7.0.0" })
333
+ };
334
+ ```
335
+
336
+ ### Utility Functions
337
+
338
+ ```js
339
+ import { formatBytes, parsePackageSpecifier, clearCache, getCacheCount } from "@node-cli/bundlecheck";
340
+
341
+ // Format bytes to human-readable string
342
+ formatBytes(1024); // "1 kB"
343
+ formatBytes(1536); // "1.5 kB"
344
+
345
+ // Parse a package specifier
346
+ parsePackageSpecifier("@scope/name@1.0.0");
347
+ // { name: "@scope/name", version: "1.0.0" }
348
+
349
+ parsePackageSpecifier("@scope/name/subpath@2.0.0");
350
+ // { name: "@scope/name", version: "2.0.0", subpath: "subpath" }
351
+
352
+ // Cache management
353
+ getCacheCount(); // Returns number of cached entries
354
+ clearCache(); // Clears all cached results
140
355
  ```
141
356
 
142
357
  ## How It Works
@@ -148,6 +363,27 @@ bundlecheck @myorg/private-pkg --registry https://npm.mycompany.com
148
363
  5. Reports raw and gzip sizes
149
364
  6. Cleans up temporary files
150
365
 
366
+ ## Platform Support
367
+
368
+ The `--platform` flag controls how the bundle is built:
369
+
370
+ - **`auto`** (default): Automatically detects the platform from the package's `engines` field. If the package specifies `engines.node` without `engines.browser`, it uses `node`; otherwise defaults to `browser`.
371
+ - **`browser`**: Builds for browser environments (also accepts aliases: `web`, `desktop`, `client`)
372
+ - **`node`**: Builds for Node.js environments (also accepts aliases: `server`, `nodejs`, `backend`)
373
+
374
+ When targeting **node** platform:
375
+ - Gzip size is not calculated (shows "N/A") since server-side code isn't typically served compressed over HTTP
376
+ - The bundle is optimized for Node.js built-ins
377
+
378
+ ```bash
379
+ # Auto-detect (recommended for most cases)
380
+ bundlecheck express # detects "node" from engines.node
381
+
382
+ # Explicit platform
383
+ bundlecheck lodash --platform browser
384
+ bundlecheck fastify -p server # "server" is an alias for "node"
385
+ ```
386
+
151
387
  ## Default Externals
152
388
 
153
389
  By default, `react` and `react-dom` are marked as external (not included in the bundle size) since most React-based packages expect these as peer dependencies. This matches how these packages would typically be used in a real application.
@@ -169,6 +405,46 @@ bundlecheck my-local-pkg --registry http://localhost:4873
169
405
 
170
406
  Note: If the registry requires authentication, ensure your npm/pnpm is configured with the appropriate credentials (via `.npmrc` or environment variables).
171
407
 
408
+ ## Cache
409
+
410
+ Bundle size results are cached locally to speed up repeated lookups. The cache is stored in `~/.bundlecheck/cache.db` using SQLite.
411
+
412
+ ### How it works
413
+
414
+ - Results are cached based on: package name, version, exports, platform, gzip level, and externals configuration
415
+ - The cache holds up to **100 entries** (least recently used entries are evicted first)
416
+ - When you check a package, the CLI first looks for a cached result with matching parameters
417
+
418
+ ### Smart version matching
419
+
420
+ The cache uses **resolved versions**, not the requested specifier. This means:
421
+
422
+ ```bash
423
+ bundlecheck @mantine/core # Resolves "latest" to e.g. 8.0.0, caches as 8.0.0
424
+ bundlecheck @mantine/core@8.0.0 # Cache hit! Same resolved version
425
+ ```
426
+
427
+ This also works with `--trend`: if you previously checked `@mantine/core` (which resolved to v8.0.0), running `--trend` will use the cached result for v8.0.0 and only fetch the other versions.
428
+
429
+ ### Bypassing the cache
430
+
431
+ Use the `--force` flag to skip the cache and re-fetch/re-calculate:
432
+
433
+ ```bash
434
+ bundlecheck lodash --force # Always fetches fresh data
435
+ bundlecheck lodash -f # Short form
436
+ ```
437
+
438
+ ### Cache location
439
+
440
+ The cache database is stored at:
441
+
442
+ ```
443
+ ~/.bundlecheck/cache.db
444
+ ```
445
+
446
+ To clear the cache, simply delete this file or the `~/.bundlecheck` directory.
447
+
172
448
  ## License
173
449
 
174
450
  MIT - see [LICENSE](./LICENSE) for details.
@@ -1,18 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
  /* istanbul ignore file */ import { Logger } from "@node-cli/logger";
3
3
  import kleur from "kleur";
4
- import { checkBundleSize, formatBytes, parsePackageSpecifier } from "./bundler.js";
5
- import { TREND_VERSION_COUNT } from "./defaults.js";
4
+ import { checkBundleSize, formatBytes, getExternals, parsePackageSpecifier } from "./bundler.js";
5
+ import { getCachedResult, normalizeCacheKey, setCachedResult } from "./cache.js";
6
+ import { normalizePlatform, TREND_VERSION_COUNT } from "./defaults.js";
6
7
  import { config } from "./parse.js";
7
8
  import { analyzeTrend, renderTrendGraph, selectTrendVersions } from "./trend.js";
8
9
  import { fetchPackageVersions, promptForVersion } from "./versions.js";
9
10
  const flags = config.flags;
10
11
  const parameters = config.parameters;
11
- // Disable kleur colors when --boring flag is set
12
+ // Disable kleur colors when --boring flag is set.
12
13
  kleur.enabled = !flags?.boring;
13
14
  const log = new Logger({
14
15
  boring: flags?.boring
15
16
  });
17
+ /**
18
+ * Display bundle result in a formatted box.
19
+ */ function displayResult(result, isAutoDetected) {
20
+ const blue = kleur.blue;
21
+ const green = kleur.green;
22
+ const platformLabel = result.platform === "node" ? "node" : "browser";
23
+ const platformNote = isAutoDetected ? " (auto-detected)" : "";
24
+ log.printBox([
25
+ `${blue("Package:")} ${result.packageName} (${blue("version:")} ${result.packageVersion})`,
26
+ result.exports.length > 0 ? `${blue("Exports:")} { ${result.exports.join(", ")} }` : `${blue("Exports:")} * (entire package)`,
27
+ "",
28
+ `${blue("Raw size:")} ${formatBytes(result.rawSize)}`,
29
+ result.gzipSize !== null ? `${blue("Gzip size:")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})` : `${blue("Gzip size:")} N/A (not applicable for node platform)`,
30
+ "",
31
+ result.externals.length > 0 ? `${blue("Externals:")} ${result.externals.join(", ")}` : `${blue("Externals:")} ${green("none")}`,
32
+ result.dependencies.length > 0 ? `${blue("Dependencies:")} ${result.dependencies.join(", ")}` : `${blue("Dependencies:")} ${green("none")}`,
33
+ `${blue("Platform:")} ${platformLabel}${platformNote}`
34
+ ], {
35
+ borderStyle: "round",
36
+ align: "left"
37
+ });
38
+ }
16
39
  async function main() {
17
40
  let packageName = parameters?.["0"];
18
41
  if (!packageName) {
@@ -20,26 +43,29 @@ async function main() {
20
43
  config.showHelp?.();
21
44
  process.exit(1);
22
45
  }
23
- // Parse additional externals if provided (comma-separated)
46
+ // Parse additional externals if provided (comma-separated).
24
47
  let additionalExternals;
25
48
  if (flags?.external) {
26
49
  additionalExternals = flags.external.split(",").map((e)=>e.trim()).filter(Boolean);
27
50
  }
28
- // Parse exports if provided (comma-separated)
51
+ // Parse exports if provided (comma-separated).
29
52
  let exports;
30
53
  const exportsArg = parameters?.["1"];
31
54
  if (exportsArg) {
32
55
  exports = exportsArg.split(",").map((e)=>e.trim()).filter(Boolean);
33
56
  }
34
- // If --trend flag is set, show bundle size trend across versions
35
- // --trend alone uses default (5), --trend N uses N versions
36
- const trendValue = flags?.trend;
57
+ // Normalize platform from flag (handles aliases like "web" "browser").
58
+ const platform = normalizePlatform(flags?.platform);
59
+ /**
60
+ * If --trend flag is set, show bundle size trend across versions --trend alone
61
+ * uses default (5), --trend N uses N versions.
62
+ */ const trendValue = flags?.trend;
37
63
  if (trendValue !== undefined) {
38
64
  const parsedCount = Number.parseInt(trendValue, 10);
39
65
  const versionCount = !Number.isNaN(parsedCount) && parsedCount > 0 ? parsedCount : TREND_VERSION_COUNT;
40
66
  try {
41
67
  const { name, subpath } = parsePackageSpecifier(packageName);
42
- // Construct the full package path including subpath if present
68
+ // Construct the full package path including subpath if present.
43
69
  const fullPackagePath = subpath ? `${name}/${subpath}` : name;
44
70
  log.info(`\nFetching available versions for ${name}...`);
45
71
  const { versions } = await fetchPackageVersions({
@@ -50,7 +76,7 @@ async function main() {
50
76
  log.error("No versions found for this package");
51
77
  process.exit(1);
52
78
  }
53
- // Select versions for trend
79
+ // Select versions for trend.
54
80
  const trendVersions = selectTrendVersions(versions, versionCount);
55
81
  log.info(`Analyzing ${trendVersions.length} versions: ${trendVersions.join(", ")}`);
56
82
  log.info("");
@@ -62,13 +88,15 @@ async function main() {
62
88
  noExternal: flags?.noExternal,
63
89
  gzipLevel: flags?.gzipLevel,
64
90
  boring: flags?.boring,
65
- registry: flags?.registry
91
+ registry: flags?.registry,
92
+ platform,
93
+ force: flags?.force
66
94
  });
67
95
  if (results.length === 0) {
68
96
  log.error("Failed to analyze any versions");
69
97
  process.exit(1);
70
98
  }
71
- // Render and display the trend graph
99
+ // Render and display the trend graph.
72
100
  const graphLines = renderTrendGraph(fullPackagePath, results, flags?.boring);
73
101
  for (const line of graphLines){
74
102
  log.log(line);
@@ -80,7 +108,7 @@ async function main() {
80
108
  process.exit(1);
81
109
  }
82
110
  }
83
- // If --versions flag is set, fetch and prompt for version selection
111
+ // If --versions flag is set, fetch and prompt for version selection.
84
112
  if (flags?.versions) {
85
113
  try {
86
114
  const { name, subpath } = parsePackageSpecifier(packageName);
@@ -94,7 +122,7 @@ async function main() {
94
122
  process.exit(1);
95
123
  }
96
124
  const selectedVersion = await promptForVersion(name, versions, tags);
97
- // Rebuild specifier preserving any subpath
125
+ // Rebuild specifier preserving any subpath.
98
126
  packageName = subpath ? `${name}/${subpath}@${selectedVersion}` : `${name}@${selectedVersion}`;
99
127
  log.info(`\nSelected: ${packageName}`);
100
128
  } catch (error) {
@@ -107,32 +135,54 @@ async function main() {
107
135
  if (exports && exports.length > 0) {
108
136
  log.info(`Exports: { ${exports.join(", ")} }`);
109
137
  }
110
- log.info("Please wait, installing and bundling...\n");
111
138
  try {
139
+ // Parse package specifier to get name and version.
140
+ const { name: baseName, version: requestedVersion } = parsePackageSpecifier(packageName);
141
+ // Resolve "latest" to actual version for cache key.
142
+ let resolvedVersion = requestedVersion;
143
+ if (requestedVersion === "latest") {
144
+ const { tags } = await fetchPackageVersions({
145
+ packageName: baseName,
146
+ registry: flags?.registry
147
+ });
148
+ resolvedVersion = tags.latest || requestedVersion;
149
+ }
150
+ // Compute externals for cache key (same logic as bundler).
151
+ const externals = getExternals(baseName, additionalExternals, flags?.noExternal);
152
+ /**
153
+ * Build cache key.
154
+ * NOTE: platform can be undefined (auto-detect), which is stored as "auto" in cache.
155
+ */ const cacheKey = normalizeCacheKey({
156
+ packageName: baseName,
157
+ version: resolvedVersion,
158
+ exports,
159
+ platform,
160
+ gzipLevel: flags?.gzipLevel ?? 5,
161
+ externals,
162
+ noExternal: flags?.noExternal ?? false
163
+ });
164
+ // Check cache (unless --force flag is set).
165
+ if (!flags?.force) {
166
+ const cached = getCachedResult(cacheKey);
167
+ if (cached) {
168
+ log.info("NOTE: Using cached results\n");
169
+ displayResult(cached, platform === undefined);
170
+ process.exit(0);
171
+ }
172
+ }
173
+ log.info("Please wait, installing and bundling...\n");
112
174
  const result = await checkBundleSize({
113
175
  packageName,
114
176
  exports,
115
177
  additionalExternals,
116
178
  noExternal: flags?.noExternal,
117
179
  gzipLevel: flags?.gzipLevel,
118
- registry: flags?.registry
119
- });
120
- const blue = kleur.blue;
121
- const green = kleur.green;
122
- // Display results
123
- log.printBox([
124
- `${blue("Package:")} ${result.packageName} (${blue("version:")} ${result.packageVersion})`,
125
- result.exports.length > 0 ? `${blue("Exports:")} { ${result.exports.join(", ")} }` : `${blue("Exports:")} * (entire package)`,
126
- "",
127
- `${blue("Raw size:")} ${formatBytes(result.rawSize)}`,
128
- `${blue("Gzip size:")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})`,
129
- "",
130
- result.externals.length > 0 ? `${blue("Externals:")} ${result.externals.join(", ")}` : `${blue("Externals:")} ${green("none")}`,
131
- result.dependencies.length > 0 ? `${blue("Dependencies:")} ${result.dependencies.join(", ")}` : `${blue("Dependencies:")} ${green("none")}`
132
- ], {
133
- borderStyle: "round",
134
- align: "left"
180
+ registry: flags?.registry,
181
+ platform
135
182
  });
183
+ // Store result in cache.
184
+ setCachedResult(cacheKey, result);
185
+ displayResult(result, platform === undefined);
136
186
  process.exit(0);
137
187
  } catch (error) {
138
188
  const errorMessage = error instanceof Error ? error.message : String(error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundlecheck.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/* istanbul ignore file */\n\nimport { Logger } from \"@node-cli/logger\";\nimport kleur from \"kleur\";\nimport {\n\tcheckBundleSize,\n\tformatBytes,\n\tparsePackageSpecifier,\n} from \"./bundler.js\";\nimport { TREND_VERSION_COUNT } from \"./defaults.js\";\nimport { config } from \"./parse.js\";\nimport {\n\tanalyzeTrend,\n\trenderTrendGraph,\n\tselectTrendVersions,\n} from \"./trend.js\";\nimport { fetchPackageVersions, promptForVersion } from \"./versions.js\";\n\nconst flags = config.flags;\nconst parameters = config.parameters;\n\n// Disable kleur colors when --boring flag is set\nkleur.enabled = !flags?.boring;\n\nconst log = new Logger({\n\tboring: flags?.boring,\n});\n\nasync function main() {\n\tlet packageName = parameters?.[\"0\"];\n\n\tif (!packageName) {\n\t\tlog.error(\"Package name is required\");\n\t\tconfig.showHelp?.();\n\t\tprocess.exit(1);\n\t}\n\n\t// Parse additional externals if provided (comma-separated)\n\tlet additionalExternals: string[] | undefined;\n\tif (flags?.external) {\n\t\tadditionalExternals = flags.external\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// Parse exports if provided (comma-separated)\n\tlet exports: string[] | undefined;\n\tconst exportsArg = parameters?.[\"1\"];\n\tif (exportsArg) {\n\t\texports = exportsArg\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// If --trend flag is set, show bundle size trend across versions\n\t// --trend alone uses default (5), --trend N uses N versions\n\tconst trendValue = flags?.trend;\n\tif (trendValue !== undefined) {\n\t\tconst parsedCount = Number.parseInt(trendValue, 10);\n\t\tconst versionCount =\n\t\t\t!Number.isNaN(parsedCount) && parsedCount > 0\n\t\t\t\t? parsedCount\n\t\t\t\t: TREND_VERSION_COUNT;\n\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\t// Construct the full package path including subpath if present\n\t\t\tconst fullPackagePath = subpath ? `${name}/${subpath}` : name;\n\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Select versions for trend\n\t\t\tconst trendVersions = selectTrendVersions(versions, versionCount);\n\n\t\t\tlog.info(\n\t\t\t\t`Analyzing ${trendVersions.length} versions: ${trendVersions.join(\", \")}`,\n\t\t\t);\n\t\t\tlog.info(\"\");\n\n\t\t\tconst results = await analyzeTrend({\n\t\t\t\tpackageName: fullPackagePath,\n\t\t\t\tversions: trendVersions,\n\t\t\t\texports,\n\t\t\t\tadditionalExternals,\n\t\t\t\tnoExternal: flags?.noExternal,\n\t\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\t\tboring: flags?.boring,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\tlog.error(\"Failed to analyze any versions\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Render and display the trend graph\n\t\t\tconst graphLines = renderTrendGraph(\n\t\t\t\tfullPackagePath,\n\t\t\t\tresults,\n\t\t\t\tflags?.boring,\n\t\t\t);\n\t\t\tfor (const line of graphLines) {\n\t\t\t\tlog.log(line);\n\t\t\t}\n\n\t\t\tprocess.exit(0);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to analyze trend: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// If --versions flag is set, fetch and prompt for version selection\n\tif (flags?.versions) {\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions, tags } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tconst selectedVersion = await promptForVersion(name, versions, tags);\n\t\t\t// Rebuild specifier preserving any subpath\n\t\t\tpackageName = subpath\n\t\t\t\t? `${name}/${subpath}@${selectedVersion}`\n\t\t\t\t: `${name}@${selectedVersion}`;\n\t\t\tlog.info(`\\nSelected: ${packageName}`);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to fetch versions: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tlog.info(`\\nAnalyzing bundle size for: ${packageName}`);\n\tif (exports && exports.length > 0) {\n\t\tlog.info(`Exports: { ${exports.join(\", \")} }`);\n\t}\n\tlog.info(\"Please wait, installing and bundling...\\n\");\n\n\ttry {\n\t\tconst result = await checkBundleSize({\n\t\t\tpackageName,\n\t\t\texports,\n\t\t\tadditionalExternals,\n\t\t\tnoExternal: flags?.noExternal,\n\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\tregistry: flags?.registry,\n\t\t});\n\n\t\tconst blue = kleur.blue;\n\t\tconst green = kleur.green;\n\n\t\t// Display results\n\t\tlog.printBox(\n\t\t\t[\n\t\t\t\t`${blue(\"Package:\")} ${result.packageName} (${blue(\"version:\")} ${result.packageVersion})`,\n\t\t\t\tresult.exports.length > 0\n\t\t\t\t\t? `${blue(\"Exports:\")} { ${result.exports.join(\", \")} }`\n\t\t\t\t\t: `${blue(\"Exports:\")} * (entire package)`,\n\t\t\t\t\"\",\n\t\t\t\t`${blue(\"Raw size:\")} ${formatBytes(result.rawSize)}`,\n\t\t\t\t`${blue(\"Gzip size:\")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})`,\n\t\t\t\t\"\",\n\t\t\t\tresult.externals.length > 0\n\t\t\t\t\t? `${blue(\"Externals:\")} ${result.externals.join(\", \")}`\n\t\t\t\t\t: `${blue(\"Externals:\")} ${green(\"none\")}`,\n\t\t\t\tresult.dependencies.length > 0\n\t\t\t\t\t? `${blue(\"Dependencies:\")} ${result.dependencies.join(\", \")}`\n\t\t\t\t\t: `${blue(\"Dependencies:\")} ${green(\"none\")}`,\n\t\t\t],\n\t\t\t{\n\t\t\t\tborderStyle: \"round\",\n\t\t\t\talign: \"left\",\n\t\t\t},\n\t\t);\n\n\t\tprocess.exit(0);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\tlog.error(`Failed to analyze bundle size: ${errorMessage}`);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"names":["Logger","kleur","checkBundleSize","formatBytes","parsePackageSpecifier","TREND_VERSION_COUNT","config","analyzeTrend","renderTrendGraph","selectTrendVersions","fetchPackageVersions","promptForVersion","flags","parameters","enabled","boring","log","main","packageName","error","showHelp","process","exit","additionalExternals","external","split","map","e","trim","filter","Boolean","exports","exportsArg","trendValue","trend","undefined","parsedCount","Number","parseInt","versionCount","isNaN","name","subpath","fullPackagePath","info","versions","registry","length","trendVersions","join","results","noExternal","gzipLevel","graphLines","line","errorMessage","Error","message","String","tags","selectedVersion","result","blue","green","printBox","packageVersion","rawSize","gzipSize","externals","dependencies","borderStyle","align"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/bundlecheck.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/* istanbul ignore file */\n\nimport { Logger } from \"@node-cli/logger\";\nimport kleur from \"kleur\";\nimport {\n\tcheckBundleSize,\n\tformatBytes,\n\tgetExternals,\n\tparsePackageSpecifier,\n} from \"./bundler.js\";\nimport {\n\tgetCachedResult,\n\tnormalizeCacheKey,\n\tsetCachedResult,\n} from \"./cache.js\";\nimport { normalizePlatform, TREND_VERSION_COUNT } from \"./defaults.js\";\nimport { config } from \"./parse.js\";\nimport {\n\tanalyzeTrend,\n\trenderTrendGraph,\n\tselectTrendVersions,\n} from \"./trend.js\";\nimport { fetchPackageVersions, promptForVersion } from \"./versions.js\";\n\nconst flags = config.flags;\nconst parameters = config.parameters;\n\n// Disable kleur colors when --boring flag is set.\nkleur.enabled = !flags?.boring;\n\nconst log = new Logger({\n\tboring: flags?.boring,\n});\n\n/**\n * Display bundle result in a formatted box.\n */\nfunction displayResult(\n\tresult: {\n\t\tpackageName: string;\n\t\tpackageVersion: string;\n\t\texports: string[];\n\t\trawSize: number;\n\t\tgzipSize: number | null;\n\t\tgzipLevel: number;\n\t\texternals: string[];\n\t\tdependencies: string[];\n\t\tplatform: \"browser\" | \"node\";\n\t},\n\tisAutoDetected: boolean,\n): void {\n\tconst blue = kleur.blue;\n\tconst green = kleur.green;\n\n\tconst platformLabel = result.platform === \"node\" ? \"node\" : \"browser\";\n\tconst platformNote = isAutoDetected ? \" (auto-detected)\" : \"\";\n\n\tlog.printBox(\n\t\t[\n\t\t\t`${blue(\"Package:\")} ${result.packageName} (${blue(\"version:\")} ${result.packageVersion})`,\n\t\t\tresult.exports.length > 0\n\t\t\t\t? `${blue(\"Exports:\")} { ${result.exports.join(\", \")} }`\n\t\t\t\t: `${blue(\"Exports:\")} * (entire package)`,\n\t\t\t\"\",\n\t\t\t`${blue(\"Raw size:\")} ${formatBytes(result.rawSize)}`,\n\t\t\tresult.gzipSize !== null\n\t\t\t\t? `${blue(\"Gzip size:\")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})`\n\t\t\t\t: `${blue(\"Gzip size:\")} N/A (not applicable for node platform)`,\n\t\t\t\"\",\n\t\t\tresult.externals.length > 0\n\t\t\t\t? `${blue(\"Externals:\")} ${result.externals.join(\", \")}`\n\t\t\t\t: `${blue(\"Externals:\")} ${green(\"none\")}`,\n\t\t\tresult.dependencies.length > 0\n\t\t\t\t? `${blue(\"Dependencies:\")} ${result.dependencies.join(\", \")}`\n\t\t\t\t: `${blue(\"Dependencies:\")} ${green(\"none\")}`,\n\t\t\t`${blue(\"Platform:\")} ${platformLabel}${platformNote}`,\n\t\t],\n\t\t{\n\t\t\tborderStyle: \"round\",\n\t\t\talign: \"left\",\n\t\t},\n\t);\n}\n\nasync function main() {\n\tlet packageName = parameters?.[\"0\"];\n\n\tif (!packageName) {\n\t\tlog.error(\"Package name is required\");\n\t\tconfig.showHelp?.();\n\t\tprocess.exit(1);\n\t}\n\n\t// Parse additional externals if provided (comma-separated).\n\tlet additionalExternals: string[] | undefined;\n\tif (flags?.external) {\n\t\tadditionalExternals = flags.external\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// Parse exports if provided (comma-separated).\n\tlet exports: string[] | undefined;\n\tconst exportsArg = parameters?.[\"1\"];\n\tif (exportsArg) {\n\t\texports = exportsArg\n\t\t\t.split(\",\")\n\t\t\t.map((e) => e.trim())\n\t\t\t.filter(Boolean);\n\t}\n\n\t// Normalize platform from flag (handles aliases like \"web\" → \"browser\").\n\tconst platform = normalizePlatform(flags?.platform);\n\n\t/**\n\t * If --trend flag is set, show bundle size trend across versions --trend alone\n\t * uses default (5), --trend N uses N versions.\n\t */\n\tconst trendValue = flags?.trend;\n\tif (trendValue !== undefined) {\n\t\tconst parsedCount = Number.parseInt(trendValue, 10);\n\t\tconst versionCount =\n\t\t\t!Number.isNaN(parsedCount) && parsedCount > 0\n\t\t\t\t? parsedCount\n\t\t\t\t: TREND_VERSION_COUNT;\n\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\t// Construct the full package path including subpath if present.\n\t\t\tconst fullPackagePath = subpath ? `${name}/${subpath}` : name;\n\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Select versions for trend.\n\t\t\tconst trendVersions = selectTrendVersions(versions, versionCount);\n\n\t\t\tlog.info(\n\t\t\t\t`Analyzing ${trendVersions.length} versions: ${trendVersions.join(\", \")}`,\n\t\t\t);\n\t\t\tlog.info(\"\");\n\n\t\t\tconst results = await analyzeTrend({\n\t\t\t\tpackageName: fullPackagePath,\n\t\t\t\tversions: trendVersions,\n\t\t\t\texports,\n\t\t\t\tadditionalExternals,\n\t\t\t\tnoExternal: flags?.noExternal,\n\t\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\t\tboring: flags?.boring,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t\tplatform,\n\t\t\t\tforce: flags?.force,\n\t\t\t});\n\n\t\t\tif (results.length === 0) {\n\t\t\t\tlog.error(\"Failed to analyze any versions\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\t// Render and display the trend graph.\n\t\t\tconst graphLines = renderTrendGraph(\n\t\t\t\tfullPackagePath,\n\t\t\t\tresults,\n\t\t\t\tflags?.boring,\n\t\t\t);\n\t\t\tfor (const line of graphLines) {\n\t\t\t\tlog.log(line);\n\t\t\t}\n\n\t\t\tprocess.exit(0);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to analyze trend: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// If --versions flag is set, fetch and prompt for version selection.\n\tif (flags?.versions) {\n\t\ttry {\n\t\t\tconst { name, subpath } = parsePackageSpecifier(packageName);\n\t\t\tlog.info(`\\nFetching available versions for ${name}...`);\n\n\t\t\tconst { versions, tags } = await fetchPackageVersions({\n\t\t\t\tpackageName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\n\t\t\tif (versions.length === 0) {\n\t\t\t\tlog.error(\"No versions found for this package\");\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tconst selectedVersion = await promptForVersion(name, versions, tags);\n\t\t\t// Rebuild specifier preserving any subpath.\n\t\t\tpackageName = subpath\n\t\t\t\t? `${name}/${subpath}@${selectedVersion}`\n\t\t\t\t: `${name}@${selectedVersion}`;\n\t\t\tlog.info(`\\nSelected: ${packageName}`);\n\t\t} catch (error) {\n\t\t\tconst errorMessage =\n\t\t\t\terror instanceof Error ? error.message : String(error);\n\t\t\tlog.error(`Failed to fetch versions: ${errorMessage}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tlog.info(`\\nAnalyzing bundle size for: ${packageName}`);\n\tif (exports && exports.length > 0) {\n\t\tlog.info(`Exports: { ${exports.join(\", \")} }`);\n\t}\n\n\ttry {\n\t\t// Parse package specifier to get name and version.\n\t\tconst { name: baseName, version: requestedVersion } =\n\t\t\tparsePackageSpecifier(packageName);\n\n\t\t// Resolve \"latest\" to actual version for cache key.\n\t\tlet resolvedVersion = requestedVersion;\n\t\tif (requestedVersion === \"latest\") {\n\t\t\tconst { tags } = await fetchPackageVersions({\n\t\t\t\tpackageName: baseName,\n\t\t\t\tregistry: flags?.registry,\n\t\t\t});\n\t\t\tresolvedVersion = tags.latest || requestedVersion;\n\t\t}\n\n\t\t// Compute externals for cache key (same logic as bundler).\n\t\tconst externals = getExternals(\n\t\t\tbaseName,\n\t\t\tadditionalExternals,\n\t\t\tflags?.noExternal,\n\t\t);\n\n\t\t/**\n\t\t * Build cache key.\n\t\t * NOTE: platform can be undefined (auto-detect), which is stored as \"auto\" in cache.\n\t\t */\n\t\tconst cacheKey = normalizeCacheKey({\n\t\t\tpackageName: baseName,\n\t\t\tversion: resolvedVersion,\n\t\t\texports,\n\t\t\tplatform,\n\t\t\tgzipLevel: flags?.gzipLevel ?? 5,\n\t\t\texternals,\n\t\t\tnoExternal: flags?.noExternal ?? false,\n\t\t});\n\n\t\t// Check cache (unless --force flag is set).\n\t\tif (!flags?.force) {\n\t\t\tconst cached = getCachedResult(cacheKey);\n\t\t\tif (cached) {\n\t\t\t\tlog.info(\"NOTE: Using cached results\\n\");\n\t\t\t\tdisplayResult(cached, platform === undefined);\n\t\t\t\tprocess.exit(0);\n\t\t\t}\n\t\t}\n\n\t\tlog.info(\"Please wait, installing and bundling...\\n\");\n\n\t\tconst result = await checkBundleSize({\n\t\t\tpackageName,\n\t\t\texports,\n\t\t\tadditionalExternals,\n\t\t\tnoExternal: flags?.noExternal,\n\t\t\tgzipLevel: flags?.gzipLevel,\n\t\t\tregistry: flags?.registry,\n\t\t\tplatform,\n\t\t});\n\n\t\t// Store result in cache.\n\t\tsetCachedResult(cacheKey, result);\n\n\t\tdisplayResult(result, platform === undefined);\n\n\t\tprocess.exit(0);\n\t} catch (error) {\n\t\tconst errorMessage = error instanceof Error ? error.message : String(error);\n\t\tlog.error(`Failed to analyze bundle size: ${errorMessage}`);\n\t\tprocess.exit(1);\n\t}\n}\n\nmain();\n"],"names":["Logger","kleur","checkBundleSize","formatBytes","getExternals","parsePackageSpecifier","getCachedResult","normalizeCacheKey","setCachedResult","normalizePlatform","TREND_VERSION_COUNT","config","analyzeTrend","renderTrendGraph","selectTrendVersions","fetchPackageVersions","promptForVersion","flags","parameters","enabled","boring","log","displayResult","result","isAutoDetected","blue","green","platformLabel","platform","platformNote","printBox","packageName","packageVersion","exports","length","join","rawSize","gzipSize","gzipLevel","externals","dependencies","borderStyle","align","main","error","showHelp","process","exit","additionalExternals","external","split","map","e","trim","filter","Boolean","exportsArg","trendValue","trend","undefined","parsedCount","Number","parseInt","versionCount","isNaN","name","subpath","fullPackagePath","info","versions","registry","trendVersions","results","noExternal","force","graphLines","line","errorMessage","Error","message","String","tags","selectedVersion","baseName","version","requestedVersion","resolvedVersion","latest","cacheKey","cached"],"mappings":"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"}
package/dist/bundler.d.ts CHANGED
@@ -4,7 +4,7 @@ export type ParsedPackage = {
4
4
  subpath?: string;
5
5
  };
6
6
  /**
7
- * Parse a package specifier to extract name, version, and subpath
7
+ * Parse a package specifier to extract name, version, and subpath.
8
8
  * Handles:
9
9
  * - @scope/package@1.0.0
10
10
  * - @scope/package/subpath@1.0.0
@@ -19,19 +19,27 @@ export type BundleOptions = {
19
19
  noExternal?: boolean;
20
20
  gzipLevel?: number;
21
21
  registry?: string;
22
+ /**
23
+ * Target platform. If undefined, auto-detects from package.json engines.
24
+ */
25
+ platform?: "browser" | "node";
22
26
  };
23
27
  export type BundleResult = {
24
28
  packageName: string;
25
29
  packageVersion: string;
26
30
  exports: string[];
27
31
  rawSize: number;
28
- gzipSize: number;
32
+ /**
33
+ * Gzip size in bytes, or null for node platform (gzip not applicable).
34
+ */
35
+ gzipSize: number | null;
29
36
  gzipLevel: number;
30
37
  externals: string[];
31
38
  dependencies: string[];
39
+ platform: "browser" | "node";
32
40
  };
33
41
  /**
34
- * Format bytes to human-readable string
42
+ * Format bytes to human-readable string.
35
43
  */
36
44
  export declare function formatBytes(bytes: number): string;
37
45
  export type EntryContentOptions = {
@@ -41,6 +49,10 @@ export type EntryContentOptions = {
41
49
  allSubpaths?: string[];
42
50
  exportToSubpath?: Map<string, string>;
43
51
  };
52
+ /**
53
+ * Get externals list based on options.
54
+ */
55
+ export declare function getExternals(packageName: string, externals?: string[], noExternal?: boolean): string[];
44
56
  export type PackageExports = Record<string, string | {
45
57
  import?: string;
46
58
  types?: string;
@@ -51,8 +63,9 @@ export type PackageInfo = {
51
63
  peerDependencies: Record<string, string>;
52
64
  exports: PackageExports | null;
53
65
  hasMainEntry: boolean;
66
+ engines: Record<string, string> | null;
54
67
  };
55
68
  /**
56
- * Check the bundle size of an npm package
69
+ * Check the bundle size of an npm package.
57
70
  */
58
71
  export declare function checkBundleSize(options: BundleOptions): Promise<BundleResult>;