@node-cli/bundlecheck 1.0.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/parse.js ADDED
@@ -0,0 +1,118 @@
1
+ /* istanbul ignore file */ import { parser } from "@node-cli/parser";
2
+ import { DEFAULT_EXTERNALS, defaultFlags } from "./defaults.js";
3
+ export const config = parser({
4
+ meta: import.meta,
5
+ examples: [
6
+ {
7
+ command: "bundlecheck lodash",
8
+ comment: "## Check the bundle size of the entire lodash package"
9
+ },
10
+ {
11
+ command: "bundlecheck lodash@4.17.0",
12
+ comment: "## Check a specific version of a package"
13
+ },
14
+ {
15
+ command: "bundlecheck @mantine/core",
16
+ comment: "## Check the bundle size of the entire @mantine/core package"
17
+ },
18
+ {
19
+ command: 'bundlecheck @mantine/core "ScrollArea,Button"',
20
+ comment: "## Check the bundle size of specific exports from @mantine/core"
21
+ },
22
+ {
23
+ command: "bundlecheck react --no-external",
24
+ comment: "## Check the bundle size of react itself (not marked as external)"
25
+ },
26
+ {
27
+ command: 'bundlecheck some-pkg --external "vue,svelte"',
28
+ comment: "## Add vue and svelte as additional external dependencies"
29
+ },
30
+ {
31
+ command: "bundlecheck lodash --gzip-level 6",
32
+ comment: "## Use a different gzip compression level (1-9)"
33
+ },
34
+ {
35
+ command: "bundlecheck lodash --versions",
36
+ comment: "## Choose from available versions interactively"
37
+ },
38
+ {
39
+ command: "bundlecheck lodash --trend",
40
+ comment: "## Show bundle size trend (default: 5 versions)"
41
+ },
42
+ {
43
+ command: "bundlecheck lodash --trend 3",
44
+ comment: "## Show bundle size trend for 3 versions"
45
+ }
46
+ ],
47
+ flags: {
48
+ gzipLevel: {
49
+ shortFlag: "g",
50
+ default: defaultFlags.gzipLevel,
51
+ description: "Gzip compression level (1-9, default: 5)",
52
+ type: "number"
53
+ },
54
+ external: {
55
+ shortFlag: "e",
56
+ default: defaultFlags.external,
57
+ description: `Comma-separated additional externals (default externals: ${DEFAULT_EXTERNALS.join(", ")})`,
58
+ type: "string"
59
+ },
60
+ noExternal: {
61
+ shortFlag: "n",
62
+ default: defaultFlags.noExternal,
63
+ description: "Do not mark any packages as external (useful for checking react/react-dom themselves)",
64
+ type: "boolean"
65
+ },
66
+ boring: {
67
+ shortFlag: "b",
68
+ default: defaultFlags.boring,
69
+ description: "Do not use color output",
70
+ type: "boolean"
71
+ },
72
+ help: {
73
+ shortFlag: "h",
74
+ description: "Display help instructions",
75
+ type: "boolean"
76
+ },
77
+ version: {
78
+ shortFlag: "v",
79
+ description: "Output the current version",
80
+ type: "boolean"
81
+ },
82
+ versions: {
83
+ shortFlag: "V",
84
+ default: defaultFlags.versions,
85
+ description: "Choose from available package versions interactively",
86
+ type: "boolean"
87
+ },
88
+ trend: {
89
+ shortFlag: "t",
90
+ description: "Show bundle size trend for N recent versions (default: 5)",
91
+ type: "string"
92
+ }
93
+ },
94
+ parameters: {
95
+ package: {
96
+ description: "The npm package to check (e.g., lodash, @mantine/core)"
97
+ },
98
+ exports: {
99
+ description: 'Comma-separated list of exports to check (e.g., "ScrollArea,Button")'
100
+ }
101
+ },
102
+ restrictions: [
103
+ {
104
+ exit: 1,
105
+ message: ()=>"Error: Package name is required",
106
+ test: (flags, parameters)=>!parameters?.["0"]
107
+ },
108
+ {
109
+ exit: 1,
110
+ message: ()=>"Error: Gzip level must be between 1 and 9",
111
+ test: (flags)=>flags.gzipLevel !== undefined && (flags.gzipLevel < 1 || flags.gzipLevel > 9)
112
+ }
113
+ ],
114
+ usage: true,
115
+ defaultFlags
116
+ });
117
+
118
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parse.ts"],"sourcesContent":["/* istanbul ignore file */\nimport { parser } from \"@node-cli/parser\";\nimport { DEFAULT_EXTERNALS, defaultFlags } from \"./defaults.js\";\n\nexport type Flags = {\n\tboring?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tversions?: boolean;\n\ttrend?: string;\n\tgzipLevel?: number;\n\texternal?: string;\n\tnoExternal?: boolean;\n};\n\nexport type Parameters = {\n\t[\"0\"]?: string; // package name\n\t[\"1\"]?: string; // exports (comma-separated)\n};\n\nexport type Configuration = {\n\tflags?: Flags;\n\tparameters?: Parameters;\n\tusage?: boolean;\n\tshowHelp?: () => void;\n};\n\nexport const config: Configuration = parser({\n\tmeta: import.meta,\n\texamples: [\n\t\t{\n\t\t\tcommand: \"bundlecheck lodash\",\n\t\t\tcomment: \"## Check the bundle size of the entire lodash package\",\n\t\t},\n\t\t{\n\t\t\tcommand: \"bundlecheck lodash@4.17.0\",\n\t\t\tcomment: \"## Check a specific version of a package\",\n\t\t},\n\t\t{\n\t\t\tcommand: \"bundlecheck @mantine/core\",\n\t\t\tcomment: \"## Check the bundle size of the entire @mantine/core package\",\n\t\t},\n\t\t{\n\t\t\tcommand: 'bundlecheck @mantine/core \"ScrollArea,Button\"',\n\t\t\tcomment:\n\t\t\t\t\"## Check the bundle size of specific exports from @mantine/core\",\n\t\t},\n\t\t{\n\t\t\tcommand: \"bundlecheck react --no-external\",\n\t\t\tcomment:\n\t\t\t\t\"## Check the bundle size of react itself (not marked as external)\",\n\t\t},\n\t\t{\n\t\t\tcommand: 'bundlecheck some-pkg --external \"vue,svelte\"',\n\t\t\tcomment: \"## Add vue and svelte as additional external dependencies\",\n\t\t},\n\t\t{\n\t\t\tcommand: \"bundlecheck lodash --gzip-level 6\",\n\t\t\tcomment: \"## Use a different gzip compression level (1-9)\",\n\t\t},\n\t\t{\n\t\t\tcommand: \"bundlecheck lodash --versions\",\n\t\t\tcomment: \"## Choose from available versions interactively\",\n\t\t},\n\t\t{\n\t\t\tcommand: \"bundlecheck lodash --trend\",\n\t\t\tcomment: \"## Show bundle size trend (default: 5 versions)\",\n\t\t},\n\t\t{\n\t\t\tcommand: \"bundlecheck lodash --trend 3\",\n\t\t\tcomment: \"## Show bundle size trend for 3 versions\",\n\t\t},\n\t],\n\tflags: {\n\t\tgzipLevel: {\n\t\t\tshortFlag: \"g\",\n\t\t\tdefault: defaultFlags.gzipLevel,\n\t\t\tdescription: \"Gzip compression level (1-9, default: 5)\",\n\t\t\ttype: \"number\",\n\t\t},\n\t\texternal: {\n\t\t\tshortFlag: \"e\",\n\t\t\tdefault: defaultFlags.external,\n\t\t\tdescription: `Comma-separated additional externals (default externals: ${DEFAULT_EXTERNALS.join(\", \")})`,\n\t\t\ttype: \"string\",\n\t\t},\n\t\tnoExternal: {\n\t\t\tshortFlag: \"n\",\n\t\t\tdefault: defaultFlags.noExternal,\n\t\t\tdescription:\n\t\t\t\t\"Do not mark any packages as external (useful for checking react/react-dom themselves)\",\n\t\t\ttype: \"boolean\",\n\t\t},\n\t\tboring: {\n\t\t\tshortFlag: \"b\",\n\t\t\tdefault: defaultFlags.boring,\n\t\t\tdescription: \"Do not use color output\",\n\t\t\ttype: \"boolean\",\n\t\t},\n\t\thelp: {\n\t\t\tshortFlag: \"h\",\n\t\t\tdescription: \"Display help instructions\",\n\t\t\ttype: \"boolean\",\n\t\t},\n\t\tversion: {\n\t\t\tshortFlag: \"v\",\n\t\t\tdescription: \"Output the current version\",\n\t\t\ttype: \"boolean\",\n\t\t},\n\t\tversions: {\n\t\t\tshortFlag: \"V\",\n\t\t\tdefault: defaultFlags.versions,\n\t\t\tdescription: \"Choose from available package versions interactively\",\n\t\t\ttype: \"boolean\",\n\t\t},\n\t\ttrend: {\n\t\t\tshortFlag: \"t\",\n\t\t\tdescription: \"Show bundle size trend for N recent versions (default: 5)\",\n\t\t\ttype: \"string\",\n\t\t},\n\t},\n\tparameters: {\n\t\tpackage: {\n\t\t\tdescription: \"The npm package to check (e.g., lodash, @mantine/core)\",\n\t\t},\n\t\texports: {\n\t\t\tdescription:\n\t\t\t\t'Comma-separated list of exports to check (e.g., \"ScrollArea,Button\")',\n\t\t},\n\t},\n\trestrictions: [\n\t\t{\n\t\t\texit: 1,\n\t\t\tmessage: () => \"Error: Package name is required\",\n\t\t\ttest: (flags, parameters) => !parameters?.[\"0\"],\n\t\t},\n\t\t{\n\t\t\texit: 1,\n\t\t\tmessage: () => \"Error: Gzip level must be between 1 and 9\",\n\t\t\ttest: (flags) =>\n\t\t\t\tflags.gzipLevel !== undefined &&\n\t\t\t\t(flags.gzipLevel < 1 || flags.gzipLevel > 9),\n\t\t},\n\t],\n\tusage: true,\n\tdefaultFlags,\n});\n"],"names":["parser","DEFAULT_EXTERNALS","defaultFlags","config","meta","examples","command","comment","flags","gzipLevel","shortFlag","default","description","type","external","join","noExternal","boring","help","version","versions","trend","parameters","package","exports","restrictions","exit","message","test","undefined","usage"],"mappings":"AAAA,wBAAwB,GACxB,SAASA,MAAM,QAAQ,mBAAmB;AAC1C,SAASC,iBAAiB,EAAEC,YAAY,QAAQ,gBAAgB;AAyBhE,OAAO,MAAMC,SAAwBH,OAAO;IAC3CI,MAAM;IACNC,UAAU;QACT;YACCC,SAAS;YACTC,SAAS;QACV;QACA;YACCD,SAAS;YACTC,SAAS;QACV;QACA;YACCD,SAAS;YACTC,SAAS;QACV;QACA;YACCD,SAAS;YACTC,SACC;QACF;QACA;YACCD,SAAS;YACTC,SACC;QACF;QACA;YACCD,SAAS;YACTC,SAAS;QACV;QACA;YACCD,SAAS;YACTC,SAAS;QACV;QACA;YACCD,SAAS;YACTC,SAAS;QACV;QACA;YACCD,SAAS;YACTC,SAAS;QACV;QACA;YACCD,SAAS;YACTC,SAAS;QACV;KACA;IACDC,OAAO;QACNC,WAAW;YACVC,WAAW;YACXC,SAAST,aAAaO,SAAS;YAC/BG,aAAa;YACbC,MAAM;QACP;QACAC,UAAU;YACTJ,WAAW;YACXC,SAAST,aAAaY,QAAQ;YAC9BF,aAAa,CAAC,yDAAyD,EAAEX,kBAAkBc,IAAI,CAAC,MAAM,CAAC,CAAC;YACxGF,MAAM;QACP;QACAG,YAAY;YACXN,WAAW;YACXC,SAAST,aAAac,UAAU;YAChCJ,aACC;YACDC,MAAM;QACP;QACAI,QAAQ;YACPP,WAAW;YACXC,SAAST,aAAae,MAAM;YAC5BL,aAAa;YACbC,MAAM;QACP;QACAK,MAAM;YACLR,WAAW;YACXE,aAAa;YACbC,MAAM;QACP;QACAM,SAAS;YACRT,WAAW;YACXE,aAAa;YACbC,MAAM;QACP;QACAO,UAAU;YACTV,WAAW;YACXC,SAAST,aAAakB,QAAQ;YAC9BR,aAAa;YACbC,MAAM;QACP;QACAQ,OAAO;YACNX,WAAW;YACXE,aAAa;YACbC,MAAM;QACP;IACD;IACAS,YAAY;QACXC,SAAS;YACRX,aAAa;QACd;QACAY,SAAS;YACRZ,aACC;QACF;IACD;IACAa,cAAc;QACb;YACCC,MAAM;YACNC,SAAS,IAAM;YACfC,MAAM,CAACpB,OAAOc,aAAe,CAACA,YAAY,CAAC,IAAI;QAChD;QACA;YACCI,MAAM;YACNC,SAAS,IAAM;YACfC,MAAM,CAACpB,QACNA,MAAMC,SAAS,KAAKoB,aACnBrB,CAAAA,MAAMC,SAAS,GAAG,KAAKD,MAAMC,SAAS,GAAG,CAAA;QAC5C;KACA;IACDqB,OAAO;IACP5B;AACD,GAAG"}
@@ -0,0 +1,27 @@
1
+ export type TrendResult = {
2
+ version: string;
3
+ rawSize: number;
4
+ gzipSize: number;
5
+ };
6
+ export type TrendOptions = {
7
+ packageName: string;
8
+ versions: string[];
9
+ exports?: string[];
10
+ additionalExternals?: string[];
11
+ noExternal?: boolean;
12
+ gzipLevel?: number;
13
+ boring?: boolean;
14
+ };
15
+ /**
16
+ * Select versions for trend analysis
17
+ * Returns the most recent versions (newest first)
18
+ */
19
+ export declare function selectTrendVersions(allVersions: string[], count?: number): string[];
20
+ /**
21
+ * Analyze bundle size trend across multiple versions
22
+ */
23
+ export declare function analyzeTrend(options: TrendOptions): Promise<TrendResult[]>;
24
+ /**
25
+ * Render a bar graph showing bundle size trend
26
+ */
27
+ export declare function renderTrendGraph(packageName: string, results: TrendResult[], boring?: boolean): string[];
package/dist/trend.js ADDED
@@ -0,0 +1,118 @@
1
+ import { Logger } from "@node-cli/logger";
2
+ import kleur from "kleur";
3
+ import { checkBundleSize, formatBytes } from "./bundler.js";
4
+ import { TREND_VERSION_COUNT } from "./defaults.js";
5
+ /**
6
+ * Select versions for trend analysis
7
+ * Returns the most recent versions (newest first)
8
+ */ export function selectTrendVersions(allVersions, count = TREND_VERSION_COUNT) {
9
+ return allVersions.slice(0, count);
10
+ }
11
+ /**
12
+ * Analyze bundle size trend across multiple versions
13
+ */ export async function analyzeTrend(options) {
14
+ const { packageName, versions, exports, additionalExternals, noExternal, gzipLevel, boring } = options;
15
+ const log = new Logger({
16
+ boring
17
+ });
18
+ const results = [];
19
+ for (const version of versions){
20
+ const versionedPackage = `${packageName}@${version}`;
21
+ log.info(` Checking ${version}...`);
22
+ try {
23
+ const result = await checkBundleSize({
24
+ packageName: versionedPackage,
25
+ exports,
26
+ additionalExternals,
27
+ noExternal,
28
+ gzipLevel
29
+ });
30
+ results.push({
31
+ version,
32
+ rawSize: result.rawSize,
33
+ gzipSize: result.gzipSize
34
+ });
35
+ } catch {
36
+ // Skip versions that fail to analyze
37
+ log.info(` Skipping ${version} (failed to analyze)`);
38
+ }
39
+ }
40
+ return results;
41
+ }
42
+ /**
43
+ * Render a bar graph showing bundle size trend
44
+ */ export function renderTrendGraph(packageName, results, boring = false) {
45
+ if (results.length === 0) {
46
+ return [
47
+ "No data to display"
48
+ ];
49
+ }
50
+ const lines = [];
51
+ // Color helper (respects boring flag)
52
+ const blue = (text)=>boring ? text : kleur.blue(text);
53
+ // Find min/max sizes for scaling (use min-max scaling to show differences)
54
+ const gzipSizes = results.map((r)=>r.gzipSize);
55
+ const rawSizes = results.map((r)=>r.rawSize);
56
+ const minGzipSize = Math.min(...gzipSizes);
57
+ const maxGzipSize = Math.max(...gzipSizes);
58
+ const minRawSize = Math.min(...rawSizes);
59
+ const maxRawSize = Math.max(...rawSizes);
60
+ // Find max version length for alignment
61
+ const maxVersionLen = Math.max(...results.map((r)=>r.version.length));
62
+ // Bar width (characters)
63
+ const barWidth = 30;
64
+ const minBarWidth = 10; // Minimum bar length for smallest value
65
+ // Helper to calculate bar length with min-max scaling
66
+ const calcBarLength = (value, min, max)=>{
67
+ if (max === min) {
68
+ return barWidth; // All values are the same
69
+ }
70
+ // Scale from minBarWidth to barWidth based on position between min and max
71
+ const ratio = (value - min) / (max - min);
72
+ return Math.round(minBarWidth + ratio * (barWidth - minBarWidth));
73
+ };
74
+ // Header
75
+ lines.push("");
76
+ lines.push(`${blue("Bundle Size:")} ${packageName}`);
77
+ lines.push("─".repeat(60));
78
+ lines.push("");
79
+ // Gzip size bars
80
+ lines.push(blue("Gzip Size:"));
81
+ for (const result of results){
82
+ const barLength = calcBarLength(result.gzipSize, minGzipSize, maxGzipSize);
83
+ const bar = "▇".repeat(barLength);
84
+ const padding = " ".repeat(maxVersionLen - result.version.length);
85
+ const sizeStr = formatBytes(result.gzipSize);
86
+ lines.push(` ${result.version}${padding} ${bar} ${sizeStr}`);
87
+ }
88
+ lines.push("");
89
+ // Raw size bars
90
+ lines.push(blue("Raw Size:"));
91
+ for (const result of results){
92
+ const barLength = calcBarLength(result.rawSize, minRawSize, maxRawSize);
93
+ const bar = "▇".repeat(barLength);
94
+ const padding = " ".repeat(maxVersionLen - result.version.length);
95
+ const sizeStr = formatBytes(result.rawSize);
96
+ lines.push(` ${result.version}${padding} ${bar} ${sizeStr}`);
97
+ }
98
+ lines.push("");
99
+ // Summary
100
+ const oldestResult = results[results.length - 1];
101
+ const newestResult = results[0];
102
+ if (results.length > 1) {
103
+ const gzipDiff = newestResult.gzipSize - oldestResult.gzipSize;
104
+ const gzipPercent = (gzipDiff / oldestResult.gzipSize * 100).toFixed(1);
105
+ const rawDiff = newestResult.rawSize - oldestResult.rawSize;
106
+ const rawPercent = (rawDiff / oldestResult.rawSize * 100).toFixed(1);
107
+ const gzipTrend = gzipDiff >= 0 ? `+${formatBytes(gzipDiff)}` : `-${formatBytes(Math.abs(gzipDiff))}`;
108
+ const rawTrend = rawDiff >= 0 ? `+${formatBytes(rawDiff)}` : `-${formatBytes(Math.abs(rawDiff))}`;
109
+ lines.push("─".repeat(60));
110
+ lines.push(`Change from ${oldestResult.version} to ${newestResult.version}:`);
111
+ lines.push(` ${blue("Gzip:")} ${gzipTrend} (${gzipDiff >= 0 ? "+" : ""}${gzipPercent}%)`);
112
+ lines.push(` ${blue("Raw:")} ${rawTrend} (${rawDiff >= 0 ? "+" : ""}${rawPercent}%)`);
113
+ }
114
+ lines.push("");
115
+ return lines;
116
+ }
117
+
118
+ //# sourceMappingURL=trend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/trend.ts"],"sourcesContent":["import { Logger } from \"@node-cli/logger\";\nimport kleur from \"kleur\";\nimport { checkBundleSize, formatBytes } from \"./bundler.js\";\nimport { TREND_VERSION_COUNT } from \"./defaults.js\";\n\nexport type TrendResult = {\n\tversion: string;\n\trawSize: number;\n\tgzipSize: number;\n};\n\nexport type TrendOptions = {\n\tpackageName: string;\n\tversions: string[];\n\texports?: string[];\n\tadditionalExternals?: string[];\n\tnoExternal?: boolean;\n\tgzipLevel?: number;\n\tboring?: boolean;\n};\n\n/**\n * Select versions for trend analysis\n * Returns the most recent versions (newest first)\n */\nexport function selectTrendVersions(\n\tallVersions: string[],\n\tcount: number = TREND_VERSION_COUNT,\n): string[] {\n\treturn allVersions.slice(0, count);\n}\n\n/**\n * Analyze bundle size trend across multiple versions\n */\nexport async function analyzeTrend(\n\toptions: TrendOptions,\n): Promise<TrendResult[]> {\n\tconst {\n\t\tpackageName,\n\t\tversions,\n\t\texports,\n\t\tadditionalExternals,\n\t\tnoExternal,\n\t\tgzipLevel,\n\t\tboring,\n\t} = options;\n\n\tconst log = new Logger({ boring });\n\tconst results: TrendResult[] = [];\n\n\tfor (const version of versions) {\n\t\tconst versionedPackage = `${packageName}@${version}`;\n\t\tlog.info(` Checking ${version}...`);\n\n\t\ttry {\n\t\t\tconst result = await checkBundleSize({\n\t\t\t\tpackageName: versionedPackage,\n\t\t\t\texports,\n\t\t\t\tadditionalExternals,\n\t\t\t\tnoExternal,\n\t\t\t\tgzipLevel,\n\t\t\t});\n\n\t\t\tresults.push({\n\t\t\t\tversion,\n\t\t\t\trawSize: result.rawSize,\n\t\t\t\tgzipSize: result.gzipSize,\n\t\t\t});\n\t\t} catch {\n\t\t\t// Skip versions that fail to analyze\n\t\t\tlog.info(` Skipping ${version} (failed to analyze)`);\n\t\t}\n\t}\n\n\treturn results;\n}\n\n/**\n * Render a bar graph showing bundle size trend\n */\nexport function renderTrendGraph(\n\tpackageName: string,\n\tresults: TrendResult[],\n\tboring: boolean = false,\n): string[] {\n\tif (results.length === 0) {\n\t\treturn [\"No data to display\"];\n\t}\n\n\tconst lines: string[] = [];\n\n\t// Color helper (respects boring flag)\n\tconst blue = (text: string) => (boring ? text : kleur.blue(text));\n\n\t// Find min/max sizes for scaling (use min-max scaling to show differences)\n\tconst gzipSizes = results.map((r) => r.gzipSize);\n\tconst rawSizes = results.map((r) => r.rawSize);\n\n\tconst minGzipSize = Math.min(...gzipSizes);\n\tconst maxGzipSize = Math.max(...gzipSizes);\n\tconst minRawSize = Math.min(...rawSizes);\n\tconst maxRawSize = Math.max(...rawSizes);\n\n\t// Find max version length for alignment\n\tconst maxVersionLen = Math.max(...results.map((r) => r.version.length));\n\n\t// Bar width (characters)\n\tconst barWidth = 30;\n\tconst minBarWidth = 10; // Minimum bar length for smallest value\n\n\t// Helper to calculate bar length with min-max scaling\n\tconst calcBarLength = (value: number, min: number, max: number): number => {\n\t\tif (max === min) {\n\t\t\treturn barWidth; // All values are the same\n\t\t}\n\t\t// Scale from minBarWidth to barWidth based on position between min and max\n\t\tconst ratio = (value - min) / (max - min);\n\t\treturn Math.round(minBarWidth + ratio * (barWidth - minBarWidth));\n\t};\n\n\t// Header\n\tlines.push(\"\");\n\tlines.push(`${blue(\"Bundle Size:\")} ${packageName}`);\n\tlines.push(\"─\".repeat(60));\n\tlines.push(\"\");\n\n\t// Gzip size bars\n\tlines.push(blue(\"Gzip Size:\"));\n\tfor (const result of results) {\n\t\tconst barLength = calcBarLength(result.gzipSize, minGzipSize, maxGzipSize);\n\t\tconst bar = \"▇\".repeat(barLength);\n\t\tconst padding = \" \".repeat(maxVersionLen - result.version.length);\n\t\tconst sizeStr = formatBytes(result.gzipSize);\n\t\tlines.push(` ${result.version}${padding} ${bar} ${sizeStr}`);\n\t}\n\n\tlines.push(\"\");\n\n\t// Raw size bars\n\tlines.push(blue(\"Raw Size:\"));\n\tfor (const result of results) {\n\t\tconst barLength = calcBarLength(result.rawSize, minRawSize, maxRawSize);\n\t\tconst bar = \"▇\".repeat(barLength);\n\t\tconst padding = \" \".repeat(maxVersionLen - result.version.length);\n\t\tconst sizeStr = formatBytes(result.rawSize);\n\t\tlines.push(` ${result.version}${padding} ${bar} ${sizeStr}`);\n\t}\n\n\tlines.push(\"\");\n\n\t// Summary\n\tconst oldestResult = results[results.length - 1];\n\tconst newestResult = results[0];\n\n\tif (results.length > 1) {\n\t\tconst gzipDiff = newestResult.gzipSize - oldestResult.gzipSize;\n\t\tconst gzipPercent = ((gzipDiff / oldestResult.gzipSize) * 100).toFixed(1);\n\t\tconst rawDiff = newestResult.rawSize - oldestResult.rawSize;\n\t\tconst rawPercent = ((rawDiff / oldestResult.rawSize) * 100).toFixed(1);\n\n\t\tconst gzipTrend =\n\t\t\tgzipDiff >= 0\n\t\t\t\t? `+${formatBytes(gzipDiff)}`\n\t\t\t\t: `-${formatBytes(Math.abs(gzipDiff))}`;\n\t\tconst rawTrend =\n\t\t\trawDiff >= 0\n\t\t\t\t? `+${formatBytes(rawDiff)}`\n\t\t\t\t: `-${formatBytes(Math.abs(rawDiff))}`;\n\n\t\tlines.push(\"─\".repeat(60));\n\t\tlines.push(\n\t\t\t`Change from ${oldestResult.version} to ${newestResult.version}:`,\n\t\t);\n\t\tlines.push(\n\t\t\t` ${blue(\"Gzip:\")} ${gzipTrend} (${gzipDiff >= 0 ? \"+\" : \"\"}${gzipPercent}%)`,\n\t\t);\n\t\tlines.push(\n\t\t\t` ${blue(\"Raw:\")} ${rawTrend} (${rawDiff >= 0 ? \"+\" : \"\"}${rawPercent}%)`,\n\t\t);\n\t}\n\n\tlines.push(\"\");\n\n\treturn lines;\n}\n"],"names":["Logger","kleur","checkBundleSize","formatBytes","TREND_VERSION_COUNT","selectTrendVersions","allVersions","count","slice","analyzeTrend","options","packageName","versions","exports","additionalExternals","noExternal","gzipLevel","boring","log","results","version","versionedPackage","info","result","push","rawSize","gzipSize","renderTrendGraph","length","lines","blue","text","gzipSizes","map","r","rawSizes","minGzipSize","Math","min","maxGzipSize","max","minRawSize","maxRawSize","maxVersionLen","barWidth","minBarWidth","calcBarLength","value","ratio","round","repeat","barLength","bar","padding","sizeStr","oldestResult","newestResult","gzipDiff","gzipPercent","toFixed","rawDiff","rawPercent","gzipTrend","abs","rawTrend"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export type NpmPackageInfo = {
2
+ versions: string[];
3
+ tags: Record<string, string>;
4
+ };
5
+ export declare function fetchPackageVersions(packageName: string): Promise<NpmPackageInfo>;
6
+ /**
7
+ * Prompt user to select a version from available versions
8
+ */
9
+ export declare function promptForVersion(packageName: string, versions: string[], tags: Record<string, string>): Promise<string>;
@@ -0,0 +1,66 @@
1
+ import select from "@inquirer/select";
2
+ import { rsort } from "semver";
3
+ import { parsePackageSpecifier } from "./bundler.js";
4
+ export async function fetchPackageVersions(packageName) {
5
+ // Parse the package specifier to get just the name (without version)
6
+ const { name } = parsePackageSpecifier(packageName);
7
+ const url = `https://registry.npmjs.org/${name}`;
8
+ const response = await fetch(url);
9
+ if (!response.ok) {
10
+ throw new Error(`Failed to fetch package info: ${response.statusText}`);
11
+ }
12
+ const data = await response.json();
13
+ // Get all versions sorted by semver (newest first)
14
+ const versions = rsort(Object.keys(data.versions || {}));
15
+ // Get dist-tags (latest, next, beta, etc.)
16
+ const tags = data["dist-tags"] || {};
17
+ return {
18
+ versions,
19
+ tags
20
+ };
21
+ }
22
+ /**
23
+ * Prompt user to select a version from available versions
24
+ */ export async function promptForVersion(packageName, versions, tags) {
25
+ // Build choices with tags highlighted
26
+ const taggedVersions = new Set(Object.values(tags));
27
+ const tagByVersion = Object.fromEntries(Object.entries(tags).map(([tag, ver])=>[
28
+ ver,
29
+ tag
30
+ ]));
31
+ // Limit to most recent 20 versions for usability, but include all tagged versions
32
+ const recentVersions = versions.slice(0, 20);
33
+ const displayVersions = [
34
+ ...new Set([
35
+ ...Object.values(tags),
36
+ ...recentVersions
37
+ ])
38
+ ];
39
+ // Sort so tagged versions appear first, then by version order
40
+ displayVersions.sort((a, b)=>{
41
+ const aTagged = taggedVersions.has(a);
42
+ const bTagged = taggedVersions.has(b);
43
+ if (aTagged && !bTagged) {
44
+ return -1;
45
+ }
46
+ if (!aTagged && bTagged) {
47
+ return 1;
48
+ }
49
+ return versions.indexOf(a) - versions.indexOf(b);
50
+ });
51
+ const choices = displayVersions.map((ver)=>{
52
+ const tag = tagByVersion[ver];
53
+ return {
54
+ name: tag ? `${ver} (${tag})` : ver,
55
+ value: ver
56
+ };
57
+ });
58
+ const selectedVersion = await select({
59
+ message: `Select a version for ${packageName}:`,
60
+ choices,
61
+ pageSize: 15
62
+ });
63
+ return selectedVersion;
64
+ }
65
+
66
+ //# sourceMappingURL=versions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/versions.ts"],"sourcesContent":["import select from \"@inquirer/select\";\nimport { rsort } from \"semver\";\nimport { parsePackageSpecifier } from \"./bundler.js\";\n\nexport type NpmPackageInfo = {\n\tversions: string[];\n\ttags: Record<string, string>;\n};\n\n/**\n * Fetch available versions for an npm package from the registry\n */\ntype NpmRegistryResponse = {\n\tversions?: Record<string, unknown>;\n\t\"dist-tags\"?: Record<string, string>;\n};\n\nexport async function fetchPackageVersions(\n\tpackageName: string,\n): Promise<NpmPackageInfo> {\n\t// Parse the package specifier to get just the name (without version)\n\tconst { name } = parsePackageSpecifier(packageName);\n\n\tconst url = `https://registry.npmjs.org/${name}`;\n\tconst response = await fetch(url);\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to fetch package info: ${response.statusText}`);\n\t}\n\n\tconst data = (await response.json()) as NpmRegistryResponse;\n\n\t// Get all versions sorted by semver (newest first)\n\tconst versions = rsort(Object.keys(data.versions || {}));\n\n\t// Get dist-tags (latest, next, beta, etc.)\n\tconst tags = data[\"dist-tags\"] || {};\n\n\treturn { versions, tags };\n}\n\n/**\n * Prompt user to select a version from available versions\n */\nexport async function promptForVersion(\n\tpackageName: string,\n\tversions: string[],\n\ttags: Record<string, string>,\n): Promise<string> {\n\t// Build choices with tags highlighted\n\tconst taggedVersions = new Set(Object.values(tags));\n\tconst tagByVersion = Object.fromEntries(\n\t\tObject.entries(tags).map(([tag, ver]) => [ver, tag]),\n\t);\n\n\t// Limit to most recent 20 versions for usability, but include all tagged versions\n\tconst recentVersions = versions.slice(0, 20);\n\tconst displayVersions = [\n\t\t...new Set([...Object.values(tags), ...recentVersions]),\n\t];\n\n\t// Sort so tagged versions appear first, then by version order\n\tdisplayVersions.sort((a, b) => {\n\t\tconst aTagged = taggedVersions.has(a);\n\t\tconst bTagged = taggedVersions.has(b);\n\t\tif (aTagged && !bTagged) {\n\t\t\treturn -1;\n\t\t}\n\t\tif (!aTagged && bTagged) {\n\t\t\treturn 1;\n\t\t}\n\t\treturn versions.indexOf(a) - versions.indexOf(b);\n\t});\n\n\tconst choices = displayVersions.map((ver) => {\n\t\tconst tag = tagByVersion[ver];\n\t\treturn {\n\t\t\tname: tag ? `${ver} (${tag})` : ver,\n\t\t\tvalue: ver,\n\t\t};\n\t});\n\n\tconst selectedVersion = await select({\n\t\tmessage: `Select a version for ${packageName}:`,\n\t\tchoices,\n\t\tpageSize: 15,\n\t});\n\n\treturn selectedVersion;\n}\n"],"names":["select","rsort","parsePackageSpecifier","fetchPackageVersions","packageName","name","url","response","fetch","ok","Error","statusText","data","json","versions","Object","keys","tags","promptForVersion","taggedVersions","Set","values","tagByVersion","fromEntries","entries","map","tag","ver","recentVersions","slice","displayVersions","sort","a","b","aTagged","has","bTagged","indexOf","choices","value","selectedVersion","message","pageSize"],"mappings":"AAAA,OAAOA,YAAY,mBAAmB;AACtC,SAASC,KAAK,QAAQ,SAAS;AAC/B,SAASC,qBAAqB,QAAQ,eAAe;AAerD,OAAO,eAAeC,qBACrBC,WAAmB;IAEnB,qEAAqE;IACrE,MAAM,EAAEC,IAAI,EAAE,GAAGH,sBAAsBE;IAEvC,MAAME,MAAM,CAAC,2BAA2B,EAAED,MAAM;IAChD,MAAME,WAAW,MAAMC,MAAMF;IAE7B,IAAI,CAACC,SAASE,EAAE,EAAE;QACjB,MAAM,IAAIC,MAAM,CAAC,8BAA8B,EAAEH,SAASI,UAAU,EAAE;IACvE;IAEA,MAAMC,OAAQ,MAAML,SAASM,IAAI;IAEjC,mDAAmD;IACnD,MAAMC,WAAWb,MAAMc,OAAOC,IAAI,CAACJ,KAAKE,QAAQ,IAAI,CAAC;IAErD,2CAA2C;IAC3C,MAAMG,OAAOL,IAAI,CAAC,YAAY,IAAI,CAAC;IAEnC,OAAO;QAAEE;QAAUG;IAAK;AACzB;AAEA;;CAEC,GACD,OAAO,eAAeC,iBACrBd,WAAmB,EACnBU,QAAkB,EAClBG,IAA4B;IAE5B,sCAAsC;IACtC,MAAME,iBAAiB,IAAIC,IAAIL,OAAOM,MAAM,CAACJ;IAC7C,MAAMK,eAAeP,OAAOQ,WAAW,CACtCR,OAAOS,OAAO,CAACP,MAAMQ,GAAG,CAAC,CAAC,CAACC,KAAKC,IAAI,GAAK;YAACA;YAAKD;SAAI;IAGpD,kFAAkF;IAClF,MAAME,iBAAiBd,SAASe,KAAK,CAAC,GAAG;IACzC,MAAMC,kBAAkB;WACpB,IAAIV,IAAI;eAAIL,OAAOM,MAAM,CAACJ;eAAUW;SAAe;KACtD;IAED,8DAA8D;IAC9DE,gBAAgBC,IAAI,CAAC,CAACC,GAAGC;QACxB,MAAMC,UAAUf,eAAegB,GAAG,CAACH;QACnC,MAAMI,UAAUjB,eAAegB,GAAG,CAACF;QACnC,IAAIC,WAAW,CAACE,SAAS;YACxB,OAAO,CAAC;QACT;QACA,IAAI,CAACF,WAAWE,SAAS;YACxB,OAAO;QACR;QACA,OAAOtB,SAASuB,OAAO,CAACL,KAAKlB,SAASuB,OAAO,CAACJ;IAC/C;IAEA,MAAMK,UAAUR,gBAAgBL,GAAG,CAAC,CAACE;QACpC,MAAMD,MAAMJ,YAAY,CAACK,IAAI;QAC7B,OAAO;YACNtB,MAAMqB,MAAM,GAAGC,IAAI,EAAE,EAAED,IAAI,CAAC,CAAC,GAAGC;YAChCY,OAAOZ;QACR;IACD;IAEA,MAAMa,kBAAkB,MAAMxC,OAAO;QACpCyC,SAAS,CAAC,qBAAqB,EAAErC,YAAY,CAAC,CAAC;QAC/CkC;QACAI,UAAU;IACX;IAEA,OAAOF;AACR"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@node-cli/bundlecheck",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "author": "Arno Versini",
6
+ "description": "CLI tool to check the bundle size of npm packages (like bundlephobia)",
7
+ "type": "module",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/bundler.js"
13
+ }
14
+ },
15
+ "bin": "dist/bundlecheck.js",
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "node": ">=20",
21
+ "scripts": {
22
+ "build": "npm-run-all --serial clean build:types build:js build:barrel",
23
+ "build:barrel": "barrelsby --delete --directory dist --pattern \"**/*.d.ts\" --name \"index.d\"",
24
+ "build:js": "swc --strip-leading-paths --source-maps --out-dir dist src",
25
+ "build:types": "tsc",
26
+ "clean": "rimraf dist types coverage",
27
+ "comments:fix": "comments --merge-line-comments 'src/**/*.ts'",
28
+ "lint": "biome lint src",
29
+ "lint:fix": "biome check src --write --no-errors-on-unmatched",
30
+ "test": "vitest run --globals",
31
+ "test:coverage": "vitest run --coverage --globals",
32
+ "test:watch": "vitest --globals",
33
+ "watch": "swc --strip-leading-paths --watch --out-dir dist src"
34
+ },
35
+ "dependencies": {
36
+ "@inquirer/select": "5.0.4",
37
+ "@node-cli/logger": "1.3.3",
38
+ "@node-cli/parser": "2.4.4",
39
+ "esbuild": "0.27.2",
40
+ "kleur": "4.1.5",
41
+ "semver": "7.7.1"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "devDependencies": {
47
+ "@node-cli/comments": "0.2.8",
48
+ "@vitest/coverage-v8": "4.0.16",
49
+ "vitest": "4.0.16"
50
+ },
51
+ "gitHead": "85988924e928a649037f2d11bd9c6d7ee0d298c9"
52
+ }