@node-cli/bundlesize 4.2.3 → 4.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
@@ -184,6 +184,64 @@ export default {
184
184
  };
185
185
  ```
186
186
 
187
+ #### Report with subgroup headers (multiple tables)
188
+
189
+ You can interleave header objects inside the `sizes` array to break the report output into multiple markdown tables. Each header object must contain a `header` property (any markdown heading is allowed). A sub-bundle total line will be printed after each group along with its diff to previous stats (if available), followed by the overall bundle size and status at the end.
190
+
191
+ ```js
192
+ export default {
193
+ report: {
194
+ header: "## Bundle Size With Groups",
195
+ previous: "stats/previous.json",
196
+ current: "stats/current.json"
197
+ },
198
+ sizes: [
199
+ { header: "### Core" },
200
+ { path: "dist/core.js", limit: "20 kB" },
201
+ { path: "dist/core-extra.js", limit: "10 kB" },
202
+ { header: "### Widgets" },
203
+ { path: "dist/widget-a.js", limit: "15 kB" },
204
+ { path: "dist/widget-b.js", limit: "15 kB" }
205
+ ]
206
+ };
207
+ ```
208
+
209
+ Example output:
210
+
211
+ ```
212
+ ## Bundle Size With Groups
213
+
214
+ ### Core
215
+
216
+ | Status | File | Size (Gzip) | Limits |
217
+ | --- | --- | --- | --- |
218
+ | ✅ | core.js | 12.34 KB (-1.2 KB -8.80%) | 20 kB |
219
+ | ✅ | core-extra.js | 3.21 KB | 10 kB |
220
+
221
+ Sub-bundle size: 15.55 KB (-1.2 KB -7.17%)
222
+
223
+
224
+ ### Widgets
225
+
226
+ | Status | File | Size (Gzip) | Limits |
227
+ | --- | --- | --- | --- |
228
+ | ✅ | widget-a.js | 5.00 KB (+500 B +10.84%) | 15 kB |
229
+ | ✅ | widget-b.js | 4.50 KB | 15 kB |
230
+
231
+ Sub-bundle size: 9.50 KB (+500 B +5.56%)
232
+
233
+
234
+ Overall bundle size: 25.05 KB (-700 B -2.72%)
235
+ Overall status: ✅
236
+ ```
237
+
238
+ Notes:
239
+
240
+ - If no header objects are present, the legacy single-table format is used (backwards compatible).
241
+ - Headers are rendered in the order they appear in `sizes`.
242
+ - Only size entries that resolve in the current stats are printed; missing ones are skipped silently.
243
+ - Sub-bundle diff lines only show percentage / size diff when previous stats for at least one file in the subgroup exist.
244
+
187
245
  ## Usage
188
246
 
189
247
  ### Print the stats at the command line
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /* v8 ignore start */ import { Logger } from "@node-cli/logger";
2
+ /* istanbul ignore file */ import { Logger } from "@node-cli/logger";
3
3
  import fs from "fs-extra";
4
4
  import { getRawStats } from "./getRawStats.js";
5
5
  import { config } from "./parse.js";
@@ -70,6 +70,6 @@ if (flags.type === "report") {
70
70
  }
71
71
  }
72
72
  log.error("Invalid type, please use 'size' or 'report'");
73
- process.exit(1); /* v8 ignore stop */
73
+ process.exit(1);
74
74
 
75
75
  //# sourceMappingURL=bundlesize.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundlesize.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/* v8 ignore start */\n\nimport { Logger } from \"@node-cli/logger\";\nimport fs from \"fs-extra\";\nimport { getRawStats } from \"./getRawStats.js\";\nimport { config } from \"./parse.js\";\nimport { reportStats } from \"./reportStats.js\";\nimport { IGNORE, STDOUT } from \"./utilities.js\";\n\nconst flags = config.flags;\n\nconst log = new Logger({\n\tboring: flags.boring,\n});\n\nif (flags.type === \"size\") {\n\ttry {\n\t\tconst result = await getRawStats({ flags });\n\n\t\tif (result.outputFile === IGNORE) {\n\t\t\tprocess.exit(result.exitCode);\n\t\t}\n\n\t\tif (result.exitMessage !== \"\") {\n\t\t\tlog.error(result.exitMessage);\n\t\t\tprocess.exit(result.exitCode);\n\t\t}\n\n\t\tif (result.outputFile === STDOUT) {\n\t\t\tlog.info(`Configuration: ${flags.configuration}`);\n\t\t\tlog.info(`Output: ${result.outputFile}`);\n\t\t\tlog.info(`Output prefix: ${result.prefix}`);\n\t\t\tlog.log(result.data);\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tfs.outputJsonSync(result.outputFile, result.data, { spaces: 2 });\n\t\t\t} catch (error) {\n\t\t\t\tlog.error(`Failed to write to file: ${error.message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t\tprocess.exit(result.exitCode);\n\t} catch (error) {\n\t\tlog.error(error);\n\t\tprocess.exit(1);\n\t}\n}\n\nif (flags.type === \"report\") {\n\ttry {\n\t\tconst result = await reportStats({ flags });\n\n\t\tif (result.exitMessage !== \"\") {\n\t\t\tlog.error(result.exitMessage);\n\t\t\tprocess.exit(result.exitCode);\n\t\t}\n\n\t\tif (result.outputFile === STDOUT) {\n\t\t\tlog.info(`Configuration: ${flags.configuration}`);\n\t\t\tlog.info(`Output: ${result.outputFile}`);\n\t\t\tlog.log(result.data);\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tfs.outputFileSync(result.outputFile, result.data);\n\t\t\t} catch (error) {\n\t\t\t\tlog.error(`Failed to write to file: ${error.message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t\tprocess.exit(result.exitCode);\n\t} catch (error) {\n\t\tlog.error(error);\n\t\tprocess.exit(1);\n\t}\n}\n\nlog.error(\"Invalid type, please use 'size' or 'report'\");\nprocess.exit(1);\n/* v8 ignore stop */\n"],"names":["Logger","fs","getRawStats","config","reportStats","IGNORE","STDOUT","flags","log","boring","type","result","outputFile","process","exit","exitCode","exitMessage","error","info","configuration","prefix","data","outputJsonSync","spaces","message","outputFileSync"],"mappings":";AAEA,mBAAmB,GAEnB,SAASA,MAAM,QAAQ,mBAAmB;AAC1C,OAAOC,QAAQ,WAAW;AAC1B,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,MAAM,QAAQ,aAAa;AACpC,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,MAAM,EAAEC,MAAM,QAAQ,iBAAiB;AAEhD,MAAMC,QAAQJ,OAAOI,KAAK;AAE1B,MAAMC,MAAM,IAAIR,OAAO;IACtBS,QAAQF,MAAME,MAAM;AACrB;AAEA,IAAIF,MAAMG,IAAI,KAAK,QAAQ;IAC1B,IAAI;QACH,MAAMC,SAAS,MAAMT,YAAY;YAAEK;QAAM;QAEzC,IAAII,OAAOC,UAAU,KAAKP,QAAQ;YACjCQ,QAAQC,IAAI,CAACH,OAAOI,QAAQ;QAC7B;QAEA,IAAIJ,OAAOK,WAAW,KAAK,IAAI;YAC9BR,IAAIS,KAAK,CAACN,OAAOK,WAAW;YAC5BH,QAAQC,IAAI,CAACH,OAAOI,QAAQ;QAC7B;QAEA,IAAIJ,OAAOC,UAAU,KAAKN,QAAQ;YACjCE,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEX,MAAMY,aAAa,EAAE;YAChDX,IAAIU,IAAI,CAAC,CAAC,QAAQ,EAAEP,OAAOC,UAAU,EAAE;YACvCJ,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEP,OAAOS,MAAM,EAAE;YAC1CZ,IAAIA,GAAG,CAACG,OAAOU,IAAI;QACpB,OAAO;YACN,IAAI;gBACHpB,GAAGqB,cAAc,CAACX,OAAOC,UAAU,EAAED,OAAOU,IAAI,EAAE;oBAAEE,QAAQ;gBAAE;YAC/D,EAAE,OAAON,OAAO;gBACfT,IAAIS,KAAK,CAAC,CAAC,yBAAyB,EAAEA,MAAMO,OAAO,EAAE;gBACrDX,QAAQC,IAAI,CAAC;YACd;QACD;QACAD,QAAQC,IAAI,CAACH,OAAOI,QAAQ;IAC7B,EAAE,OAAOE,OAAO;QACfT,IAAIS,KAAK,CAACA;QACVJ,QAAQC,IAAI,CAAC;IACd;AACD;AAEA,IAAIP,MAAMG,IAAI,KAAK,UAAU;IAC5B,IAAI;QACH,MAAMC,SAAS,MAAMP,YAAY;YAAEG;QAAM;QAEzC,IAAII,OAAOK,WAAW,KAAK,IAAI;YAC9BR,IAAIS,KAAK,CAACN,OAAOK,WAAW;YAC5BH,QAAQC,IAAI,CAACH,OAAOI,QAAQ;QAC7B;QAEA,IAAIJ,OAAOC,UAAU,KAAKN,QAAQ;YACjCE,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEX,MAAMY,aAAa,EAAE;YAChDX,IAAIU,IAAI,CAAC,CAAC,QAAQ,EAAEP,OAAOC,UAAU,EAAE;YACvCJ,IAAIA,GAAG,CAACG,OAAOU,IAAI;QACpB,OAAO;YACN,IAAI;gBACHpB,GAAGwB,cAAc,CAACd,OAAOC,UAAU,EAAED,OAAOU,IAAI;YACjD,EAAE,OAAOJ,OAAO;gBACfT,IAAIS,KAAK,CAAC,CAAC,yBAAyB,EAAEA,MAAMO,OAAO,EAAE;gBACrDX,QAAQC,IAAI,CAAC;YACd;QACD;QACAD,QAAQC,IAAI,CAACH,OAAOI,QAAQ;IAC7B,EAAE,OAAOE,OAAO;QACfT,IAAIS,KAAK,CAACA;QACVJ,QAAQC,IAAI,CAAC;IACd;AACD;AAEAN,IAAIS,KAAK,CAAC;AACVJ,QAAQC,IAAI,CAAC,IACb,kBAAkB"}
1
+ {"version":3,"sources":["../src/bundlesize.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/* istanbul ignore file */\n\nimport { Logger } from \"@node-cli/logger\";\nimport fs from \"fs-extra\";\nimport { getRawStats } from \"./getRawStats.js\";\nimport { config } from \"./parse.js\";\nimport { reportStats } from \"./reportStats.js\";\nimport { IGNORE, STDOUT } from \"./utilities.js\";\n\nconst flags = config.flags;\n\nconst log = new Logger({\n\tboring: flags.boring,\n});\n\nif (flags.type === \"size\") {\n\ttry {\n\t\tconst result = await getRawStats({ flags });\n\n\t\tif (result.outputFile === IGNORE) {\n\t\t\tprocess.exit(result.exitCode);\n\t\t}\n\n\t\tif (result.exitMessage !== \"\") {\n\t\t\tlog.error(result.exitMessage);\n\t\t\tprocess.exit(result.exitCode);\n\t\t}\n\n\t\tif (result.outputFile === STDOUT) {\n\t\t\tlog.info(`Configuration: ${flags.configuration}`);\n\t\t\tlog.info(`Output: ${result.outputFile}`);\n\t\t\tlog.info(`Output prefix: ${result.prefix}`);\n\t\t\tlog.log(result.data);\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tfs.outputJsonSync(result.outputFile, result.data, { spaces: 2 });\n\t\t\t} catch (error) {\n\t\t\t\tlog.error(`Failed to write to file: ${error.message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t\tprocess.exit(result.exitCode);\n\t} catch (error) {\n\t\tlog.error(error);\n\t\tprocess.exit(1);\n\t}\n}\n\nif (flags.type === \"report\") {\n\ttry {\n\t\tconst result = await reportStats({ flags });\n\n\t\tif (result.exitMessage !== \"\") {\n\t\t\tlog.error(result.exitMessage);\n\t\t\tprocess.exit(result.exitCode);\n\t\t}\n\n\t\tif (result.outputFile === STDOUT) {\n\t\t\tlog.info(`Configuration: ${flags.configuration}`);\n\t\t\tlog.info(`Output: ${result.outputFile}`);\n\t\t\tlog.log(result.data);\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tfs.outputFileSync(result.outputFile, result.data);\n\t\t\t} catch (error) {\n\t\t\t\tlog.error(`Failed to write to file: ${error.message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\t\t}\n\t\tprocess.exit(result.exitCode);\n\t} catch (error) {\n\t\tlog.error(error);\n\t\tprocess.exit(1);\n\t}\n}\n\nlog.error(\"Invalid type, please use 'size' or 'report'\");\nprocess.exit(1);\n"],"names":["Logger","fs","getRawStats","config","reportStats","IGNORE","STDOUT","flags","log","boring","type","result","outputFile","process","exit","exitCode","exitMessage","error","info","configuration","prefix","data","outputJsonSync","spaces","message","outputFileSync"],"mappings":";AAEA,wBAAwB,GAExB,SAASA,MAAM,QAAQ,mBAAmB;AAC1C,OAAOC,QAAQ,WAAW;AAC1B,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,MAAM,QAAQ,aAAa;AACpC,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,MAAM,EAAEC,MAAM,QAAQ,iBAAiB;AAEhD,MAAMC,QAAQJ,OAAOI,KAAK;AAE1B,MAAMC,MAAM,IAAIR,OAAO;IACtBS,QAAQF,MAAME,MAAM;AACrB;AAEA,IAAIF,MAAMG,IAAI,KAAK,QAAQ;IAC1B,IAAI;QACH,MAAMC,SAAS,MAAMT,YAAY;YAAEK;QAAM;QAEzC,IAAII,OAAOC,UAAU,KAAKP,QAAQ;YACjCQ,QAAQC,IAAI,CAACH,OAAOI,QAAQ;QAC7B;QAEA,IAAIJ,OAAOK,WAAW,KAAK,IAAI;YAC9BR,IAAIS,KAAK,CAACN,OAAOK,WAAW;YAC5BH,QAAQC,IAAI,CAACH,OAAOI,QAAQ;QAC7B;QAEA,IAAIJ,OAAOC,UAAU,KAAKN,QAAQ;YACjCE,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEX,MAAMY,aAAa,EAAE;YAChDX,IAAIU,IAAI,CAAC,CAAC,QAAQ,EAAEP,OAAOC,UAAU,EAAE;YACvCJ,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEP,OAAOS,MAAM,EAAE;YAC1CZ,IAAIA,GAAG,CAACG,OAAOU,IAAI;QACpB,OAAO;YACN,IAAI;gBACHpB,GAAGqB,cAAc,CAACX,OAAOC,UAAU,EAAED,OAAOU,IAAI,EAAE;oBAAEE,QAAQ;gBAAE;YAC/D,EAAE,OAAON,OAAO;gBACfT,IAAIS,KAAK,CAAC,CAAC,yBAAyB,EAAEA,MAAMO,OAAO,EAAE;gBACrDX,QAAQC,IAAI,CAAC;YACd;QACD;QACAD,QAAQC,IAAI,CAACH,OAAOI,QAAQ;IAC7B,EAAE,OAAOE,OAAO;QACfT,IAAIS,KAAK,CAACA;QACVJ,QAAQC,IAAI,CAAC;IACd;AACD;AAEA,IAAIP,MAAMG,IAAI,KAAK,UAAU;IAC5B,IAAI;QACH,MAAMC,SAAS,MAAMP,YAAY;YAAEG;QAAM;QAEzC,IAAII,OAAOK,WAAW,KAAK,IAAI;YAC9BR,IAAIS,KAAK,CAACN,OAAOK,WAAW;YAC5BH,QAAQC,IAAI,CAACH,OAAOI,QAAQ;QAC7B;QAEA,IAAIJ,OAAOC,UAAU,KAAKN,QAAQ;YACjCE,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEX,MAAMY,aAAa,EAAE;YAChDX,IAAIU,IAAI,CAAC,CAAC,QAAQ,EAAEP,OAAOC,UAAU,EAAE;YACvCJ,IAAIA,GAAG,CAACG,OAAOU,IAAI;QACpB,OAAO;YACN,IAAI;gBACHpB,GAAGwB,cAAc,CAACd,OAAOC,UAAU,EAAED,OAAOU,IAAI;YACjD,EAAE,OAAOJ,OAAO;gBACfT,IAAIS,KAAK,CAAC,CAAC,yBAAyB,EAAEA,MAAMO,OAAO,EAAE;gBACrDX,QAAQC,IAAI,CAAC;YACd;QACD;QACAD,QAAQC,IAAI,CAACH,OAAOI,QAAQ;IAC7B,EAAE,OAAOE,OAAO;QACfT,IAAIS,KAAK,CAACA;QACVJ,QAAQC,IAAI,CAAC;IACd;AACD;AAEAN,IAAIS,KAAK,CAAC;AACVJ,QAAQC,IAAI,CAAC"}
package/dist/defaults.js CHANGED
@@ -1,9 +1,9 @@
1
- /* v8 ignore start */ export const defaultFlags = {
1
+ /* istanbul ignore file */ export const defaultFlags = {
2
2
  boring: false,
3
3
  configuration: "",
4
4
  silent: false,
5
5
  type: "size",
6
6
  force: false
7
- }; /* v8 ignore stop */
7
+ };
8
8
 
9
9
  //# sourceMappingURL=defaults.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["/* v8 ignore start */\n\nexport const defaultFlags = {\n\tboring: false,\n\tconfiguration: \"\",\n\tsilent: false,\n\ttype: \"size\",\n\tforce: false,\n};\n/* v8 ignore stop */\n"],"names":["defaultFlags","boring","configuration","silent","type","force"],"mappings":"AAAA,mBAAmB,GAEnB,OAAO,MAAMA,eAAe;IAC3BC,QAAQ;IACRC,eAAe;IACfC,QAAQ;IACRC,MAAM;IACNC,OAAO;AACR,EAAE,CACF,kBAAkB"}
1
+ {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["/* istanbul ignore file */\n\nexport const defaultFlags = {\n\tboring: false,\n\tconfiguration: \"\",\n\tsilent: false,\n\ttype: \"size\",\n\tforce: false,\n};\n"],"names":["defaultFlags","boring","configuration","silent","type","force"],"mappings":"AAAA,wBAAwB,GAExB,OAAO,MAAMA,eAAe;IAC3BC,QAAQ;IACRC,eAAe;IACfC,QAAQ;IACRC,MAAM;IACNC,OAAO;AACR,EAAE"}
@@ -32,21 +32,27 @@ export const getRawStats = async ({ flags })=>{
32
32
  return result;
33
33
  }
34
34
  for (const artifact of configuration.sizes){
35
- const rootPath = artifact.path.startsWith("/") ? "" : dirname(configurationFile);
36
- const artifactPath = dirname(artifact.path);
37
- const hasHash = artifact.path.includes(HASH_KEY);
38
- const hasSemver = artifact.path.includes(SEMVER_KEY);
35
+ // Support header-only entries (group markers) that do not contribute to raw
36
+ // stats.
37
+ if (artifact.header) {
38
+ continue;
39
+ }
40
+ const sizeArtifact = artifact;
41
+ const rootPath = sizeArtifact.path.startsWith("/") ? "" : dirname(configurationFile);
42
+ const artifactPath = dirname(sizeArtifact.path);
43
+ const hasHash = sizeArtifact.path.includes(HASH_KEY);
44
+ const hasSemver = sizeArtifact.path.includes(SEMVER_KEY);
39
45
  if (hasSemver && hasHash) {
40
46
  result.exitCode = 1;
41
- result.exitMessage = `Invalid path: ${artifact.path}.\nCannot use ${HASH_KEY} and ${SEMVER_KEY} in the same path.`;
47
+ result.exitMessage = `Invalid path: ${sizeArtifact.path}.\nCannot use ${HASH_KEY} and ${SEMVER_KEY} in the same path.`;
42
48
  return result;
43
49
  }
44
- let location = artifact.path;
50
+ let location = sizeArtifact.path;
45
51
  if (hasHash) {
46
- location = artifact.path.replace(HASH_KEY, GLOB_HASH);
52
+ location = sizeArtifact.path.replace(HASH_KEY, GLOB_HASH);
47
53
  }
48
54
  if (hasSemver) {
49
- location = artifact.path.replace(SEMVER_KEY, GLOB_SEMVER);
55
+ location = sizeArtifact.path.replace(SEMVER_KEY, GLOB_SEMVER);
50
56
  }
51
57
  const fileGlob = join(rootPath, location);
52
58
  const files = glob.sync(fileGlob);
@@ -57,25 +63,25 @@ export const getRawStats = async ({ flags })=>{
57
63
  }
58
64
  if (files.length > 1) {
59
65
  result.exitCode = 1;
60
- result.exitMessage = `Multiple files found for: ${artifact.path}.\nPlease use a more specific path.`;
66
+ result.exitMessage = `Multiple files found for: ${sizeArtifact.path}.\nPlease use a more specific path.`;
61
67
  return result;
62
68
  }
63
69
  for (const file of files){
64
70
  const fileSize = statSync(file).size;
65
71
  const fileSizeGzip = gzipSizeFromFileSync(file);
66
- const passed = fileSizeGzip < bytes(artifact.limit);
72
+ const passed = fileSizeGzip < bytes(sizeArtifact.limit);
67
73
  if (passed === false) {
68
74
  result.pass = false;
69
75
  failed = true;
70
76
  }
71
- let index = hasHash || hasSemver ? artifact.path : join(artifactPath, basename(file));
72
- if (artifact.alias) {
73
- index = artifact.alias;
77
+ let index = hasHash || hasSemver ? sizeArtifact.path : join(artifactPath, basename(file));
78
+ if (sizeArtifact.alias) {
79
+ index = sizeArtifact.alias;
74
80
  }
75
81
  currentResults[index] = {
76
82
  fileSize,
77
83
  fileSizeGzip,
78
- limit: artifact.limit,
84
+ limit: sizeArtifact.limit,
79
85
  passed
80
86
  };
81
87
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/getRawStats.ts"],"sourcesContent":["import { statSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport bytes from \"bytes\";\nimport fs from \"fs-extra\";\nimport { glob } from \"glob\";\nimport {\n\tGLOB_HASH,\n\tGLOB_SEMVER,\n\tgetOutputFile,\n\tgzipSizeFromFileSync,\n\tHASH_KEY,\n\tIGNORE,\n\tSEMVER_KEY,\n\tSTDOUT,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\n\ntype SizesConfiguration = {\n\tlimit: string;\n\tpath: string;\n\talias?: string;\n};\n\ntype ReportStats = {\n\tdata: Record<string, unknown>;\n\texitCode: number;\n\texitMessage: string;\n\toutputFile: string;\n\tpass: boolean;\n\tprefix: string;\n};\n\nexport const getRawStats = async ({ flags }): Promise<ReportStats> => {\n\tconst result: ReportStats = {\n\t\tpass: true,\n\t\texitCode: 0,\n\t\texitMessage: \"\",\n\t\toutputFile: \"\",\n\t\tprefix: \"\",\n\t\tdata: {},\n\t};\n\tlet failed = false;\n\tconst isValidConfigResult = validateConfigurationFile(flags.configuration);\n\tif (isValidConfigResult.exitMessage !== \"\") {\n\t\treturn {\n\t\t\t...result,\n\t\t\t...isValidConfigResult,\n\t\t};\n\t}\n\tconst configurationFile = isValidConfigResult.data;\n\tconst outputFile = getOutputFile(flags.output);\n\tconst prefix = flags.prefix || \"0.0.0\";\n\tconst currentResults = {};\n\n\tconst configuration: { sizes: SizesConfiguration[] } = await import(\n\t\tconfigurationFile\n\t).then((m) => m.default);\n\n\tif (configuration.sizes === undefined) {\n\t\tresult.exitMessage = \"Invalid configuration file: missing sizes object!\";\n\t\tresult.exitCode = 1;\n\t\treturn result;\n\t}\n\n\tfor (const artifact of configuration.sizes) {\n\t\tconst rootPath = artifact.path.startsWith(\"/\")\n\t\t\t? \"\"\n\t\t\t: dirname(configurationFile);\n\t\tconst artifactPath = dirname(artifact.path);\n\t\tconst hasHash = artifact.path.includes(HASH_KEY);\n\t\tconst hasSemver = artifact.path.includes(SEMVER_KEY);\n\n\t\tif (hasSemver && hasHash) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Invalid path: ${artifact.path}.\\nCannot use ${HASH_KEY} and ${SEMVER_KEY} in the same path.`;\n\t\t\treturn result;\n\t\t}\n\n\t\tlet location = artifact.path;\n\t\tif (hasHash) {\n\t\t\tlocation = artifact.path.replace(HASH_KEY, GLOB_HASH);\n\t\t}\n\t\tif (hasSemver) {\n\t\t\tlocation = artifact.path.replace(SEMVER_KEY, GLOB_SEMVER);\n\t\t}\n\t\tconst fileGlob = join(rootPath, location);\n\t\tconst files = glob.sync(fileGlob);\n\n\t\tif (files.length === 0) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `File not found: ${fileGlob}`;\n\t\t\treturn result;\n\t\t}\n\n\t\tif (files.length > 1) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Multiple files found for: ${artifact.path}.\\nPlease use a more specific path.`;\n\t\t\treturn result;\n\t\t}\n\n\t\tfor (const file of files) {\n\t\t\tconst fileSize = statSync(file).size;\n\t\t\tconst fileSizeGzip = gzipSizeFromFileSync(file);\n\t\t\tconst passed = fileSizeGzip < bytes(artifact.limit);\n\n\t\t\tif (passed === false) {\n\t\t\t\tresult.pass = false;\n\t\t\t\tfailed = true;\n\t\t\t}\n\n\t\t\tlet index =\n\t\t\t\thasHash || hasSemver\n\t\t\t\t\t? artifact.path\n\t\t\t\t\t: join(artifactPath, basename(file));\n\t\t\tif (artifact.alias) {\n\t\t\t\tindex = artifact.alias;\n\t\t\t}\n\n\t\t\tcurrentResults[index] = {\n\t\t\t\tfileSize,\n\t\t\t\tfileSizeGzip,\n\t\t\t\tlimit: artifact.limit,\n\t\t\t\tpassed,\n\t\t\t};\n\t\t}\n\t}\n\n\tlet existingResults = {};\n\tif (outputFile !== STDOUT) {\n\t\ttry {\n\t\t\texistingResults = fs.readJsonSync(outputFile);\n\t\t\t/* v8 ignore next 6 */\n\t\t} catch {\n\t\t\t/**\n\t\t\t * There are no existing results, so we can ignore this error, and simply\n\t\t\t * write the current results to the output file.\n\t\t\t */\n\t\t}\n\t}\n\n\t/**\n\t * If the prefix already exists in the output file,\n\t * - if --force flag is used, overwrite the existing results\n\t * - if --force flag is not used, ignore the new results and\n\t * keep the existing ones.\n\t */\n\tif (existingResults[prefix] !== undefined && flags.force === false) {\n\t\tresult.outputFile = IGNORE;\n\t} else {\n\t\tresult.outputFile = outputFile;\n\t\texistingResults[prefix] = currentResults;\n\t}\n\n\tresult.prefix = prefix;\n\tresult.exitCode = flags.silent === true ? 0 : Number(failed);\n\tresult.data = existingResults;\n\treturn result;\n};\n"],"names":["statSync","basename","dirname","join","bytes","fs","glob","GLOB_HASH","GLOB_SEMVER","getOutputFile","gzipSizeFromFileSync","HASH_KEY","IGNORE","SEMVER_KEY","STDOUT","validateConfigurationFile","getRawStats","flags","result","pass","exitCode","exitMessage","outputFile","prefix","data","failed","isValidConfigResult","configuration","configurationFile","output","currentResults","then","m","default","sizes","undefined","artifact","rootPath","path","startsWith","artifactPath","hasHash","includes","hasSemver","location","replace","fileGlob","files","sync","length","file","fileSize","size","fileSizeGzip","passed","limit","index","alias","existingResults","readJsonSync","force","silent","Number"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/getRawStats.ts"],"sourcesContent":["import { statSync } from \"node:fs\";\nimport { basename, dirname, join } from \"node:path\";\nimport bytes from \"bytes\";\nimport fs from \"fs-extra\";\nimport { glob } from \"glob\";\nimport {\n\tGLOB_HASH,\n\tGLOB_SEMVER,\n\tgetOutputFile,\n\tgzipSizeFromFileSync,\n\tHASH_KEY,\n\tIGNORE,\n\tSEMVER_KEY,\n\tSTDOUT,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\n\ntype SizeEntry = {\n\tlimit: string;\n\tpath: string;\n\talias?: string;\n};\n\ntype HeaderEntry = { header: string };\n\ntype SizesConfiguration = SizeEntry | HeaderEntry;\n\ntype ReportStats = {\n\tdata: Record<string, unknown>;\n\texitCode: number;\n\texitMessage: string;\n\toutputFile: string;\n\tpass: boolean;\n\tprefix: string;\n};\n\nexport const getRawStats = async ({ flags }): Promise<ReportStats> => {\n\tconst result: ReportStats = {\n\t\tpass: true,\n\t\texitCode: 0,\n\t\texitMessage: \"\",\n\t\toutputFile: \"\",\n\t\tprefix: \"\",\n\t\tdata: {},\n\t};\n\tlet failed = false;\n\tconst isValidConfigResult = validateConfigurationFile(flags.configuration);\n\tif (isValidConfigResult.exitMessage !== \"\") {\n\t\treturn {\n\t\t\t...result,\n\t\t\t...isValidConfigResult,\n\t\t};\n\t}\n\tconst configurationFile = isValidConfigResult.data;\n\tconst outputFile = getOutputFile(flags.output);\n\tconst prefix = flags.prefix || \"0.0.0\";\n\tconst currentResults = {};\n\n\tconst configuration: { sizes: SizesConfiguration[] } = await import(\n\t\tconfigurationFile\n\t).then((m) => m.default);\n\n\tif (configuration.sizes === undefined) {\n\t\tresult.exitMessage = \"Invalid configuration file: missing sizes object!\";\n\t\tresult.exitCode = 1;\n\t\treturn result;\n\t}\n\n\tfor (const artifact of configuration.sizes) {\n\t\t// Support header-only entries (group markers) that do not contribute to raw\n\t\t// stats.\n\t\tif ((artifact as HeaderEntry).header) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst sizeArtifact = artifact as SizeEntry;\n\t\tconst rootPath = sizeArtifact.path.startsWith(\"/\")\n\t\t\t? \"\"\n\t\t\t: dirname(configurationFile);\n\t\tconst artifactPath = dirname(sizeArtifact.path);\n\t\tconst hasHash = sizeArtifact.path.includes(HASH_KEY);\n\t\tconst hasSemver = sizeArtifact.path.includes(SEMVER_KEY);\n\n\t\tif (hasSemver && hasHash) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Invalid path: ${sizeArtifact.path}.\\nCannot use ${HASH_KEY} and ${SEMVER_KEY} in the same path.`;\n\t\t\treturn result;\n\t\t}\n\n\t\tlet location = sizeArtifact.path;\n\t\tif (hasHash) {\n\t\t\tlocation = sizeArtifact.path.replace(HASH_KEY, GLOB_HASH);\n\t\t}\n\t\tif (hasSemver) {\n\t\t\tlocation = sizeArtifact.path.replace(SEMVER_KEY, GLOB_SEMVER);\n\t\t}\n\t\tconst fileGlob = join(rootPath, location);\n\t\tconst files = glob.sync(fileGlob);\n\n\t\tif (files.length === 0) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `File not found: ${fileGlob}`;\n\t\t\treturn result;\n\t\t}\n\n\t\tif (files.length > 1) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Multiple files found for: ${sizeArtifact.path}.\\nPlease use a more specific path.`;\n\t\t\treturn result;\n\t\t}\n\n\t\tfor (const file of files) {\n\t\t\tconst fileSize = statSync(file).size;\n\t\t\tconst fileSizeGzip = gzipSizeFromFileSync(file);\n\t\t\tconst passed = fileSizeGzip < bytes(sizeArtifact.limit);\n\n\t\t\tif (passed === false) {\n\t\t\t\tresult.pass = false;\n\t\t\t\tfailed = true;\n\t\t\t}\n\n\t\t\tlet index =\n\t\t\t\thasHash || hasSemver\n\t\t\t\t\t? sizeArtifact.path\n\t\t\t\t\t: join(artifactPath, basename(file));\n\t\t\tif (sizeArtifact.alias) {\n\t\t\t\tindex = sizeArtifact.alias;\n\t\t\t}\n\n\t\t\tcurrentResults[index] = {\n\t\t\t\tfileSize,\n\t\t\t\tfileSizeGzip,\n\t\t\t\tlimit: sizeArtifact.limit,\n\t\t\t\tpassed,\n\t\t\t};\n\t\t}\n\t}\n\n\tlet existingResults = {};\n\tif (outputFile !== STDOUT) {\n\t\ttry {\n\t\t\texistingResults = fs.readJsonSync(outputFile);\n\t\t\t/* v8 ignore next 6 */\n\t\t} catch {\n\t\t\t/**\n\t\t\t * There are no existing results, so we can ignore this error, and simply\n\t\t\t * write the current results to the output file.\n\t\t\t */\n\t\t}\n\t}\n\n\t/**\n\t * If the prefix already exists in the output file,\n\t * - if --force flag is used, overwrite the existing results\n\t * - if --force flag is not used, ignore the new results and\n\t * keep the existing ones.\n\t */\n\tif (existingResults[prefix] !== undefined && flags.force === false) {\n\t\tresult.outputFile = IGNORE;\n\t} else {\n\t\tresult.outputFile = outputFile;\n\t\texistingResults[prefix] = currentResults;\n\t}\n\n\tresult.prefix = prefix;\n\tresult.exitCode = flags.silent === true ? 0 : Number(failed);\n\tresult.data = existingResults;\n\treturn result;\n};\n"],"names":["statSync","basename","dirname","join","bytes","fs","glob","GLOB_HASH","GLOB_SEMVER","getOutputFile","gzipSizeFromFileSync","HASH_KEY","IGNORE","SEMVER_KEY","STDOUT","validateConfigurationFile","getRawStats","flags","result","pass","exitCode","exitMessage","outputFile","prefix","data","failed","isValidConfigResult","configuration","configurationFile","output","currentResults","then","m","default","sizes","undefined","artifact","header","sizeArtifact","rootPath","path","startsWith","artifactPath","hasHash","includes","hasSemver","location","replace","fileGlob","files","sync","length","file","fileSize","size","fileSizeGzip","passed","limit","index","alias","existingResults","readJsonSync","force","silent","Number"],"mappings":"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"}
package/dist/parse.js CHANGED
@@ -1,4 +1,4 @@
1
- /* v8 ignore start */ import { parser } from "@node-cli/parser";
1
+ /* istanbul ignore file */ import { parser } from "@node-cli/parser";
2
2
  import { defaultFlags } from "./defaults.js";
3
3
  export const config = parser({
4
4
  meta: import.meta,
@@ -56,6 +56,6 @@ export const config = parser({
56
56
  },
57
57
  usage: true,
58
58
  defaultFlags
59
- }); /* v8 ignore stop */
59
+ });
60
60
 
61
61
  //# sourceMappingURL=parse.js.map
package/dist/parse.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/parse.ts"],"sourcesContent":["/* v8 ignore start */\nimport { parser } from \"@node-cli/parser\";\nimport { defaultFlags } from \"./defaults.js\";\n\nexport type Flags = {\n\tboring?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tconfiguration?: string;\n\toutput?: string;\n\tprefix?: string;\n\tsilent?: boolean;\n\ttype?: \"size\" | \"report\";\n\tforce?: boolean;\n};\n\nexport type Configuration = {\n\tflags?: Flags;\n\tusage?: boolean;\n\tshowHelp?: () => void;\n};\n\nexport const config: Configuration = parser({\n\tmeta: import.meta,\n\texamples: [],\n\tflags: {\n\t\tforce: {\n\t\t\tshortFlag: \"f\",\n\t\t\tdescription: \"Overwrite existing results\",\n\t\t\ttype: \"boolean\",\n\t\t\tdefault: defaultFlags.force,\n\t\t},\n\t\ttype: {\n\t\t\tshortFlag: \"t\",\n\t\t\tdescription: \"Specify the type of output: size or report\",\n\t\t\ttype: \"string\",\n\t\t\tdefault: defaultFlags.type,\n\t\t},\n\t\tconfiguration: {\n\t\t\tshortFlag: \"c\",\n\t\t\tdescription: \"Specify a configuration file\",\n\t\t\ttype: \"string\",\n\t\t},\n\t\toutput: {\n\t\t\tshortFlag: \"o\",\n\t\t\tdescription: \"Specify the output file\",\n\t\t\ttype: \"string\",\n\t\t},\n\t\tprefix: {\n\t\t\tshortFlag: \"p\",\n\t\t\tdescription: \"Specify a prefix to use in the output file\",\n\t\t\ttype: \"string\",\n\t\t},\n\t\tsilent: {\n\t\t\tshortFlag: \"s\",\n\t\t\tdefault: defaultFlags.silent,\n\t\t\tdescription: \"Do not exit in error when a limit is exceeded\",\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},\n\tusage: true,\n\tdefaultFlags,\n});\n/* v8 ignore stop */\n"],"names":["parser","defaultFlags","config","meta","examples","flags","force","shortFlag","description","type","default","configuration","output","prefix","silent","boring","help","version","usage"],"mappings":"AAAA,mBAAmB,GACnB,SAASA,MAAM,QAAQ,mBAAmB;AAC1C,SAASC,YAAY,QAAQ,gBAAgB;AAoB7C,OAAO,MAAMC,SAAwBF,OAAO;IAC3CG,MAAM;IACNC,UAAU,EAAE;IACZC,OAAO;QACNC,OAAO;YACNC,WAAW;YACXC,aAAa;YACbC,MAAM;YACNC,SAAST,aAAaK,KAAK;QAC5B;QACAG,MAAM;YACLF,WAAW;YACXC,aAAa;YACbC,MAAM;YACNC,SAAST,aAAaQ,IAAI;QAC3B;QACAE,eAAe;YACdJ,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAG,QAAQ;YACPL,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAI,QAAQ;YACPN,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAK,QAAQ;YACPP,WAAW;YACXG,SAAST,aAAaa,MAAM;YAC5BN,aAAa;YACbC,MAAM;QACP;QACAM,QAAQ;YACPR,WAAW;YACXG,SAAST,aAAac,MAAM;YAC5BP,aAAa;YACbC,MAAM;QACP;QACAO,MAAM;YACLT,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAQ,SAAS;YACRV,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;IACD;IACAS,OAAO;IACPjB;AACD,GAAG,CACH,kBAAkB"}
1
+ {"version":3,"sources":["../src/parse.ts"],"sourcesContent":["/* istanbul ignore file */\nimport { parser } from \"@node-cli/parser\";\nimport { defaultFlags } from \"./defaults.js\";\n\nexport type Flags = {\n\tboring?: boolean;\n\thelp?: boolean;\n\tversion?: boolean;\n\tconfiguration?: string;\n\toutput?: string;\n\tprefix?: string;\n\tsilent?: boolean;\n\ttype?: \"size\" | \"report\";\n\tforce?: boolean;\n};\n\nexport type Configuration = {\n\tflags?: Flags;\n\tusage?: boolean;\n\tshowHelp?: () => void;\n};\n\nexport const config: Configuration = parser({\n\tmeta: import.meta,\n\texamples: [],\n\tflags: {\n\t\tforce: {\n\t\t\tshortFlag: \"f\",\n\t\t\tdescription: \"Overwrite existing results\",\n\t\t\ttype: \"boolean\",\n\t\t\tdefault: defaultFlags.force,\n\t\t},\n\t\ttype: {\n\t\t\tshortFlag: \"t\",\n\t\t\tdescription: \"Specify the type of output: size or report\",\n\t\t\ttype: \"string\",\n\t\t\tdefault: defaultFlags.type,\n\t\t},\n\t\tconfiguration: {\n\t\t\tshortFlag: \"c\",\n\t\t\tdescription: \"Specify a configuration file\",\n\t\t\ttype: \"string\",\n\t\t},\n\t\toutput: {\n\t\t\tshortFlag: \"o\",\n\t\t\tdescription: \"Specify the output file\",\n\t\t\ttype: \"string\",\n\t\t},\n\t\tprefix: {\n\t\t\tshortFlag: \"p\",\n\t\t\tdescription: \"Specify a prefix to use in the output file\",\n\t\t\ttype: \"string\",\n\t\t},\n\t\tsilent: {\n\t\t\tshortFlag: \"s\",\n\t\t\tdefault: defaultFlags.silent,\n\t\t\tdescription: \"Do not exit in error when a limit is exceeded\",\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},\n\tusage: true,\n\tdefaultFlags,\n});\n"],"names":["parser","defaultFlags","config","meta","examples","flags","force","shortFlag","description","type","default","configuration","output","prefix","silent","boring","help","version","usage"],"mappings":"AAAA,wBAAwB,GACxB,SAASA,MAAM,QAAQ,mBAAmB;AAC1C,SAASC,YAAY,QAAQ,gBAAgB;AAoB7C,OAAO,MAAMC,SAAwBF,OAAO;IAC3CG,MAAM;IACNC,UAAU,EAAE;IACZC,OAAO;QACNC,OAAO;YACNC,WAAW;YACXC,aAAa;YACbC,MAAM;YACNC,SAAST,aAAaK,KAAK;QAC5B;QACAG,MAAM;YACLF,WAAW;YACXC,aAAa;YACbC,MAAM;YACNC,SAAST,aAAaQ,IAAI;QAC3B;QACAE,eAAe;YACdJ,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAG,QAAQ;YACPL,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAI,QAAQ;YACPN,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAK,QAAQ;YACPP,WAAW;YACXG,SAAST,aAAaa,MAAM;YAC5BN,aAAa;YACbC,MAAM;QACP;QACAM,QAAQ;YACPR,WAAW;YACXG,SAAST,aAAac,MAAM;YAC5BP,aAAa;YACbC,MAAM;QACP;QACAO,MAAM;YACLT,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAQ,SAAS;YACRV,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;IACD;IACAS,OAAO;IACPjB;AACD,GAAG"}
@@ -1,6 +1,7 @@
1
1
  import { basename, dirname, join } from "node:path";
2
2
  import bytes from "bytes";
3
3
  import { addMDrow, formatFooter, getMostRecentVersion, getOutputFile, percentFormatter, readJSONFile, validateConfigurationFile } from "./utilities.js";
4
+ const isHeaderEntry = (entry)=>"header" in entry;
4
5
  export const reportStats = async ({ flags })=>{
5
6
  const result = {
6
7
  exitCode: 0,
@@ -37,7 +38,7 @@ export const reportStats = async ({ flags })=>{
37
38
  }
38
39
  previousVersion = getMostRecentVersion(previousStats.data);
39
40
  } catch {
40
- // nothing to declare officer.
41
+ // no previous stats available.
41
42
  }
42
43
  const currentVersion = getMostRecentVersion(currentStats.data);
43
44
  let limitReached = false;
@@ -60,27 +61,105 @@ export const reportStats = async ({ flags })=>{
60
61
  }
61
62
  ];
62
63
  const rowsMD = [];
63
- rowsMD.push(addMDrow({
64
- type: "header",
65
- columns
66
- }));
67
- for (const key of Object.keys(currentStats.data[currentVersion])){
64
+ const hasGroupHeaders = Boolean(configuration.sizes?.some(isHeaderEntry));
65
+ if (!hasGroupHeaders) {
66
+ rowsMD.push(addMDrow({
67
+ type: "header",
68
+ columns
69
+ }));
70
+ }
71
+ // Build ordered keys based on configuration.sizes if present.
72
+ const orderedKeys = [];
73
+ if (configuration.sizes && Array.isArray(configuration.sizes)) {
74
+ for (const entry of configuration.sizes){
75
+ if (isHeaderEntry(entry)) {
76
+ orderedKeys.push({
77
+ type: "header",
78
+ value: "",
79
+ header: entry.header
80
+ });
81
+ continue;
82
+ }
83
+ // entry is FileSizeEntry here.
84
+ if (entry.alias && currentStats.data[currentVersion][entry.alias]) {
85
+ orderedKeys.push({
86
+ type: "item",
87
+ value: entry.alias
88
+ });
89
+ } else if (currentStats.data[currentVersion][entry.path]) {
90
+ orderedKeys.push({
91
+ type: "item",
92
+ value: entry.path
93
+ });
94
+ }
95
+ }
96
+ }
97
+ if (orderedKeys.length === 0) {
98
+ for (const key of Object.keys(currentStats.data[currentVersion])){
99
+ orderedKeys.push({
100
+ type: "item",
101
+ value: key
102
+ });
103
+ }
104
+ }
105
+ let subGroupAccumulatedSize = 0;
106
+ let subGroupAccumulatedPrevSize = 0;
107
+ let hasSubGroup = false;
108
+ const flushSubGroup = ()=>{
109
+ if (!hasSubGroup) {
110
+ return;
111
+ }
112
+ const diff = subGroupAccumulatedSize - subGroupAccumulatedPrevSize;
113
+ let diffString = "";
114
+ if (diff !== 0 && subGroupAccumulatedPrevSize !== 0) {
115
+ const sign = diff > 0 ? "+" : "-";
116
+ diffString = ` (${sign}${bytes(Math.abs(diff), {
117
+ unitSeparator: " "
118
+ })} ${percentFormatter(diff / subGroupAccumulatedPrevSize)})`;
119
+ }
120
+ if (hasGroupHeaders) {
121
+ rowsMD.push(`\nSub-bundle size: ${bytes(subGroupAccumulatedSize, {
122
+ unitSeparator: " "
123
+ })}${diffString}`, "");
124
+ }
125
+ subGroupAccumulatedSize = 0;
126
+ subGroupAccumulatedPrevSize = 0;
127
+ hasSubGroup = false;
128
+ };
129
+ for (const entry of orderedKeys){
130
+ if (entry.type === "header") {
131
+ flushSubGroup();
132
+ rowsMD.push("", entry.header, "");
133
+ rowsMD.push(addMDrow({
134
+ type: "header",
135
+ columns
136
+ }));
137
+ continue;
138
+ }
139
+ const key = entry.value;
68
140
  const item = currentStats.data[currentVersion][key];
141
+ if (!item) {
142
+ continue;
143
+ }
69
144
  const name = basename(key);
70
145
  if (!item.passed) {
71
146
  limitReached = true;
72
147
  }
73
148
  let diffString = "";
149
+ let previousFileSizeGzip = 0;
74
150
  if (previousStats && previousVersion) {
75
151
  const previousFileStats = previousStats.data[previousVersion] && previousStats.data[previousVersion][key];
76
- const previousFileSizeGzip = previousFileStats?.fileSizeGzip || 0;
152
+ previousFileSizeGzip = previousFileStats?.fileSizeGzip || 0;
77
153
  const diff = item.fileSizeGzip - previousFileSizeGzip;
78
154
  overallDiff += diff;
79
155
  diffString = diff === 0 || diff === item.fileSizeGzip ? "" : ` (${diff > 0 ? "+" : "-"}${bytes(Math.abs(diff), {
80
156
  unitSeparator: " "
81
- })} ${percentFormatter(diff / previousFileSizeGzip)})`;
157
+ })} ${percentFormatter(previousFileSizeGzip === 0 ? 0 : diff / previousFileSizeGzip)})`;
82
158
  }
83
159
  totalGzipSize += item.fileSizeGzip;
160
+ subGroupAccumulatedSize += item.fileSizeGzip;
161
+ subGroupAccumulatedPrevSize += previousFileSizeGzip;
162
+ hasSubGroup = true;
84
163
  rowsMD.push(addMDrow({
85
164
  type: "row",
86
165
  passed: item.passed,
@@ -91,6 +170,7 @@ export const reportStats = async ({ flags })=>{
91
170
  columns
92
171
  }));
93
172
  }
173
+ flushSubGroup();
94
174
  const template = `
95
175
  ${headerString}
96
176
  ${rowsMD.join("\n")}
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reportStats.ts"],"sourcesContent":["import { basename, dirname, join } from \"node:path\";\nimport bytes from \"bytes\";\nimport type { FooterProperties } from \"./utilities.js\";\nimport {\n\taddMDrow,\n\tformatFooter,\n\tgetMostRecentVersion,\n\tgetOutputFile,\n\tpercentFormatter,\n\treadJSONFile,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\n\ntype ReportConfiguration = {\n\tcurrent: string;\n\tprevious: string;\n\n\theader?: string;\n\tfooter?: (arguments_: FooterProperties) => string;\n\tcolumns?: { [key: string]: string }[];\n};\n\ntype ReportCompare = {\n\tdata: string;\n\texitCode: number;\n\texitMessage: string;\n\toutputFile: string;\n};\n\nexport const reportStats = async ({ flags }): Promise<ReportCompare> => {\n\tconst result: ReportCompare = {\n\t\texitCode: 0,\n\t\texitMessage: \"\",\n\t\toutputFile: \"\",\n\t\tdata: \"\",\n\t};\n\tlet previousStats, previousVersion: string;\n\tconst isValidConfigResult = validateConfigurationFile(flags.configuration);\n\tif (isValidConfigResult.exitMessage !== \"\") {\n\t\treturn {\n\t\t\t...result,\n\t\t\t...isValidConfigResult,\n\t\t};\n\t}\n\tconst configurationFile = isValidConfigResult.data;\n\tconst outputFile = getOutputFile(flags.output);\n\tresult.outputFile = outputFile;\n\n\tconst configuration: { report: ReportConfiguration } = await import(\n\t\tconfigurationFile\n\t).then((m) => m.default);\n\n\tconst currentStats = readJSONFile(\n\t\tjoin(dirname(configurationFile), configuration.report.current),\n\t);\n\tif (currentStats.exitMessage !== \"\") {\n\t\treturn {\n\t\t\t...result,\n\t\t\t...currentStats,\n\t\t};\n\t}\n\n\ttry {\n\t\tpreviousStats = readJSONFile(\n\t\t\tjoin(dirname(configurationFile), configuration.report.previous),\n\t\t);\n\t\tif (previousStats.exitMessage !== \"\") {\n\t\t\treturn {\n\t\t\t\t...result,\n\t\t\t\t...previousStats,\n\t\t\t};\n\t\t}\n\t\tpreviousVersion = getMostRecentVersion(previousStats.data);\n\t} catch {\n\t\t// nothing to declare officer.\n\t}\n\tconst currentVersion = getMostRecentVersion(currentStats.data);\n\n\tlet limitReached = false;\n\tlet overallDiff = 0;\n\tlet totalGzipSize = 0;\n\n\tconst headerString = configuration.report.header || \"## Bundle Size\";\n\tconst footerFormatter = configuration.report.footer || formatFooter;\n\tconst columns = configuration.report.columns || [\n\t\t{ status: \"Status\" },\n\t\t{ file: \"File\" },\n\t\t{ size: \"Size (Gzip)\" },\n\t\t{ limits: \"Limits\" },\n\t];\n\n\tconst rowsMD = [];\n\trowsMD.push(addMDrow({ type: \"header\", columns }));\n\n\tfor (const key of Object.keys(currentStats.data[currentVersion])) {\n\t\tconst item = currentStats.data[currentVersion][key];\n\t\tconst name = basename(key);\n\t\tif (!item.passed) {\n\t\t\tlimitReached = true;\n\t\t}\n\n\t\tlet diffString = \"\";\n\t\tif (previousStats && previousVersion) {\n\t\t\tconst previousFileStats =\n\t\t\t\tpreviousStats.data[previousVersion] &&\n\t\t\t\tpreviousStats.data[previousVersion][key];\n\t\t\tconst previousFileSizeGzip = previousFileStats?.fileSizeGzip || 0;\n\t\t\tconst diff = item.fileSizeGzip - previousFileSizeGzip;\n\n\t\t\toverallDiff += diff;\n\t\t\tdiffString =\n\t\t\t\tdiff === 0 || diff === item.fileSizeGzip\n\t\t\t\t\t? \"\"\n\t\t\t\t\t: ` (${diff > 0 ? \"+\" : \"-\"}${bytes(Math.abs(diff), {\n\t\t\t\t\t\t\tunitSeparator: \" \",\n\t\t\t\t\t\t})} ${percentFormatter(diff / previousFileSizeGzip)})`;\n\t\t}\n\n\t\ttotalGzipSize += item.fileSizeGzip;\n\n\t\trowsMD.push(\n\t\t\taddMDrow({\n\t\t\t\ttype: \"row\",\n\t\t\t\tpassed: item.passed,\n\t\t\t\tname: name,\n\t\t\t\tsize: item.fileSizeGzip,\n\t\t\t\tdiff: diffString,\n\t\t\t\tlimit: item.limit,\n\t\t\t\tcolumns,\n\t\t\t}),\n\t\t);\n\t}\n\n\tconst template = `\n${headerString}\n${rowsMD.join(\"\\n\")}\n\n${footerFormatter({\n\tlimitReached,\n\toverallDiff,\n\ttotalGzipSize,\n})}\n`;\n\n\tif (limitReached) {\n\t\tresult.exitCode = 1;\n\t}\n\n\tresult.data = template;\n\n\treturn result;\n};\n"],"names":["basename","dirname","join","bytes","addMDrow","formatFooter","getMostRecentVersion","getOutputFile","percentFormatter","readJSONFile","validateConfigurationFile","reportStats","flags","result","exitCode","exitMessage","outputFile","data","previousStats","previousVersion","isValidConfigResult","configuration","configurationFile","output","then","m","default","currentStats","report","current","previous","currentVersion","limitReached","overallDiff","totalGzipSize","headerString","header","footerFormatter","footer","columns","status","file","size","limits","rowsMD","push","type","key","Object","keys","item","name","passed","diffString","previousFileStats","previousFileSizeGzip","fileSizeGzip","diff","Math","abs","unitSeparator","limit","template"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/reportStats.ts"],"sourcesContent":["import { basename, dirname, join } from \"node:path\";\nimport bytes from \"bytes\";\nimport type { FooterProperties } from \"./utilities.js\";\nimport {\n\taddMDrow,\n\tformatFooter,\n\tgetMostRecentVersion,\n\tgetOutputFile,\n\tpercentFormatter,\n\treadJSONFile,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\n\ntype ReportConfiguration = {\n\tcurrent: string;\n\tprevious: string;\n\theader?: string;\n\tfooter?: (arguments_: FooterProperties) => string;\n\tcolumns?: { [key: string]: string }[];\n};\n\ninterface HeaderSizeEntry {\n\theader: string;\n}\ninterface FileSizeEntry {\n\tpath: string;\n\tlimit: string;\n\talias?: string;\n}\ntype SizesConfiguration = Array<HeaderSizeEntry | FileSizeEntry>;\n\nconst isHeaderEntry = (\n\tentry: HeaderSizeEntry | FileSizeEntry,\n): entry is HeaderSizeEntry => \"header\" in entry;\n\ntype ReportCompare = {\n\tdata: string;\n\texitCode: number;\n\texitMessage: string;\n\toutputFile: string;\n};\n\nexport const reportStats = async ({ flags }): Promise<ReportCompare> => {\n\tconst result: ReportCompare = {\n\t\texitCode: 0,\n\t\texitMessage: \"\",\n\t\toutputFile: \"\",\n\t\tdata: \"\",\n\t};\n\n\tlet previousStats, previousVersion: string;\n\tconst isValidConfigResult = validateConfigurationFile(flags.configuration);\n\tif (isValidConfigResult.exitMessage !== \"\") {\n\t\treturn { ...result, ...isValidConfigResult };\n\t}\n\n\tconst configurationFile = isValidConfigResult.data;\n\tconst outputFile = getOutputFile(flags.output);\n\tresult.outputFile = outputFile;\n\n\tconst configuration: {\n\t\treport: ReportConfiguration;\n\t\tsizes?: SizesConfiguration;\n\t} = await import(configurationFile).then((m) => m.default);\n\n\tconst currentStats = readJSONFile(\n\t\tjoin(dirname(configurationFile), configuration.report.current),\n\t);\n\tif (currentStats.exitMessage !== \"\") {\n\t\treturn { ...result, ...currentStats };\n\t}\n\n\ttry {\n\t\tpreviousStats = readJSONFile(\n\t\t\tjoin(dirname(configurationFile), configuration.report.previous),\n\t\t);\n\t\tif (previousStats.exitMessage !== \"\") {\n\t\t\treturn { ...result, ...previousStats };\n\t\t}\n\t\tpreviousVersion = getMostRecentVersion(previousStats.data);\n\t} catch {\n\t\t// no previous stats available.\n\t}\n\n\tconst currentVersion = getMostRecentVersion(currentStats.data);\n\n\tlet limitReached = false;\n\tlet overallDiff = 0;\n\tlet totalGzipSize = 0;\n\n\tconst headerString = configuration.report.header || \"## Bundle Size\";\n\tconst footerFormatter = configuration.report.footer || formatFooter;\n\tconst columns = configuration.report.columns || [\n\t\t{ status: \"Status\" },\n\t\t{ file: \"File\" },\n\t\t{ size: \"Size (Gzip)\" },\n\t\t{ limits: \"Limits\" },\n\t];\n\n\tconst rowsMD: string[] = [];\n\tconst hasGroupHeaders = Boolean(configuration.sizes?.some(isHeaderEntry));\n\tif (!hasGroupHeaders) {\n\t\trowsMD.push(addMDrow({ type: \"header\", columns }));\n\t}\n\n\t// Build ordered keys based on configuration.sizes if present.\n\tconst orderedKeys: Array<{\n\t\ttype: \"header\" | \"item\";\n\t\tvalue: string;\n\t\theader?: string;\n\t}> = [];\n\tif (configuration.sizes && Array.isArray(configuration.sizes)) {\n\t\tfor (const entry of configuration.sizes) {\n\t\t\tif (isHeaderEntry(entry)) {\n\t\t\t\torderedKeys.push({ type: \"header\", value: \"\", header: entry.header });\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// entry is FileSizeEntry here.\n\t\t\tif (entry.alias && currentStats.data[currentVersion][entry.alias]) {\n\t\t\t\torderedKeys.push({ type: \"item\", value: entry.alias });\n\t\t\t} else if (currentStats.data[currentVersion][entry.path]) {\n\t\t\t\torderedKeys.push({ type: \"item\", value: entry.path });\n\t\t\t}\n\t\t}\n\t}\n\n\tif (orderedKeys.length === 0) {\n\t\tfor (const key of Object.keys(currentStats.data[currentVersion])) {\n\t\t\torderedKeys.push({ type: \"item\", value: key });\n\t\t}\n\t}\n\n\tlet subGroupAccumulatedSize = 0;\n\tlet subGroupAccumulatedPrevSize = 0;\n\tlet hasSubGroup = false;\n\tconst flushSubGroup = () => {\n\t\tif (!hasSubGroup) {\n\t\t\treturn;\n\t\t}\n\t\tconst diff = subGroupAccumulatedSize - subGroupAccumulatedPrevSize;\n\t\tlet diffString = \"\";\n\t\tif (diff !== 0 && subGroupAccumulatedPrevSize !== 0) {\n\t\t\tconst sign = diff > 0 ? \"+\" : \"-\";\n\t\t\tdiffString = ` (${sign}${bytes(Math.abs(diff), { unitSeparator: \" \" })} ${percentFormatter(\n\t\t\t\tdiff / subGroupAccumulatedPrevSize,\n\t\t\t)})`;\n\t\t}\n\t\tif (hasGroupHeaders) {\n\t\t\trowsMD.push(\n\t\t\t\t`\\nSub-bundle size: ${bytes(subGroupAccumulatedSize, {\n\t\t\t\t\tunitSeparator: \" \",\n\t\t\t\t})}${diffString}`,\n\t\t\t\t\"\",\n\t\t\t);\n\t\t}\n\t\tsubGroupAccumulatedSize = 0;\n\t\tsubGroupAccumulatedPrevSize = 0;\n\t\thasSubGroup = false;\n\t};\n\n\tfor (const entry of orderedKeys) {\n\t\tif (entry.type === \"header\") {\n\t\t\tflushSubGroup();\n\t\t\trowsMD.push(\"\", entry.header as string, \"\");\n\t\t\trowsMD.push(addMDrow({ type: \"header\", columns }));\n\t\t\tcontinue;\n\t\t}\n\t\tconst key = entry.value;\n\t\tconst item = currentStats.data[currentVersion][key];\n\t\tif (!item) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst name = basename(key);\n\t\tif (!item.passed) {\n\t\t\tlimitReached = true;\n\t\t}\n\n\t\tlet diffString = \"\";\n\t\tlet previousFileSizeGzip = 0;\n\t\tif (previousStats && previousVersion) {\n\t\t\tconst previousFileStats =\n\t\t\t\tpreviousStats.data[previousVersion] &&\n\t\t\t\tpreviousStats.data[previousVersion][key];\n\t\t\tpreviousFileSizeGzip = previousFileStats?.fileSizeGzip || 0;\n\t\t\tconst diff = item.fileSizeGzip - previousFileSizeGzip;\n\t\t\toverallDiff += diff;\n\t\t\tdiffString =\n\t\t\t\tdiff === 0 || diff === item.fileSizeGzip\n\t\t\t\t\t? \"\"\n\t\t\t\t\t: ` (${diff > 0 ? \"+\" : \"-\"}${bytes(Math.abs(diff), { unitSeparator: \" \" })} ${percentFormatter(\n\t\t\t\t\t\t\tpreviousFileSizeGzip === 0 ? 0 : diff / previousFileSizeGzip,\n\t\t\t\t\t\t)})`;\n\t\t}\n\n\t\ttotalGzipSize += item.fileSizeGzip;\n\t\tsubGroupAccumulatedSize += item.fileSizeGzip;\n\t\tsubGroupAccumulatedPrevSize += previousFileSizeGzip;\n\t\thasSubGroup = true;\n\n\t\trowsMD.push(\n\t\t\taddMDrow({\n\t\t\t\ttype: \"row\",\n\t\t\t\tpassed: item.passed,\n\t\t\t\tname: name,\n\t\t\t\tsize: item.fileSizeGzip,\n\t\t\t\tdiff: diffString,\n\t\t\t\tlimit: item.limit,\n\t\t\t\tcolumns,\n\t\t\t}),\n\t\t);\n\t}\n\n\tflushSubGroup();\n\n\tconst template = `\n${headerString}\n${rowsMD.join(\"\\n\")}\n\n${footerFormatter({ limitReached, overallDiff, totalGzipSize })}\n`;\n\n\tif (limitReached) {\n\t\tresult.exitCode = 1;\n\t}\n\tresult.data = template;\n\treturn result;\n};\n"],"names":["basename","dirname","join","bytes","addMDrow","formatFooter","getMostRecentVersion","getOutputFile","percentFormatter","readJSONFile","validateConfigurationFile","isHeaderEntry","entry","reportStats","flags","result","exitCode","exitMessage","outputFile","data","previousStats","previousVersion","isValidConfigResult","configuration","configurationFile","output","then","m","default","currentStats","report","current","previous","currentVersion","limitReached","overallDiff","totalGzipSize","headerString","header","footerFormatter","footer","columns","status","file","size","limits","rowsMD","hasGroupHeaders","Boolean","sizes","some","push","type","orderedKeys","Array","isArray","value","alias","path","length","key","Object","keys","subGroupAccumulatedSize","subGroupAccumulatedPrevSize","hasSubGroup","flushSubGroup","diff","diffString","sign","Math","abs","unitSeparator","item","name","passed","previousFileSizeGzip","previousFileStats","fileSizeGzip","limit","template"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-cli/bundlesize",
3
- "version": "4.2.3",
3
+ "version": "4.3.0",
4
4
  "license": "MIT",
5
5
  "author": "Arno Versini",
6
6
  "description": "Simple CLI tool that checks file(s) size and report if limits have been reached",
@@ -30,8 +30,8 @@
30
30
  "watch": "swc --strip-leading-paths --watch --out-dir dist src"
31
31
  },
32
32
  "dependencies": {
33
- "@node-cli/logger": "../logger",
34
- "@node-cli/parser": "../parser",
33
+ "@node-cli/logger": "1.3.2",
34
+ "@node-cli/parser": "2.4.3",
35
35
  "bytes": "3.1.2",
36
36
  "fs-extra": "11.3.1",
37
37
  "glob": "11.0.3",
@@ -41,9 +41,9 @@
41
41
  "access": "public"
42
42
  },
43
43
  "devDependencies": {
44
- "@node-cli/comments": "0.2.0",
44
+ "@node-cli/comments": "0.2.6",
45
45
  "@vitest/coverage-v8": "3.2.4",
46
46
  "vitest": "3.2.4"
47
47
  },
48
- "gitHead": "d90392dcb766dd605bc3eeabc7c7a7ab0c8e6da6"
48
+ "gitHead": "a1b287c23dd32b40d0b5fa5439241bebe4b3f5cd"
49
49
  }