@node-cli/bundlesize 2.1.4 → 3.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/README.md CHANGED
@@ -12,56 +12,74 @@
12
12
 
13
13
  ## Configuration
14
14
 
15
- A configuration file must be provided via the `-c` parameter, it must export an array of objects with the following properties:
15
+ A configuration file must be provided via the `-c` parameter.
16
+
17
+ For the size option, it must export an object named "size" which is an array of objects with the following properties:
16
18
 
17
19
  - `path`: the path to the file to check
18
20
  - `limit`: the limit to check against
19
21
 
20
- ### Examples
22
+ For the report option, it must export an object named "report" which is an object with the following properties:
23
+
24
+ - `header`: the header to display (optional, string)
25
+ - `footer`: the footer to display (optional, function receiving a boolean indicating if the limit has been reached, and a value corresponding to the diff. It must return a string)
26
+ - `previous`: the previous path to the report to compare against (required, string)
27
+ - `current`: the current path to the report to compare against (required, string)
28
+ - `columns`: the columns to display (optional, array of objects)
29
+
30
+ ## Examples
31
+
32
+ ### Getting stats from files
21
33
 
22
34
  #### Single file
23
35
 
24
36
  ```js
25
- export default [
26
- {
27
- path: "dist/some-bundle.js",
28
- limit: "10 kB",
29
- },
30
- ];
37
+ export default {
38
+ sizes: [
39
+ {
40
+ path: "dist/some-bundle.js",
41
+ limit: "10 kB",
42
+ },
43
+ ],
44
+ };
31
45
  ```
32
46
 
33
47
  #### Multiple files
34
48
 
35
49
  ```js
36
- export default [
37
- {
38
- path: "dist/some-bundle.js",
39
- limit: "10 kB",
40
- },
41
- {
42
- path: "dist/some-other-bundle.js",
43
- limit: "100 kB",
44
- },
45
- ];
50
+ export default {
51
+ sizes: [
52
+ {
53
+ path: "dist/some-bundle.js",
54
+ limit: "10 kB",
55
+ },
56
+ {
57
+ path: "dist/some-other-bundle.js",
58
+ limit: "100 kB",
59
+ },
60
+ ],
61
+ };
46
62
  ```
47
63
 
48
64
  #### With glob patterns
49
65
 
50
66
  ```js
51
- export default [
52
- {
53
- path: "dist/**/some-bundle.js",
54
- limit: "10 kB",
55
- },
56
- {
57
- path: "dist/**/some-other-bundle-*.js",
58
- limit: "100 kB",
59
- },
60
- {
61
- path: "dist/**/extra-+([a-zA-Z0-9]).js",
62
- limit: "100 kB",
63
- },
64
- ];
67
+ export default {
68
+ sizes: [
69
+ {
70
+ path: "dist/**/some-bundle.js",
71
+ limit: "10 kB",
72
+ },
73
+ {
74
+ path: "dist/**/some-other-bundle-*.js",
75
+ limit: "100 kB",
76
+ },
77
+ {
78
+ path: "dist/**/extra-+([a-zA-Z0-9]).js",
79
+ limit: "100 kB",
80
+ },
81
+ ],
82
+ };
65
83
  ```
66
84
 
67
85
  #### With a hash
@@ -76,9 +94,65 @@ The special keyword `<hash>` can be used to match a hash in the filename. It can
76
94
  | Not OK | `dist/**/some-bundle-<hash>.js` | If multiple files match the pattern |
77
95
  | Not OK | `dist/**/some-bundle-<hash>.*` | Cannot use `<hash>` with `*` |
78
96
 
97
+ ### Printing reports from stats
98
+
99
+ #### Simple report
100
+
101
+ ```js
102
+ export default {
103
+ report: {
104
+ prev: "stats/previous.json",
105
+ current: "stats/current.json",
106
+ },
107
+ };
108
+ ```
109
+
110
+ #### Simple report with custom header
111
+
112
+ ```js
113
+ export default {
114
+ report: {
115
+ header: "## My custom header",
116
+ prev: "stats/previous.json",
117
+ current: "stats/current.json",
118
+ },
119
+ };
120
+ ```
121
+
122
+ #### Simple report with custom footer
123
+
124
+ ```js
125
+ export default {
126
+ report: {
127
+ footer: (limitReached, diff) => {
128
+ return `## My custom footer: ${limitReached} ${diff}`;
129
+ },
130
+ prev: "stats/previous.json",
131
+ current: "stats/current.json",
132
+ },
133
+ };
134
+ ```
135
+
136
+ #### Simple report with custom columns
137
+
138
+ ```js
139
+ export default {
140
+ report: {
141
+ columns: [
142
+ { status: "Status" },
143
+ { file: "File" },
144
+ { size: "Size" },
145
+ { limits: "Limits" },
146
+ ],
147
+ prev: "stats/previous.json",
148
+ current: "stats/current.json",
149
+ },
150
+ };
151
+ ```
152
+
79
153
  ## Usage
80
154
 
81
- ### Print the results at the command line
155
+ ### Print the stats at the command line
82
156
 
83
157
  ```json
84
158
  "scripts": {
@@ -86,7 +160,7 @@ The special keyword `<hash>` can be used to match a hash in the filename. It can
86
160
  }
87
161
  ```
88
162
 
89
- ### Print the results in a file
163
+ ### Print the stats in a file
90
164
 
91
165
  ```json
92
166
  "scripts": {
@@ -94,7 +168,7 @@ The special keyword `<hash>` can be used to match a hash in the filename. It can
94
168
  }
95
169
  ```
96
170
 
97
- ### Print the results in a file but do not fail if the limit is reached
171
+ ### Print the stats in a file but do not fail if the limit is reached
98
172
 
99
173
  ```json
100
174
  "scripts": {
@@ -102,7 +176,7 @@ The special keyword `<hash>` can be used to match a hash in the filename. It can
102
176
  }
103
177
  ```
104
178
 
105
- ### Add a prefix to the results
179
+ ### Add a prefix to the stats
106
180
 
107
181
  ```json
108
182
  "scripts": {
@@ -118,6 +192,14 @@ The special keyword `<hash>` can be used to match a hash in the filename. It can
118
192
  }
119
193
  ```
120
194
 
195
+ ### Compare current stats with the previous ones
196
+
197
+ ```json
198
+ "scripts": {
199
+ "stats": "bundlesize -c bundlesize.config.js --type report"
200
+ }
201
+ ```
202
+
121
203
  ## Get help
122
204
 
123
205
  ```sh
@@ -1,39 +1,72 @@
1
1
  #!/usr/bin/env node
2
- /* istanbul ignore file */ import { STDOUT, reportStats } from "./utilities.js";
3
- import { Logger } from "@node-cli/logger";
2
+ /* istanbul ignore file */ import { Logger } from "@node-cli/logger";
3
+ import { STDOUT } from "./utilities.js";
4
4
  import { config } from "./parse.js";
5
5
  import fs from "fs-extra";
6
+ import { getRawStats } from "./getRawStats.js";
7
+ import { reportStats } from "./reportStats.js";
6
8
  const flags = config.flags;
7
9
  const log = new Logger({
8
10
  boring: flags.boring
9
11
  });
10
- try {
11
- const result = await reportStats({
12
- flags
13
- });
14
- if (result.exitMessage !== "") {
15
- log.error(result.exitMessage);
12
+ if (flags.type === "size") {
13
+ try {
14
+ const result = await getRawStats({
15
+ flags
16
+ });
17
+ if (result.exitMessage !== "") {
18
+ log.error(result.exitMessage);
19
+ process.exit(result.exitCode);
20
+ }
21
+ if (result.outputFile === STDOUT) {
22
+ log.info(`Configuration: ${flags.configuration}`);
23
+ log.info(`Output: ${result.outputFile}`);
24
+ log.info(`Output prefix: ${result.prefix}`);
25
+ log.log(result.data);
26
+ } else {
27
+ try {
28
+ fs.outputJsonSync(result.outputFile, result.data, {
29
+ spaces: 2
30
+ });
31
+ } catch (error) {
32
+ log.error(`Failed to write to file: ${error.message}`);
33
+ process.exit(1);
34
+ }
35
+ }
16
36
  process.exit(result.exitCode);
37
+ } catch (error) {
38
+ log.error(error);
39
+ process.exit(1);
17
40
  }
18
- if (result.outputFile === STDOUT) {
19
- log.info(`Configuration: ${flags.configuration}`);
20
- log.info(`Output: ${result.outputFile}`);
21
- log.info(`Output prefix: ${result.prefix}`);
22
- log.log(result.data);
23
- } else {
24
- try {
25
- fs.outputJsonSync(result.outputFile, result.data, {
26
- spaces: 2
27
- });
28
- } catch (error) {
29
- log.error(`Failed to write to file: ${error.message}`);
30
- process.exit(1);
41
+ }
42
+ if (flags.type === "report") {
43
+ try {
44
+ const result = await reportStats({
45
+ flags
46
+ });
47
+ if (result.exitMessage !== "") {
48
+ log.error(result.exitMessage);
49
+ process.exit(result.exitCode);
31
50
  }
51
+ if (result.outputFile === STDOUT) {
52
+ log.info(`Configuration: ${flags.configuration}`);
53
+ log.info(`Output: ${result.outputFile}`);
54
+ log.log(result.data);
55
+ } else {
56
+ try {
57
+ fs.outputFileSync(result.outputFile, result.data);
58
+ } catch (error) {
59
+ log.error(`Failed to write to file: ${error.message}`);
60
+ process.exit(1);
61
+ }
62
+ }
63
+ process.exit(result.exitCode);
64
+ } catch (error) {
65
+ log.error(error);
66
+ process.exit(1);
32
67
  }
33
- process.exit(result.exitCode);
34
- } catch (error) {
35
- log.error(error);
36
- process.exit(1);
37
68
  }
69
+ log.error("Invalid type, please use 'size' or 'report'");
70
+ process.exit(1);
38
71
 
39
72
  //# sourceMappingURL=bundlesize.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bundlesize.ts"],"sourcesContent":["#!/usr/bin/env node\n/* istanbul ignore file */\n\nimport { STDOUT, reportStats } from \"./utilities.js\";\n\nimport { Logger } from \"@node-cli/logger\";\nimport { config } from \"./parse.js\";\nimport fs from \"fs-extra\";\n\nconst flags = config.flags;\n\nconst log = new Logger({\n\tboring: flags.boring,\n});\n\ntry {\n\tconst result = await reportStats({ flags });\n\n\tif (result.exitMessage !== \"\") {\n\t\tlog.error(result.exitMessage);\n\t\tprocess.exit(result.exitCode);\n\t}\n\n\tif (result.outputFile === STDOUT) {\n\t\tlog.info(`Configuration: ${flags.configuration}`);\n\t\tlog.info(`Output: ${result.outputFile}`);\n\t\tlog.info(`Output prefix: ${result.prefix}`);\n\t\tlog.log(result.data);\n\t} else {\n\t\ttry {\n\t\t\tfs.outputJsonSync(result.outputFile, result.data, { spaces: 2 });\n\t\t} catch (error) {\n\t\t\tlog.error(`Failed to write to file: ${error.message}`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\tprocess.exit(result.exitCode);\n} catch (error) {\n\tlog.error(error);\n\tprocess.exit(1);\n}\n"],"names":["STDOUT","reportStats","Logger","config","fs","flags","log","boring","result","exitMessage","error","process","exit","exitCode","outputFile","info","configuration","prefix","data","outputJsonSync","spaces","message"],"mappings":";AACA,wBAAwB,GAExB,SAASA,MAAM,EAAEC,WAAW,QAAQ,iBAAiB;AAErD,SAASC,MAAM,QAAQ,mBAAmB;AAC1C,SAASC,MAAM,QAAQ,aAAa;AACpC,OAAOC,QAAQ,WAAW;AAE1B,MAAMC,QAAQF,OAAOE,KAAK;AAE1B,MAAMC,MAAM,IAAIJ,OAAO;IACtBK,QAAQF,MAAME,MAAM;AACrB;AAEA,IAAI;IACH,MAAMC,SAAS,MAAMP,YAAY;QAAEI;IAAM;IAEzC,IAAIG,OAAOC,WAAW,KAAK,IAAI;QAC9BH,IAAII,KAAK,CAACF,OAAOC,WAAW;QAC5BE,QAAQC,IAAI,CAACJ,OAAOK,QAAQ;IAC7B;IAEA,IAAIL,OAAOM,UAAU,KAAKd,QAAQ;QACjCM,IAAIS,IAAI,CAAC,CAAC,eAAe,EAAEV,MAAMW,aAAa,CAAC,CAAC;QAChDV,IAAIS,IAAI,CAAC,CAAC,QAAQ,EAAEP,OAAOM,UAAU,CAAC,CAAC;QACvCR,IAAIS,IAAI,CAAC,CAAC,eAAe,EAAEP,OAAOS,MAAM,CAAC,CAAC;QAC1CX,IAAIA,GAAG,CAACE,OAAOU,IAAI;IACpB,OAAO;QACN,IAAI;YACHd,GAAGe,cAAc,CAACX,OAAOM,UAAU,EAAEN,OAAOU,IAAI,EAAE;gBAAEE,QAAQ;YAAE;QAC/D,EAAE,OAAOV,OAAO;YACfJ,IAAII,KAAK,CAAC,CAAC,yBAAyB,EAAEA,MAAMW,OAAO,CAAC,CAAC;YACrDV,QAAQC,IAAI,CAAC;QACd;IACD;IACAD,QAAQC,IAAI,CAACJ,OAAOK,QAAQ;AAC7B,EAAE,OAAOH,OAAO;IACfJ,IAAII,KAAK,CAACA;IACVC,QAAQC,IAAI,CAAC;AACd"}
1
+ {"version":3,"sources":["../src/bundlesize.ts"],"sourcesContent":["#!/usr/bin/env node\n/* istanbul ignore file */\n\nimport { Logger } from \"@node-cli/logger\";\nimport { STDOUT } from \"./utilities.js\";\nimport { config } from \"./parse.js\";\nimport fs from \"fs-extra\";\nimport { getRawStats } from \"./getRawStats.js\";\nimport { reportStats } from \"./reportStats.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.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","STDOUT","config","fs","getRawStats","reportStats","flags","log","boring","type","result","exitMessage","error","process","exit","exitCode","outputFile","info","configuration","prefix","data","outputJsonSync","spaces","message","outputFileSync"],"mappings":";AACA,wBAAwB,GAExB,SAASA,MAAM,QAAQ,mBAAmB;AAC1C,SAASC,MAAM,QAAQ,iBAAiB;AACxC,SAASC,MAAM,QAAQ,aAAa;AACpC,OAAOC,QAAQ,WAAW;AAC1B,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,SAASC,WAAW,QAAQ,mBAAmB;AAE/C,MAAMC,QAAQJ,OAAOI,KAAK;AAE1B,MAAMC,MAAM,IAAIP,OAAO;IACtBQ,QAAQF,MAAME,MAAM;AACrB;AAEA,IAAIF,MAAMG,IAAI,KAAK,QAAQ;IAC1B,IAAI;QACH,MAAMC,SAAS,MAAMN,YAAY;YAAEE;QAAM;QAEzC,IAAII,OAAOC,WAAW,KAAK,IAAI;YAC9BJ,IAAIK,KAAK,CAACF,OAAOC,WAAW;YAC5BE,QAAQC,IAAI,CAACJ,OAAOK,QAAQ;QAC7B;QAEA,IAAIL,OAAOM,UAAU,KAAKf,QAAQ;YACjCM,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEX,MAAMY,aAAa,CAAC,CAAC;YAChDX,IAAIU,IAAI,CAAC,CAAC,QAAQ,EAAEP,OAAOM,UAAU,CAAC,CAAC;YACvCT,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEP,OAAOS,MAAM,CAAC,CAAC;YAC1CZ,IAAIA,GAAG,CAACG,OAAOU,IAAI;QACpB,OAAO;YACN,IAAI;gBACHjB,GAAGkB,cAAc,CAACX,OAAOM,UAAU,EAAEN,OAAOU,IAAI,EAAE;oBAAEE,QAAQ;gBAAE;YAC/D,EAAE,OAAOV,OAAO;gBACfL,IAAIK,KAAK,CAAC,CAAC,yBAAyB,EAAEA,MAAMW,OAAO,CAAC,CAAC;gBACrDV,QAAQC,IAAI,CAAC;YACd;QACD;QACAD,QAAQC,IAAI,CAACJ,OAAOK,QAAQ;IAC7B,EAAE,OAAOH,OAAO;QACfL,IAAIK,KAAK,CAACA;QACVC,QAAQC,IAAI,CAAC;IACd;AACD;AAEA,IAAIR,MAAMG,IAAI,KAAK,UAAU;IAC5B,IAAI;QACH,MAAMC,SAAS,MAAML,YAAY;YAAEC;QAAM;QAEzC,IAAII,OAAOC,WAAW,KAAK,IAAI;YAC9BJ,IAAIK,KAAK,CAACF,OAAOC,WAAW;YAC5BE,QAAQC,IAAI,CAACJ,OAAOK,QAAQ;QAC7B;QAEA,IAAIL,OAAOM,UAAU,KAAKf,QAAQ;YACjCM,IAAIU,IAAI,CAAC,CAAC,eAAe,EAAEX,MAAMY,aAAa,CAAC,CAAC;YAChDX,IAAIU,IAAI,CAAC,CAAC,QAAQ,EAAEP,OAAOM,UAAU,CAAC,CAAC;YACvCT,IAAIA,GAAG,CAACG,OAAOU,IAAI;QACpB,OAAO;YACN,IAAI;gBACHjB,GAAGqB,cAAc,CAACd,OAAOM,UAAU,EAAEN,OAAOU,IAAI;YACjD,EAAE,OAAOR,OAAO;gBACfL,IAAIK,KAAK,CAAC,CAAC,yBAAyB,EAAEA,MAAMW,OAAO,CAAC,CAAC;gBACrDV,QAAQC,IAAI,CAAC;YACd;QACD;QACAD,QAAQC,IAAI,CAACJ,OAAOK,QAAQ;IAC7B,EAAE,OAAOH,OAAO;QACfL,IAAIK,KAAK,CAACA;QACVC,QAAQC,IAAI,CAAC;IACd;AACD;AAEAP,IAAIK,KAAK,CAAC;AACVC,QAAQC,IAAI,CAAC"}
@@ -2,4 +2,5 @@ export declare const defaultFlags: {
2
2
  boring: boolean;
3
3
  configuration: string;
4
4
  silent: boolean;
5
+ type: string;
5
6
  };
package/dist/defaults.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /* istanbul ignore file */ export const defaultFlags = {
2
2
  boring: false,
3
3
  configuration: "",
4
- silent: false
4
+ silent: false,
5
+ type: "size"
5
6
  };
6
7
 
7
8
  //# sourceMappingURL=defaults.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/defaults.ts"],"sourcesContent":["/* istanbul ignore file */\n\nexport const defaultFlags = {\n\tboring: false,\n\tconfiguration: \"\",\n\tsilent: false,\n};\n"],"names":["defaultFlags","boring","configuration","silent"],"mappings":"AAAA,wBAAwB,GAExB,OAAO,MAAMA,eAAe;IAC3BC,QAAQ;IACRC,eAAe;IACfC,QAAQ;AACT,EAAE"}
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};\n"],"names":["defaultFlags","boring","configuration","silent","type"],"mappings":"AAAA,wBAAwB,GAExB,OAAO,MAAMA,eAAe;IAC3BC,QAAQ;IACRC,eAAe;IACfC,QAAQ;IACRC,MAAM;AACP,EAAE"}
@@ -0,0 +1,12 @@
1
+ type ReportStats = {
2
+ data: Record<string, unknown>;
3
+ exitCode: number;
4
+ exitMessage: string;
5
+ outputFile: string;
6
+ pass: boolean;
7
+ prefix: string;
8
+ };
9
+ export declare const getRawStats: ({ flags }: {
10
+ flags: any;
11
+ }) => Promise<ReportStats>;
12
+ export {};
@@ -0,0 +1,96 @@
1
+ import { STDOUT, getOutputFile, gzipSizeFromFileSync, validateConfigurationFile } from "./utilities.js";
2
+ import { basename, dirname, join } from "node:path";
3
+ import bytes from "bytes";
4
+ import fs from "fs-extra";
5
+ import { glob } from "glob";
6
+ import { statSync } from "node:fs";
7
+ export const getRawStats = async ({ flags })=>{
8
+ const result = {
9
+ pass: true,
10
+ exitCode: 0,
11
+ exitMessage: "",
12
+ outputFile: "",
13
+ prefix: "",
14
+ data: {}
15
+ };
16
+ let failed = false;
17
+ const isValidConfigResult = validateConfigurationFile(flags.configuration);
18
+ if (isValidConfigResult.exitMessage !== "") {
19
+ return {
20
+ ...result,
21
+ ...isValidConfigResult
22
+ };
23
+ }
24
+ const configurationFile = isValidConfigResult.data;
25
+ const outputFile = getOutputFile(flags.output);
26
+ const prefix = flags.prefix || "0.0.0";
27
+ const currentResults = {};
28
+ const configuration = await import(configurationFile).then((m)=>m.default);
29
+ if (configuration.sizes === undefined) {
30
+ result.exitMessage = "Invalid configuration file: missing sizes object!";
31
+ result.exitCode = 1;
32
+ return result;
33
+ }
34
+ for (const artifact of configuration.sizes){
35
+ const rootPath = artifact.path.startsWith("/") ? "" : dirname(configurationFile);
36
+ const artifactPath = dirname(artifact.path);
37
+ const globReplace = "+([a-zA-Z0-9_-])";
38
+ const hasHash = artifact.path.includes("<hash>");
39
+ /**
40
+ * if the artifact.path has the string <hash> in it,
41
+ * then we need to check for other characters:
42
+ * - Double stars ** are allowed.
43
+ * - Single stars * are not allowed.
44
+ */ if (hasHash && /(?<!\*)\*(?!\*)/.test(artifact.path)) {
45
+ result.exitCode = 1;
46
+ result.exitMessage = `Invalid path: ${artifact.path}.\nSingle stars (*) are not allowed when using the special keyword <hash>`;
47
+ return result;
48
+ }
49
+ const fileGlob = join(rootPath, artifact.path.replace("<hash>", globReplace));
50
+ const files = glob.sync(fileGlob);
51
+ if (files.length === 0) {
52
+ result.exitCode = 1;
53
+ result.exitMessage = `File not found: ${fileGlob}`;
54
+ return result;
55
+ }
56
+ if (files.length > 1 && hasHash) {
57
+ result.exitCode = 1;
58
+ result.exitMessage = `Multiple files found for: ${artifact.path}.\nPlease use a more specific path when using the special keyword <hash>.`;
59
+ return result;
60
+ }
61
+ for (const file of files){
62
+ const fileSize = statSync(file).size;
63
+ const fileSizeGzip = gzipSizeFromFileSync(file);
64
+ const passed = fileSizeGzip < bytes(artifact.limit);
65
+ if (passed === false) {
66
+ result.pass = false;
67
+ failed = true;
68
+ }
69
+ let index = hasHash ? artifact.path : join(artifactPath, basename(file));
70
+ currentResults[index] = {
71
+ fileSize,
72
+ fileSizeGzip,
73
+ limit: artifact.limit,
74
+ passed
75
+ };
76
+ }
77
+ }
78
+ let existingResults = {};
79
+ if (outputFile !== STDOUT) {
80
+ try {
81
+ existingResults = fs.readJsonSync(outputFile);
82
+ } catch {
83
+ /**
84
+ * There are no existing results, so we can ignore this error,
85
+ * and simply write the current results to the output file.
86
+ */ }
87
+ }
88
+ existingResults[prefix] = currentResults;
89
+ result.prefix = prefix;
90
+ result.outputFile = outputFile;
91
+ result.exitCode = failed && flags.silent === false ? 1 : 0;
92
+ result.data = existingResults;
93
+ return result;
94
+ };
95
+
96
+ //# sourceMappingURL=getRawStats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/getRawStats.ts"],"sourcesContent":["import {\n\tSTDOUT,\n\tgetOutputFile,\n\tgzipSizeFromFileSync,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\nimport { basename, dirname, join } from \"node:path\";\n\nimport bytes from \"bytes\";\nimport fs from \"fs-extra\";\nimport { glob } from \"glob\";\nimport { statSync } from \"node:fs\";\n\ntype SizesConfiguration = {\n\tlimit: string;\n\tpath: 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 globReplace = \"+([a-zA-Z0-9_-])\";\n\t\tconst hasHash = artifact.path.includes(\"<hash>\");\n\n\t\t/**\n\t\t * if the artifact.path has the string <hash> in it,\n\t\t * then we need to check for other characters:\n\t\t * - Double stars ** are allowed.\n\t\t * - Single stars * are not allowed.\n\t\t */\n\t\tif (hasHash && /(?<!\\*)\\*(?!\\*)/.test(artifact.path)) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Invalid path: ${artifact.path}.\\nSingle stars (*) are not allowed when using the special keyword <hash>`;\n\t\t\treturn result;\n\t\t}\n\n\t\tconst fileGlob = join(\n\t\t\trootPath,\n\t\t\tartifact.path.replace(\"<hash>\", globReplace),\n\t\t);\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 && hasHash) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Multiple files found for: ${artifact.path}.\\nPlease use a more specific path when using the special keyword <hash>.`;\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\t\t\tlet index = hasHash ? artifact.path : join(artifactPath, basename(file));\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} catch {\n\t\t\t/**\n\t\t\t * There are no existing results, so we can ignore this error,\n\t\t\t * and simply write the current results to the output file.\n\t\t\t */\n\t\t}\n\t}\n\texistingResults[prefix] = currentResults;\n\n\tresult.prefix = prefix;\n\tresult.outputFile = outputFile;\n\tresult.exitCode = failed && flags.silent === false ? 1 : 0;\n\tresult.data = existingResults;\n\treturn result;\n};\n"],"names":["STDOUT","getOutputFile","gzipSizeFromFileSync","validateConfigurationFile","basename","dirname","join","bytes","fs","glob","statSync","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","globReplace","hasHash","includes","test","fileGlob","replace","files","sync","length","file","fileSize","size","fileSizeGzip","passed","limit","index","existingResults","readJsonSync","silent"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -4,5 +4,7 @@
4
4
 
5
5
  export * from "./bundlesize";
6
6
  export * from "./defaults";
7
+ export * from "./getRawStats";
7
8
  export * from "./parse";
9
+ export * from "./reportStats";
8
10
  export * from "./utilities";
package/dist/parse.d.ts CHANGED
@@ -6,6 +6,7 @@ export type Flags = {
6
6
  output?: string;
7
7
  prefix?: string;
8
8
  silent?: boolean;
9
+ type?: "size" | "report";
9
10
  };
10
11
  export type Configuration = {
11
12
  flags?: Flags;
package/dist/parse.js CHANGED
@@ -4,6 +4,12 @@ import { parser } from "@node-cli/parser";
4
4
  meta: import.meta,
5
5
  examples: [],
6
6
  flags: {
7
+ type: {
8
+ shortFlag: "t",
9
+ description: "Specify the type of output: size or report",
10
+ type: "string",
11
+ default: defaultFlags.type
12
+ },
7
13
  configuration: {
8
14
  shortFlag: "c",
9
15
  description: "Specify a configuration file",
package/dist/parse.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/parse.ts"],"sourcesContent":["import { defaultFlags } from \"./defaults.js\";\nimport { parser } from \"@node-cli/parser\";\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};\n\nexport type Configuration = {\n\tflags?: Flags;\n\tusage?: boolean;\n\tshowHelp?: () => void;\n};\n\n/* istanbul ignore next */\nexport const config: Configuration = parser({\n\tmeta: import.meta,\n\texamples: [],\n\tflags: {\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":["defaultFlags","parser","config","meta","examples","flags","configuration","shortFlag","description","type","output","prefix","silent","default","boring","help","version","usage"],"mappings":"AAAA,SAASA,YAAY,QAAQ,gBAAgB;AAC7C,SAASC,MAAM,QAAQ,mBAAmB;AAkB1C,wBAAwB,GACxB,OAAO,MAAMC,SAAwBD,OAAO;IAC3CE,MAAM;IACNC,UAAU,EAAE;IACZC,OAAO;QACNC,eAAe;YACdC,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAC,QAAQ;YACPH,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAE,QAAQ;YACPJ,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAG,QAAQ;YACPL,WAAW;YACXM,SAASb,aAAaY,MAAM;YAC5BJ,aAAa;YACbC,MAAM;QACP;QACAK,QAAQ;YACPP,WAAW;YACXM,SAASb,aAAac,MAAM;YAC5BN,aAAa;YACbC,MAAM;QACP;QACAM,MAAM;YACLR,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;QACAO,SAAS;YACRT,WAAW;YACXC,aAAa;YACbC,MAAM;QACP;IACD;IACAQ,OAAO;IACPjB;AACD,GAAG"}
1
+ {"version":3,"sources":["../src/parse.ts"],"sourcesContent":["import { defaultFlags } from \"./defaults.js\";\nimport { parser } from \"@node-cli/parser\";\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};\n\nexport type Configuration = {\n\tflags?: Flags;\n\tusage?: boolean;\n\tshowHelp?: () => void;\n};\n\n/* istanbul ignore next */\nexport const config: Configuration = parser({\n\tmeta: import.meta,\n\texamples: [],\n\tflags: {\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":["defaultFlags","parser","config","meta","examples","flags","type","shortFlag","description","default","configuration","output","prefix","silent","boring","help","version","usage"],"mappings":"AAAA,SAASA,YAAY,QAAQ,gBAAgB;AAC7C,SAASC,MAAM,QAAQ,mBAAmB;AAmB1C,wBAAwB,GACxB,OAAO,MAAMC,SAAwBD,OAAO;IAC3CE,MAAM;IACNC,UAAU,EAAE;IACZC,OAAO;QACNC,MAAM;YACLC,WAAW;YACXC,aAAa;YACbF,MAAM;YACNG,SAAST,aAAaM,IAAI;QAC3B;QACAI,eAAe;YACdH,WAAW;YACXC,aAAa;YACbF,MAAM;QACP;QACAK,QAAQ;YACPJ,WAAW;YACXC,aAAa;YACbF,MAAM;QACP;QACAM,QAAQ;YACPL,WAAW;YACXC,aAAa;YACbF,MAAM;QACP;QACAO,QAAQ;YACPN,WAAW;YACXE,SAAST,aAAaa,MAAM;YAC5BL,aAAa;YACbF,MAAM;QACP;QACAQ,QAAQ;YACPP,WAAW;YACXE,SAAST,aAAac,MAAM;YAC5BN,aAAa;YACbF,MAAM;QACP;QACAS,MAAM;YACLR,WAAW;YACXC,aAAa;YACbF,MAAM;QACP;QACAU,SAAS;YACRT,WAAW;YACXC,aAAa;YACbF,MAAM;QACP;IACD;IACAW,OAAO;IACPjB;AACD,GAAG"}
@@ -0,0 +1,10 @@
1
+ type ReportCompare = {
2
+ data: string;
3
+ exitCode: number;
4
+ exitMessage: string;
5
+ outputFile: string;
6
+ };
7
+ export declare const reportStats: ({ flags }: {
8
+ flags: any;
9
+ }) => Promise<ReportCompare>;
10
+ export {};
@@ -0,0 +1,108 @@
1
+ import { addMDrow, formatFooter, getMostRecentVersion, getOutputFile, percentFormatter, readJSONFile, validateConfigurationFile } from "./utilities.js";
2
+ import { basename, dirname, join } from "node:path";
3
+ import bytes from "bytes";
4
+ export const reportStats = async ({ flags })=>{
5
+ const result = {
6
+ exitCode: 0,
7
+ exitMessage: "",
8
+ outputFile: "",
9
+ data: ""
10
+ };
11
+ let previousStats, previousVersion;
12
+ const isValidConfigResult = validateConfigurationFile(flags.configuration);
13
+ if (isValidConfigResult.exitMessage !== "") {
14
+ return {
15
+ ...result,
16
+ ...isValidConfigResult
17
+ };
18
+ }
19
+ const configurationFile = isValidConfigResult.data;
20
+ const outputFile = getOutputFile(flags.output);
21
+ result.outputFile = outputFile;
22
+ const configuration = await import(configurationFile).then((m)=>m.default);
23
+ const currentStats = readJSONFile(join(dirname(configurationFile), configuration.report.current));
24
+ if (currentStats.exitMessage !== "") {
25
+ return {
26
+ ...result,
27
+ ...currentStats
28
+ };
29
+ }
30
+ try {
31
+ previousStats = readJSONFile(join(dirname(configurationFile), configuration.report.previous));
32
+ if (previousStats.exitMessage !== "") {
33
+ return {
34
+ ...result,
35
+ ...previousStats
36
+ };
37
+ }
38
+ previousVersion = getMostRecentVersion(previousStats.data);
39
+ } catch {
40
+ // nothing to declare officer
41
+ }
42
+ const currentVersion = getMostRecentVersion(currentStats.data);
43
+ let limitReached = false;
44
+ let overallDiff = 0;
45
+ const headerString = configuration.report.header || "## Bundle Size";
46
+ const footerFormatter = configuration.report.footer || formatFooter;
47
+ const columns = configuration.report.columns || [
48
+ {
49
+ status: "Status"
50
+ },
51
+ {
52
+ file: "File"
53
+ },
54
+ {
55
+ size: "Size (Gzip)"
56
+ },
57
+ {
58
+ limits: "Limits"
59
+ }
60
+ ];
61
+ const rowsMD = [];
62
+ rowsMD.push(addMDrow({
63
+ type: "header",
64
+ columns
65
+ }));
66
+ for (const key of Object.keys(currentStats.data[currentVersion])){
67
+ const item = currentStats.data[currentVersion][key];
68
+ const name = basename(key);
69
+ if (!item.passed) {
70
+ limitReached = true;
71
+ }
72
+ let diffString = "";
73
+ if (previousStats && previousVersion) {
74
+ const previousFileStats = previousStats.data[previousVersion] && previousStats.data[previousVersion][key];
75
+ const previousFileSizeGzip = previousFileStats.fileSizeGzip;
76
+ const diff = item.fileSizeGzip - previousFileSizeGzip;
77
+ overallDiff += diff;
78
+ diffString = diff === 0 ? "" : ` (${diff > 0 ? "+" : "-"}${bytes(Math.abs(diff), {
79
+ unitSeparator: " "
80
+ })} ${percentFormatter.format(diff / previousFileSizeGzip)})`;
81
+ }
82
+ rowsMD.push(addMDrow({
83
+ type: "row",
84
+ passed: item.passed,
85
+ name: name,
86
+ size: item.fileSizeGzip,
87
+ diff: diffString,
88
+ limit: item.limit,
89
+ columns
90
+ }));
91
+ }
92
+ const overallDiffString = overallDiff === 0 ? "" : `(${overallDiff > 0 ? "+" : "-"}${bytes(Math.abs(overallDiff), {
93
+ unitSeparator: " "
94
+ })})`;
95
+ const template = `
96
+ ${headerString}
97
+ ${rowsMD.join("\n")}
98
+
99
+ ${footerFormatter(limitReached, overallDiffString).trim()}
100
+ `;
101
+ if (limitReached) {
102
+ result.exitCode = 1;
103
+ }
104
+ result.data = template;
105
+ return result;
106
+ };
107
+
108
+ //# sourceMappingURL=reportStats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/reportStats.ts"],"sourcesContent":["import {\n\taddMDrow,\n\tformatFooter,\n\tgetMostRecentVersion,\n\tgetOutputFile,\n\tpercentFormatter,\n\treadJSONFile,\n\tvalidateConfigurationFile,\n} from \"./utilities.js\";\nimport { basename, dirname, join } from \"node:path\";\n\nimport bytes from \"bytes\";\n\ntype ReportConfiguration = {\n\tcurrent: string;\n\tprevious: string;\n\n\theader?: string;\n\tfooter?: (limit: boolean, diff: string | number) => 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\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;\n\t\t\tconst diff = item.fileSizeGzip - previousFileSizeGzip;\n\n\t\t\toverallDiff += diff;\n\t\t\tdiffString =\n\t\t\t\tdiff === 0\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.format(diff / previousFileSizeGzip)})`;\n\t\t}\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 overallDiffString =\n\t\toverallDiff === 0\n\t\t\t? \"\"\n\t\t\t: `(${overallDiff > 0 ? \"+\" : \"-\"}${bytes(Math.abs(overallDiff), {\n\t\t\t\t\tunitSeparator: \" \",\n\t\t\t\t})})`;\n\n\tconst template = `\n${headerString}\n${rowsMD.join(\"\\n\")}\n\n${footerFormatter(limitReached, overallDiffString).trim()}\n`;\n\n\tif (limitReached) {\n\t\tresult.exitCode = 1;\n\t}\n\n\tresult.data = template;\n\n\treturn result;\n};\n"],"names":["addMDrow","formatFooter","getMostRecentVersion","getOutputFile","percentFormatter","readJSONFile","validateConfigurationFile","basename","dirname","join","bytes","reportStats","flags","result","exitCode","exitMessage","outputFile","data","previousStats","previousVersion","isValidConfigResult","configuration","configurationFile","output","then","m","default","currentStats","report","current","previous","currentVersion","limitReached","overallDiff","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","format","limit","overallDiffString","template","trim"],"mappings":"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"}
@@ -1,13 +1,17 @@
1
- type ReportStats = {
2
- pass: boolean;
3
- exitCode: number;
4
- exitMessage: string;
5
- outputFile: string;
6
- prefix: string;
7
- data: Record<string, unknown>;
8
- };
9
1
  export declare const STDOUT = "stdout";
10
- export declare const reportStats: ({ flags }: {
11
- flags: any;
12
- }) => Promise<ReportStats>;
13
- export {};
2
+ export declare const gzipSizeFromFileSync: (file: string) => number;
3
+ export declare const getOutputFile: (file: string) => string;
4
+ export declare const validateConfigurationFile: (file: string) => any;
5
+ export declare const readJSONFile: (file: string) => any;
6
+ export declare const getMostRecentVersion: (data: []) => any;
7
+ export declare const percentFormatter: Intl.NumberFormat;
8
+ export declare const formatFooter: (limit: boolean, diff: number | string) => string;
9
+ export declare const addMDrow: ({ type, passed, name, size, diff, limit, columns, }: {
10
+ columns: any;
11
+ type: "header" | "row";
12
+ passed?: boolean;
13
+ name?: string;
14
+ size?: number;
15
+ diff?: string;
16
+ limit?: string;
17
+ }) => string;
package/dist/utilities.js CHANGED
@@ -1,96 +1,97 @@
1
1
  import bytes from "bytes";
2
2
  import fs from "fs-extra";
3
- import { glob } from "glob";
4
- import { gzipSizeFromFileSync } from "gzip-size";
5
- import path from "node:path";
6
- import { statSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import semver from "semver";
5
+ import zlib from "node:zlib";
7
6
  export const STDOUT = "stdout";
8
7
  const CWD = process.cwd();
9
- export const reportStats = async ({ flags })=>{
8
+ export const gzipSizeFromFileSync = (file)=>{
9
+ return zlib.gzipSync(fs.readFileSync(file)).length;
10
+ };
11
+ export const getOutputFile = (file)=>{
12
+ return file === "" || file === undefined ? STDOUT : join(CWD, file);
13
+ };
14
+ export const validateConfigurationFile = (file)=>{
10
15
  const result = {
11
- pass: true,
12
16
  exitCode: 0,
13
17
  exitMessage: "",
14
- outputFile: "",
15
- prefix: "",
16
18
  data: {}
17
19
  };
18
- let failed = false;
19
- const configurationFile = flags.configuration.startsWith("/") ? flags.configuration : path.join(CWD, flags.configuration);
20
- const outputFile = flags.output === "" || flags.output === undefined ? STDOUT : path.join(CWD, flags.output);
21
- const prefix = flags.prefix || "0.0.0";
22
- const currentResults = {};
23
- if (flags.configuration === "") {
20
+ if (file === "") {
24
21
  result.exitMessage = "Please provide a configuration file";
25
22
  result.exitCode = 1;
26
23
  return result;
27
24
  }
25
+ const configurationFile = file.startsWith("/") ? file : join(CWD, file);
28
26
  if (fs.existsSync(configurationFile) === false) {
29
- result.exitMessage = "Invalid or missing configuration file!";
27
+ result.exitMessage = `Invalid or missing configuration file!\n${configurationFile}`;
30
28
  result.exitCode = 1;
31
29
  return result;
32
30
  }
33
- const configuration = await import(configurationFile).then((m)=>m.default);
34
- for (const artifact of configuration){
35
- const rootPath = artifact.path.startsWith("/") ? "" : path.dirname(configurationFile);
36
- const artifactPath = path.dirname(artifact.path);
37
- const globReplace = "+([a-zA-Z0-9_-])";
38
- const hasHash = artifact.path.includes("<hash>");
39
- /**
40
- * if the artifact.path has the string <hash> in it,
41
- * then we need to check for other characters:
42
- * - Double stars ** are allowed.
43
- * - Single stars * are not allowed.
44
- */ if (hasHash && /(?<!\*)\*(?!\*)/.test(artifact.path)) {
45
- result.exitCode = 1;
46
- result.exitMessage = `Invalid path: ${artifact.path}.\nSingle stars (*) are not allowed when using the special keyword <hash>`;
47
- return result;
48
- }
49
- const fileGlob = path.join(rootPath, artifact.path.replace("<hash>", globReplace));
50
- const files = glob.sync(fileGlob);
51
- if (files.length === 0) {
52
- result.exitCode = 1;
53
- result.exitMessage = `File not found: ${fileGlob}`;
54
- return result;
55
- }
56
- if (files.length > 1 && hasHash) {
57
- result.exitCode = 1;
58
- result.exitMessage = `Multiple files found for: ${artifact.path}.\nPlease use a more specific path when using the special keyword <hash>.`;
59
- return result;
60
- }
61
- for (const file of files){
62
- const fileSize = statSync(file).size;
63
- const fileSizeGzip = gzipSizeFromFileSync(file);
64
- const passed = fileSizeGzip < bytes(artifact.limit);
65
- if (passed === false) {
66
- result.pass = false;
67
- failed = true;
68
- }
69
- let index = hasHash ? artifact.path : path.join(artifactPath, path.basename(file));
70
- currentResults[index] = {
71
- fileSize,
72
- fileSizeGzip,
73
- limit: artifact.limit,
74
- passed
75
- };
76
- }
31
+ return {
32
+ ...result,
33
+ data: configurationFile
34
+ };
35
+ };
36
+ export const readJSONFile = (file)=>{
37
+ try {
38
+ return {
39
+ exitCode: 0,
40
+ exitMessage: "",
41
+ data: fs.readJsonSync(file)
42
+ };
43
+ } catch (error) {
44
+ return {
45
+ exitCode: 1,
46
+ exitMessage: `Failed to read JSON file: ${error.message}`
47
+ };
48
+ }
49
+ };
50
+ export const getMostRecentVersion = (data)=>{
51
+ const keys = [];
52
+ for (const key of Object.keys(data)){
53
+ keys.push(key);
77
54
  }
78
- let existingResults = {};
79
- if (outputFile !== STDOUT) {
80
- try {
81
- existingResults = fs.readJsonSync(outputFile);
82
- } catch {
83
- /**
84
- * There are no existing results, so we can ignore this error,
85
- * and simply write the current results to the output file.
86
- */ }
55
+ keys.sort(semver.rcompare);
56
+ return keys[0];
57
+ };
58
+ export const percentFormatter = new Intl.NumberFormat("en", {
59
+ style: "percent",
60
+ signDisplay: "exceptZero",
61
+ minimumFractionDigits: 2,
62
+ maximumFractionDigits: 2
63
+ });
64
+ export const formatFooter = (limit, diff)=>{
65
+ return `Overall status: ${limit ? "🚫" : "✅"} ${diff}`;
66
+ };
67
+ export const addMDrow = ({ type, passed = true, name = "", size = 0, diff = "", limit = "", columns })=>{
68
+ const totalColumns = columns.length;
69
+ if (type === "header") {
70
+ const separator = " --- |".repeat(totalColumns);
71
+ const header = columns.map((column)=>column[Object.keys(column)[0]]);
72
+ return `| ${header.join(" | ")} |\n|${separator}`;
73
+ }
74
+ if (type === "row") {
75
+ const row = columns.map((column)=>{
76
+ const key = Object.keys(column)[0];
77
+ if (key === "status") {
78
+ return passed ? "✅" : "🚫";
79
+ }
80
+ if (key === "file") {
81
+ return name.replaceAll("<", "[").replaceAll(">", "]");
82
+ }
83
+ if (key === "size") {
84
+ return `${bytes(size, {
85
+ unitSeparator: " "
86
+ })}${diff}`;
87
+ }
88
+ if (key === "limits") {
89
+ return limit;
90
+ }
91
+ return "";
92
+ });
93
+ return `| ${row.join(" | ")} |`;
87
94
  }
88
- existingResults[prefix] = currentResults;
89
- result.prefix = prefix;
90
- result.outputFile = outputFile;
91
- result.exitCode = failed && flags.silent === false ? 1 : 0;
92
- result.data = existingResults;
93
- return result;
94
95
  };
95
96
 
96
97
  //# sourceMappingURL=utilities.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utilities.ts"],"sourcesContent":["import bytes from \"bytes\";\nimport fs from \"fs-extra\";\nimport { glob } from \"glob\";\nimport { gzipSizeFromFileSync } from \"gzip-size\";\nimport path from \"node:path\";\nimport { statSync } from \"node:fs\";\n\ntype Artifact = {\n\tpath: string;\n\tlimit: string;\n};\n\ntype ReportStats = {\n\tpass: boolean;\n\texitCode: number;\n\texitMessage: string;\n\toutputFile: string;\n\tprefix: string;\n\tdata: Record<string, unknown>;\n};\n\nexport const STDOUT = \"stdout\";\nconst CWD = process.cwd();\n\nexport const reportStats = async ({ flags }): Promise<ReportStats> => {\n\tconst result = {\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 configurationFile = flags.configuration.startsWith(\"/\")\n\t\t? flags.configuration\n\t\t: path.join(CWD, flags.configuration);\n\n\tconst outputFile =\n\t\tflags.output === \"\" || flags.output === undefined\n\t\t\t? STDOUT\n\t\t\t: path.join(CWD, flags.output);\n\tconst prefix = flags.prefix || \"0.0.0\";\n\tconst currentResults = {};\n\n\tif (flags.configuration === \"\") {\n\t\tresult.exitMessage = \"Please provide a configuration file\";\n\t\tresult.exitCode = 1;\n\t\treturn result;\n\t}\n\n\tif (fs.existsSync(configurationFile) === false) {\n\t\tresult.exitMessage = \"Invalid or missing configuration file!\";\n\t\tresult.exitCode = 1;\n\t\treturn result;\n\t}\n\tconst configuration: Artifact[] = await import(configurationFile).then(\n\t\t(m) => m.default,\n\t);\n\n\tfor (const artifact of configuration) {\n\t\tconst rootPath = artifact.path.startsWith(\"/\")\n\t\t\t? \"\"\n\t\t\t: path.dirname(configurationFile);\n\t\tconst artifactPath = path.dirname(artifact.path);\n\t\tconst globReplace = \"+([a-zA-Z0-9_-])\";\n\t\tconst hasHash = artifact.path.includes(\"<hash>\");\n\n\t\t/**\n\t\t * if the artifact.path has the string <hash> in it,\n\t\t * then we need to check for other characters:\n\t\t * - Double stars ** are allowed.\n\t\t * - Single stars * are not allowed.\n\t\t */\n\t\tif (hasHash && /(?<!\\*)\\*(?!\\*)/.test(artifact.path)) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Invalid path: ${artifact.path}.\\nSingle stars (*) are not allowed when using the special keyword <hash>`;\n\t\t\treturn result;\n\t\t}\n\n\t\tconst fileGlob = path.join(\n\t\t\trootPath,\n\t\t\tartifact.path.replace(\"<hash>\", globReplace),\n\t\t);\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 && hasHash) {\n\t\t\tresult.exitCode = 1;\n\t\t\tresult.exitMessage = `Multiple files found for: ${artifact.path}.\\nPlease use a more specific path when using the special keyword <hash>.`;\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\t\t\tlet index = hasHash\n\t\t\t\t? artifact.path\n\t\t\t\t: path.join(artifactPath, path.basename(file));\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} catch {\n\t\t\t/**\n\t\t\t * There are no existing results, so we can ignore this error,\n\t\t\t * and simply write the current results to the output file.\n\t\t\t */\n\t\t}\n\t}\n\texistingResults[prefix] = currentResults;\n\n\tresult.prefix = prefix;\n\tresult.outputFile = outputFile;\n\tresult.exitCode = failed && flags.silent === false ? 1 : 0;\n\tresult.data = existingResults;\n\treturn result;\n};\n"],"names":["bytes","fs","glob","gzipSizeFromFileSync","path","statSync","STDOUT","CWD","process","cwd","reportStats","flags","result","pass","exitCode","exitMessage","outputFile","prefix","data","failed","configurationFile","configuration","startsWith","join","output","undefined","currentResults","existsSync","then","m","default","artifact","rootPath","dirname","artifactPath","globReplace","hasHash","includes","test","fileGlob","replace","files","sync","length","file","fileSize","size","fileSizeGzip","passed","limit","index","basename","existingResults","readJsonSync","silent"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/utilities.ts"],"sourcesContent":["import bytes from \"bytes\";\nimport fs from \"fs-extra\";\nimport { join } from \"node:path\";\nimport semver from \"semver\";\nimport zlib from \"node:zlib\";\n\nexport const STDOUT = \"stdout\";\nconst CWD = process.cwd();\n\nexport const gzipSizeFromFileSync = (file: string): number => {\n\treturn zlib.gzipSync(fs.readFileSync(file)).length;\n};\n\nexport const getOutputFile = (file: string): string => {\n\treturn file === \"\" || file === undefined ? STDOUT : join(CWD, file);\n};\n\nexport const validateConfigurationFile = (file: string): any => {\n\tconst result = {\n\t\texitCode: 0,\n\t\texitMessage: \"\",\n\t\tdata: {},\n\t};\n\tif (file === \"\") {\n\t\tresult.exitMessage = \"Please provide a configuration file\";\n\t\tresult.exitCode = 1;\n\t\treturn result;\n\t}\n\n\tconst configurationFile = file.startsWith(\"/\") ? file : join(CWD, file);\n\n\tif (fs.existsSync(configurationFile) === false) {\n\t\tresult.exitMessage = `Invalid or missing configuration file!\\n${configurationFile}`;\n\t\tresult.exitCode = 1;\n\t\treturn result;\n\t}\n\n\treturn {\n\t\t...result,\n\t\tdata: configurationFile,\n\t};\n};\n\nexport const readJSONFile = (file: string): any => {\n\ttry {\n\t\treturn {\n\t\t\texitCode: 0,\n\t\t\texitMessage: \"\",\n\t\t\tdata: fs.readJsonSync(file),\n\t\t};\n\t} catch (error) {\n\t\treturn {\n\t\t\texitCode: 1,\n\t\t\texitMessage: `Failed to read JSON file: ${error.message}`,\n\t\t};\n\t}\n};\n\nexport const getMostRecentVersion = (data: []) => {\n\tconst keys = [];\n\tfor (const key of Object.keys(data)) {\n\t\tkeys.push(key);\n\t}\n\tkeys.sort(semver.rcompare);\n\treturn keys[0];\n};\n\nexport const percentFormatter = new Intl.NumberFormat(\"en\", {\n\tstyle: \"percent\",\n\tsignDisplay: \"exceptZero\",\n\tminimumFractionDigits: 2,\n\tmaximumFractionDigits: 2,\n});\n\nexport const formatFooter = (limit: boolean, diff: number | string): string => {\n\treturn `Overall status: ${limit ? \"🚫\" : \"✅\"} ${diff}`;\n};\n\nexport const addMDrow = ({\n\ttype,\n\tpassed = true,\n\tname = \"\",\n\tsize = 0,\n\tdiff = \"\",\n\tlimit = \"\",\n\tcolumns,\n}: {\n\tcolumns: any;\n\ttype: \"header\" | \"row\";\n\tpassed?: boolean;\n\tname?: string;\n\tsize?: number;\n\tdiff?: string;\n\tlimit?: string;\n}) => {\n\tconst totalColumns = columns.length;\n\n\tif (type === \"header\") {\n\t\tconst separator = \" --- |\".repeat(totalColumns);\n\t\tconst header = columns.map((column) => column[Object.keys(column)[0]]);\n\t\treturn `| ${header.join(\" | \")} |\\n|${separator}`;\n\t}\n\tif (type === \"row\") {\n\t\tconst row = columns.map((column) => {\n\t\t\tconst key = Object.keys(column)[0];\n\t\t\tif (key === \"status\") {\n\t\t\t\treturn passed ? \"✅\" : \"🚫\";\n\t\t\t}\n\t\t\tif (key === \"file\") {\n\t\t\t\treturn name.replaceAll(\"<\", \"[\").replaceAll(\">\", \"]\");\n\t\t\t}\n\t\t\tif (key === \"size\") {\n\t\t\t\treturn `${bytes(size, {\n\t\t\t\t\tunitSeparator: \" \",\n\t\t\t\t})}${diff}`;\n\t\t\t}\n\t\t\tif (key === \"limits\") {\n\t\t\t\treturn limit;\n\t\t\t}\n\t\t\treturn \"\";\n\t\t});\n\n\t\treturn `| ${row.join(\" | \")} |`;\n\t}\n};\n"],"names":["bytes","fs","join","semver","zlib","STDOUT","CWD","process","cwd","gzipSizeFromFileSync","file","gzipSync","readFileSync","length","getOutputFile","undefined","validateConfigurationFile","result","exitCode","exitMessage","data","configurationFile","startsWith","existsSync","readJSONFile","readJsonSync","error","message","getMostRecentVersion","keys","key","Object","push","sort","rcompare","percentFormatter","Intl","NumberFormat","style","signDisplay","minimumFractionDigits","maximumFractionDigits","formatFooter","limit","diff","addMDrow","type","passed","name","size","columns","totalColumns","separator","repeat","header","map","column","row","replaceAll","unitSeparator"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@node-cli/bundlesize",
3
- "version": "2.1.4",
3
+ "version": "3.0.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",
@@ -32,10 +32,10 @@
32
32
  "bytes": "3.1.2",
33
33
  "fs-extra": "11.2.0",
34
34
  "glob": "10.3.10",
35
- "gzip-size": "7.0.0"
35
+ "semver": "7.5.4"
36
36
  },
37
37
  "publishConfig": {
38
38
  "access": "public"
39
39
  },
40
- "gitHead": "e7e1567e048693bb1665e6402291df2d16cfe69e"
40
+ "gitHead": "852bffa76c7fb0ad5bb594c7b71ffe68f8c8faff"
41
41
  }