@node-cli/bundlecheck 1.1.2 → 1.3.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,7 +13,10 @@ 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
17
+ - Custom npm registry support (for private registries)
16
18
  - Fast bundling using esbuild (with pnpm support)
19
+ - **Local caching** for faster repeated lookups (SQLite-based, max 100 entries)
17
20
 
18
21
  ## Installation
19
22
 
@@ -91,16 +94,19 @@ bundlecheck lodash "debounce,throttle"
91
94
 
92
95
  ### Options
93
96
 
94
- | Flag | Short | Description |
95
- | ------------------- | ----------- | ------------------------------------------------------- |
96
- | `--help` | `-h` | Display help instructions |
97
- | `--version` | `-v` | Output the current version |
98
- | `--versions` | `-V` | Choose from available package versions interactively |
99
- | `--trend [N]` | `-t [N]` | Show bundle size trend for N versions (default: 5) |
100
- | `--boring` | `-b` | Do not use color output |
101
- | `--gzipLevel <n>` | `-g <n>` | Gzip compression level (1-9, default: 5) |
102
- | `--external <pkgs>` | `-e <pkgs>` | Comma-separated additional packages to mark as external |
103
- | `--noExternal` | `-n` | Do not mark any packages as external |
97
+ | Flag | Short | Description |
98
+ | ------------------- | ----------- | ---------------------------------------------------------------- |
99
+ | `--help` | `-h` | Display help instructions |
100
+ | `--version` | `-v` | Output the current version |
101
+ | `--versions` | `-V` | Choose from available package versions interactively |
102
+ | `--trend [N]` | `-t [N]` | Show bundle size trend for N versions (default: 5) |
103
+ | `--boring` | `-b` | Do not use color output |
104
+ | `--gzipLevel <n>` | `-g <n>` | Gzip compression level (1-9, default: 5) |
105
+ | `--external <pkgs>` | `-e <pkgs>` | Comma-separated additional packages to mark as external |
106
+ | `--noExternal` | `-n` | Do not mark any packages as external |
107
+ | `--registry <url>` | `-r <url>` | Custom npm registry URL (default: registry.npmjs.org) |
108
+ | `--platform <name>` | `-p <name>` | Target platform: `auto` (default), `browser`, or `node` |
109
+ | `--force` | `-f` | Bypass cache and force re-fetch/re-calculation |
104
110
 
105
111
  ### Examples
106
112
 
@@ -132,6 +138,18 @@ bundlecheck lodash --trend
132
138
 
133
139
  # Show bundle size trend for 3 versions
134
140
  bundlecheck lodash --trend 3
141
+
142
+ # Use a custom npm registry
143
+ bundlecheck @myorg/private-pkg --registry https://npm.mycompany.com
144
+
145
+ # Check a Node.js package (explicit platform)
146
+ bundlecheck express --platform node
147
+
148
+ # Auto-detect platform (default behavior)
149
+ bundlecheck express # auto-detects "node" from package.json engines
150
+
151
+ # Bypass cache and force re-fetch
152
+ bundlecheck lodash --force
135
153
  ```
136
154
 
137
155
  ## How It Works
@@ -143,12 +161,88 @@ bundlecheck lodash --trend 3
143
161
  5. Reports raw and gzip sizes
144
162
  6. Cleans up temporary files
145
163
 
164
+ ## Platform Support
165
+
166
+ The `--platform` flag controls how the bundle is built:
167
+
168
+ - **`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`.
169
+ - **`browser`**: Builds for browser environments (also accepts aliases: `web`, `desktop`, `client`)
170
+ - **`node`**: Builds for Node.js environments (also accepts aliases: `server`, `nodejs`, `backend`)
171
+
172
+ When targeting **node** platform:
173
+ - Gzip size is not calculated (shows "N/A") since server-side code isn't typically served compressed over HTTP
174
+ - The bundle is optimized for Node.js built-ins
175
+
176
+ ```bash
177
+ # Auto-detect (recommended for most cases)
178
+ bundlecheck express # detects "node" from engines.node
179
+
180
+ # Explicit platform
181
+ bundlecheck lodash --platform browser
182
+ bundlecheck fastify -p server # "server" is an alias for "node"
183
+ ```
184
+
146
185
  ## Default Externals
147
186
 
148
187
  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.
149
188
 
150
189
  To include React/React-DOM in the bundle size calculation, use the `--no-external` flag.
151
190
 
191
+ ## Custom Registry
192
+
193
+ Use the `--registry` flag to check packages from private or alternative npm registries:
194
+
195
+ ```bash
196
+ # Private corporate registry
197
+ bundlecheck @myorg/myorg-ui-library --registry https://npm.mycompany.com
198
+
199
+ # Verdaccio local registry
200
+ bundlecheck my-local-pkg --registry http://localhost:4873
201
+
202
+ ```
203
+
204
+ Note: If the registry requires authentication, ensure your npm/pnpm is configured with the appropriate credentials (via `.npmrc` or environment variables).
205
+
206
+ ## Cache
207
+
208
+ Bundle size results are cached locally to speed up repeated lookups. The cache is stored in `~/.bundlecheck/cache.db` using SQLite.
209
+
210
+ ### How it works
211
+
212
+ - Results are cached based on: package name, version, exports, platform, gzip level, and externals configuration
213
+ - The cache holds up to **100 entries** (least recently used entries are evicted first)
214
+ - When you check a package, the CLI first looks for a cached result with matching parameters
215
+
216
+ ### Smart version matching
217
+
218
+ The cache uses **resolved versions**, not the requested specifier. This means:
219
+
220
+ ```bash
221
+ bundlecheck @mantine/core # Resolves "latest" to e.g. 8.0.0, caches as 8.0.0
222
+ bundlecheck @mantine/core@8.0.0 # Cache hit! Same resolved version
223
+ ```
224
+
225
+ 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.
226
+
227
+ ### Bypassing the cache
228
+
229
+ Use the `--force` flag to skip the cache and re-fetch/re-calculate:
230
+
231
+ ```bash
232
+ bundlecheck lodash --force # Always fetches fresh data
233
+ bundlecheck lodash -f # Short form
234
+ ```
235
+
236
+ ### Cache location
237
+
238
+ The cache database is stored at:
239
+
240
+ ```
241
+ ~/.bundlecheck/cache.db
242
+ ```
243
+
244
+ To clear the cache, simply delete this file or the `~/.bundlecheck` directory.
245
+
152
246
  ## License
153
247
 
154
248
  MIT - see [LICENSE](./LICENSE) for details.
@@ -1,8 +1,9 @@
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";
@@ -13,6 +14,28 @@ 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) {
@@ -31,6 +54,8 @@ async function main() {
31
54
  if (exportsArg) {
32
55
  exports = exportsArg.split(",").map((e)=>e.trim()).filter(Boolean);
33
56
  }
57
+ // Normalize platform from flag (handles aliases like "web" → "browser")
58
+ const platform = normalizePlatform(flags?.platform);
34
59
  // If --trend flag is set, show bundle size trend across versions
35
60
  // --trend alone uses default (5), --trend N uses N versions
36
61
  const trendValue = flags?.trend;
@@ -42,7 +67,10 @@ async function main() {
42
67
  // Construct the full package path including subpath if present
43
68
  const fullPackagePath = subpath ? `${name}/${subpath}` : name;
44
69
  log.info(`\nFetching available versions for ${name}...`);
45
- const { versions } = await fetchPackageVersions(packageName);
70
+ const { versions } = await fetchPackageVersions({
71
+ packageName,
72
+ registry: flags?.registry
73
+ });
46
74
  if (versions.length === 0) {
47
75
  log.error("No versions found for this package");
48
76
  process.exit(1);
@@ -58,7 +86,10 @@ async function main() {
58
86
  additionalExternals,
59
87
  noExternal: flags?.noExternal,
60
88
  gzipLevel: flags?.gzipLevel,
61
- boring: flags?.boring
89
+ boring: flags?.boring,
90
+ registry: flags?.registry,
91
+ platform,
92
+ force: flags?.force
62
93
  });
63
94
  if (results.length === 0) {
64
95
  log.error("Failed to analyze any versions");
@@ -81,7 +112,10 @@ async function main() {
81
112
  try {
82
113
  const { name, subpath } = parsePackageSpecifier(packageName);
83
114
  log.info(`\nFetching available versions for ${name}...`);
84
- const { versions, tags } = await fetchPackageVersions(packageName);
115
+ const { versions, tags } = await fetchPackageVersions({
116
+ packageName,
117
+ registry: flags?.registry
118
+ });
85
119
  if (versions.length === 0) {
86
120
  log.error("No versions found for this package");
87
121
  process.exit(1);
@@ -100,31 +134,53 @@ async function main() {
100
134
  if (exports && exports.length > 0) {
101
135
  log.info(`Exports: { ${exports.join(", ")} }`);
102
136
  }
103
- log.info("Please wait, installing and bundling...\n");
104
137
  try {
138
+ // Parse package specifier to get name and version
139
+ const { name: baseName, version: requestedVersion } = parsePackageSpecifier(packageName);
140
+ // Resolve "latest" to actual version for cache key
141
+ let resolvedVersion = requestedVersion;
142
+ if (requestedVersion === "latest") {
143
+ const { tags } = await fetchPackageVersions({
144
+ packageName: baseName,
145
+ registry: flags?.registry
146
+ });
147
+ resolvedVersion = tags.latest || requestedVersion;
148
+ }
149
+ // Compute externals for cache key (same logic as bundler)
150
+ const externals = getExternals(baseName, additionalExternals, flags?.noExternal);
151
+ // Build cache key
152
+ // Note: platform can be undefined (auto-detect), which is stored as "auto" in cache
153
+ const cacheKey = normalizeCacheKey({
154
+ packageName: baseName,
155
+ version: resolvedVersion,
156
+ exports,
157
+ platform,
158
+ gzipLevel: flags?.gzipLevel ?? 5,
159
+ externals,
160
+ noExternal: flags?.noExternal ?? false
161
+ });
162
+ // Check cache (unless --force flag is set)
163
+ if (!flags?.force) {
164
+ const cached = getCachedResult(cacheKey);
165
+ if (cached) {
166
+ log.info("NOTE: Using cached results\n");
167
+ displayResult(cached, platform === undefined);
168
+ process.exit(0);
169
+ }
170
+ }
171
+ log.info("Please wait, installing and bundling...\n");
105
172
  const result = await checkBundleSize({
106
173
  packageName,
107
174
  exports,
108
175
  additionalExternals,
109
176
  noExternal: flags?.noExternal,
110
- gzipLevel: flags?.gzipLevel
111
- });
112
- const blue = kleur.blue;
113
- const green = kleur.green;
114
- // Display results
115
- log.printBox([
116
- `${blue("Package:")} ${result.packageName} (${blue("version:")} ${result.packageVersion})`,
117
- result.exports.length > 0 ? `${blue("Exports:")} { ${result.exports.join(", ")} }` : `${blue("Exports:")} * (entire package)`,
118
- "",
119
- `${blue("Raw size:")} ${formatBytes(result.rawSize)}`,
120
- `${blue("Gzip size:")} ${formatBytes(result.gzipSize)} (level ${result.gzipLevel})`,
121
- "",
122
- result.externals.length > 0 ? `${blue("Externals:")} ${result.externals.join(", ")}` : `${blue("Externals:")} ${green("none")}`,
123
- result.dependencies.length > 0 ? `${blue("Dependencies:")} ${result.dependencies.join(", ")}` : `${blue("Dependencies:")} ${green("none")}`
124
- ], {
125
- borderStyle: "round",
126
- align: "left"
177
+ gzipLevel: flags?.gzipLevel,
178
+ registry: flags?.registry,
179
+ platform
127
180
  });
181
+ // Store result in cache
182
+ setCachedResult(cacheKey, result);
183
+ displayResult(result, platform === undefined);
128
184
  process.exit(0);
129
185
  } catch (error) {
130
186
  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(packageName);\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});\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(packageName);\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});\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","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// 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\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// Build cache key\n\t\t// Note: platform can be undefined (auto-detect), which is stored as \"auto\" in cache\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
@@ -18,16 +18,21 @@ export type BundleOptions = {
18
18
  additionalExternals?: string[];
19
19
  noExternal?: boolean;
20
20
  gzipLevel?: number;
21
+ registry?: string;
22
+ /** Target platform. If undefined, auto-detects from package.json engines */
23
+ platform?: "browser" | "node";
21
24
  };
22
25
  export type BundleResult = {
23
26
  packageName: string;
24
27
  packageVersion: string;
25
28
  exports: string[];
26
29
  rawSize: number;
27
- gzipSize: number;
30
+ /** Gzip size in bytes, or null for node platform (gzip not applicable) */
31
+ gzipSize: number | null;
28
32
  gzipLevel: number;
29
33
  externals: string[];
30
34
  dependencies: string[];
35
+ platform: "browser" | "node";
31
36
  };
32
37
  /**
33
38
  * Format bytes to human-readable string
@@ -40,6 +45,10 @@ export type EntryContentOptions = {
40
45
  allSubpaths?: string[];
41
46
  exportToSubpath?: Map<string, string>;
42
47
  };
48
+ /**
49
+ * Get externals list based on options
50
+ */
51
+ export declare function getExternals(packageName: string, externals?: string[], noExternal?: boolean): string[];
43
52
  export type PackageExports = Record<string, string | {
44
53
  import?: string;
45
54
  types?: string;
@@ -50,6 +59,7 @@ export type PackageInfo = {
50
59
  peerDependencies: Record<string, string>;
51
60
  exports: PackageExports | null;
52
61
  hasMainEntry: boolean;
62
+ engines: Record<string, string> | null;
53
63
  };
54
64
  /**
55
65
  * Check the bundle size of an npm package
package/dist/bundler.js CHANGED
@@ -122,16 +122,44 @@ const gzipAsync = promisify(zlib.gzip);
122
122
  }
123
123
  // Cache the result of pnpm availability check
124
124
  let usePnpm = null;
125
+ /**
126
+ * Validate and sanitize a registry URL to prevent command injection
127
+ * @param registry - The registry URL to validate
128
+ * @returns The sanitized URL or undefined if invalid
129
+ * @throws Error if the URL is invalid or contains potentially malicious characters
130
+ */ function validateRegistryUrl(registry) {
131
+ // Parse as URL to validate format
132
+ let url;
133
+ try {
134
+ url = new URL(registry);
135
+ } catch {
136
+ throw new Error(`Invalid registry URL: ${registry}. Must be a valid URL (e.g., https://registry.example.com)`);
137
+ }
138
+ // Only allow http and https protocols
139
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
140
+ throw new Error(`Invalid registry URL protocol: ${url.protocol}. Only http: and https: are allowed`);
141
+ }
142
+ // Return the sanitized URL (URL constructor normalizes it)
143
+ return url.toString();
144
+ }
125
145
  /**
126
146
  * Get the install command (pnpm preferred, npm fallback)
127
- */ function getInstallCommand() {
147
+ * @param registry - Optional custom npm registry URL
148
+ */ function getInstallCommand(registry) {
128
149
  if (usePnpm === null) {
129
150
  usePnpm = isPnpmAvailable();
130
151
  }
152
+ let registryArg = "";
153
+ if (registry) {
154
+ // Validate and sanitize the registry URL to prevent command injection
155
+ const sanitizedRegistry = validateRegistryUrl(registry);
156
+ // Quote the URL to handle any special characters safely
157
+ registryArg = ` --registry "${sanitizedRegistry}"`;
158
+ }
131
159
  if (usePnpm) {
132
- return "pnpm install --ignore-scripts --no-frozen-lockfile";
160
+ return `pnpm install --ignore-scripts --no-frozen-lockfile${registryArg}`;
133
161
  }
134
- return "npm install --legacy-peer-deps --ignore-scripts";
162
+ return `npm install --legacy-peer-deps --ignore-scripts${registryArg}`;
135
163
  }
136
164
  /**
137
165
  * Generate the entry file content based on package, subpath, and exports
@@ -180,7 +208,7 @@ let usePnpm = null;
180
208
  }
181
209
  /**
182
210
  * Get externals list based on options
183
- */ function getExternals(packageName, externals, noExternal) {
211
+ */ export function getExternals(packageName, externals, noExternal) {
184
212
  if (noExternal) {
185
213
  return [];
186
214
  }
@@ -220,7 +248,8 @@ let usePnpm = null;
220
248
  dependencies: pkgJson.dependencies || {},
221
249
  peerDependencies: pkgJson.peerDependencies || {},
222
250
  exports: pkgJson.exports || null,
223
- hasMainEntry
251
+ hasMainEntry,
252
+ engines: pkgJson.engines || null
224
253
  };
225
254
  }
226
255
  } catch {
@@ -231,7 +260,8 @@ let usePnpm = null;
231
260
  dependencies: {},
232
261
  peerDependencies: {},
233
262
  exports: null,
234
- hasMainEntry: true
263
+ hasMainEntry: true,
264
+ engines: null
235
265
  };
236
266
  }
237
267
  /**
@@ -326,7 +356,7 @@ let usePnpm = null;
326
356
  /**
327
357
  * Check the bundle size of an npm package
328
358
  */ export async function checkBundleSize(options) {
329
- const { packageName: packageSpecifier, exports, additionalExternals, noExternal, gzipLevel = 5 } = options;
359
+ const { packageName: packageSpecifier, exports, additionalExternals, noExternal, gzipLevel = 5, registry, platform: explicitPlatform } = options;
330
360
  // Parse the package specifier to extract name, version, and subpath
331
361
  const { name: packageName, version: requestedVersion, subpath } = parsePackageSpecifier(packageSpecifier);
332
362
  const tmpDir = createTempDir();
@@ -342,14 +372,22 @@ let usePnpm = null;
342
372
  };
343
373
  fs.writeFileSync(path.join(tmpDir, "package.json"), JSON.stringify(packageJson, null, 2));
344
374
  // Install the main package (try pnpm first, fallback to npm)
345
- const installCmd = getInstallCommand();
375
+ const installCmd = getInstallCommand(registry);
346
376
  execSync(installCmd, {
347
377
  cwd: tmpDir,
348
378
  stdio: "pipe"
349
379
  });
350
- // Get package info (version, dependencies, peer dependencies, exports)
380
+ // Get package info (version, dependencies, peer dependencies, exports, engines)
351
381
  const pkgInfo = getPackageInfo(tmpDir, packageName);
352
382
  const peerDepKeys = Object.keys(pkgInfo.peerDependencies);
383
+ // Determine platform: use explicit value if provided, otherwise auto-detect from engines
384
+ let platform = "browser";
385
+ if (explicitPlatform) {
386
+ platform = explicitPlatform;
387
+ } else if (pkgInfo.engines?.node && !pkgInfo.engines?.browser) {
388
+ // Package specifies node engine without browser - likely a Node.js package
389
+ platform = "node";
390
+ }
353
391
  // Collect all dependency names (prod + peer)
354
392
  const allDependencies = [
355
393
  ...new Set([
@@ -411,7 +449,7 @@ let usePnpm = null;
411
449
  bundle: true,
412
450
  write: false,
413
451
  format: "esm",
414
- platform: "browser",
452
+ platform,
415
453
  target: "es2020",
416
454
  minify: true,
417
455
  treeShaking: true,
@@ -421,11 +459,14 @@ let usePnpm = null;
421
459
  // Get raw size
422
460
  const bundleContent = result.outputFiles[0].contents;
423
461
  const rawSize = bundleContent.length;
424
- // Gzip the bundle
425
- const gzipped = await gzipAsync(Buffer.from(bundleContent), {
426
- level: gzipLevel
427
- });
428
- const gzipSize = gzipped.length;
462
+ // Gzip the bundle (only for browser platform - not relevant for Node.js)
463
+ let gzipSize = null;
464
+ if (platform === "browser") {
465
+ const gzipped = await gzipAsync(Buffer.from(bundleContent), {
466
+ level: gzipLevel
467
+ });
468
+ gzipSize = gzipped.length;
469
+ }
429
470
  // Determine the display name
430
471
  let displayName = packageName;
431
472
  if (resolvedSubpath) {
@@ -445,7 +486,8 @@ let usePnpm = null;
445
486
  gzipSize,
446
487
  gzipLevel,
447
488
  externals,
448
- dependencies: allDependencies
489
+ dependencies: allDependencies,
490
+ platform
449
491
  };
450
492
  } finally{
451
493
  cleanupTempDir(tmpDir);