@adobe/sizewatcher 1.0.0 → 1.2.1
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/CHANGELOG.md +35 -0
- package/README.md +171 -76
- package/index.js +2 -47
- package/lib/checkout.js +57 -28
- package/lib/comparators/custom.js +73 -17
- package/lib/comparators/git.js +2 -2
- package/lib/comparators/node_modules.js +42 -10
- package/lib/comparators/npm_package.js +74 -0
- package/lib/compare.js +64 -17
- package/lib/config.js +3 -3
- package/lib/render.js +44 -13
- package/lib/sizewatcher.js +74 -0
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
Improvements:
|
|
6
|
+
|
|
7
|
+
- [#41](https://github.com/adobe/sizewatcher/issues/41) Support project structures where package.json is in subdirectory. Introduces the `dir` config on comparators.
|
|
8
|
+
|
|
9
|
+
Fixes:
|
|
10
|
+
|
|
11
|
+
- [#46](https://github.com/adobe/sizewatcher/issues/46) Support running in a subdirectory of a git checkout.
|
|
12
|
+
|
|
13
|
+
- [#43](https://github.com/adobe/sizewatcher/issues/43) `node_modules` comparator failed if there were no dependencies
|
|
14
|
+
|
|
15
|
+
- [#42](https://github.com/adobe/sizewatcher/issues/42) `npm_package` failed if there was no version field in package.json
|
|
16
|
+
|
|
17
|
+
## 1.1.0
|
|
18
|
+
|
|
19
|
+
New Comparators:
|
|
20
|
+
|
|
21
|
+
- [#14](https://github.com/adobe/sizewatcher/issues/14) npm package size
|
|
22
|
+
|
|
23
|
+
Improvements:
|
|
24
|
+
|
|
25
|
+
- [#32](https://github.com/adobe/sizewatcher/issues/32) Improved PR comment that can be collapsed as whole, shows the summary result and is collapsed by default if the result is ok.
|
|
26
|
+
|
|
27
|
+
- [#33](https://github.com/adobe/sizewatcher/issues/33) Support `script` option in custom comparator. This allows to run build steps in case the output of those needs to be measured. The `path` option now also supports glob patterns.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
- [#31](https://github.com/adobe/sizewatcher/issues/31) Change default limits to make them less aggressive:
|
|
31
|
+
- fail: `100%`
|
|
32
|
+
- warn: `30%`
|
|
33
|
+
- ok: `-10%` (same)
|
|
34
|
+
|
|
35
|
+
- [#39](https://github.com/adobe/sizewatcher/issues/39) Detect if comparing the same branch and exit gracefully.
|
|
36
|
+
|
|
37
|
+
|
|
3
38
|
## 1.0.0
|
|
4
39
|
|
|
5
40
|
Initial version with comparators
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
<!-- code_chunk_output -->
|
|
12
12
|
|
|
13
|
-
- [
|
|
13
|
+
- [How it works](#how-it-works)
|
|
14
14
|
- [Requirements](#requirements)
|
|
15
15
|
- [Installation](#installation)
|
|
16
16
|
- [Usage](#usage)
|
|
@@ -28,88 +28,94 @@
|
|
|
28
28
|
- addition of large dependencies (and transient dependency trees)
|
|
29
29
|
- accidental addition of large binary files to the git repository
|
|
30
30
|
- sudden increase in build artifact size
|
|
31
|
-
- etc.
|
|
32
31
|
|
|
33
|
-
|
|
32
|
+
While any custom file or folder path can be measured via configuration, various types are automatically measured, including git repository, Node module dependencies and npm package size - see [comparators reference](#comparators-reference). More built-in languages & options are added over time and [contributions are welcome](#new-comparator).
|
|
34
33
|
|
|
35
|
-
`sizewatcher` runs as part of your CI and reports results as comment on the pull request or as github commit status (optional), allowing to block PRs if a certain threshold was exceeded.
|
|
34
|
+
`sizewatcher` runs as part of your CI and reports results as comment on the pull request or as github commit status (optional), allowing to block PRs if a certain threshold was exceeded.
|
|
35
|
+
|
|
36
|
+
This is an example of a `sizewatcher` Github PR comment with a failure (ignore the small numbers):
|
|
36
37
|
|
|
37
38
|
---
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
<details open><summary>❌ <a href="https://github.com/adobe/sizewatcher">Sizewatcher</a> detected a problematic size increase 📈:</summary>
|
|
41
|
+
<p><blockquote>
|
|
40
42
|
|
|
41
|
-
<details>
|
|
42
|
-
<summary>✅ <code>git</code> <b>+0.2%</b> (246 kB => 247 kB)</summary>
|
|
43
|
-
<br>
|
|
43
|
+
<details><summary>❌ <code>git</code> <b>+97.9%</b> (186 kB => 368 kB)</summary><br>Largest files in new changes:<pre>ecaf42b1d55c 4.5KiB lib/report.js<br>9c1a82fa7efb 2.8KiB lib/render.js<br>710a7c687b06 2.8KiB lib/render.js<br>e7a5a58b23a6 2.7KiB test/config.test.js<br>0a8f1a2ddb4f 2.4KiB test/config.test.js<br>6846cf298cd4 2.3KiB test/config.test.js<br>461b7663fd23 1.6KiB lib/config.js<br>a643d322cc26 1.5KiB lib/config.js<br>6db7d5c27a66 69B .sizewatcher.yml</pre></details>
|
|
44
44
|
|
|
45
|
-
Largest
|
|
46
|
-
```
|
|
47
|
-
360cccafa87c 974B .npmignore
|
|
48
|
-
```
|
|
49
|
-
</details>
|
|
45
|
+
<details><summary>✅ <code>node_modules</code> <b>-6.1%</b> (42.5 MB => 39.9 MB)</summary><br>Largest node modules:<pre>┌───────────────┬─────────────┬────────┐<br>│ name │ children │ size │<br>├───────────────┼─────────────┼────────┤<br>│ @octokit/rest │ 33 │ 11.25M │<br>├───────────────┼─────────────┼────────┤<br>│ js-yaml │ 3 │ 0.72M │<br>├───────────────┼─────────────┼────────┤<br>│ simple-git │ 2 │ 0.24M │<br>├───────────────┼─────────────┼────────┤<br>│ tmp │ 13 │ 0.22M │<br>├───────────────┼─────────────┼────────┤<br>│ debug │ 1 │ 0.08M │<br>├───────────────┼─────────────┼────────┤<br>│ deepmerge │ 0 │ 0.03M │<br>├───────────────┼─────────────┼────────┤<br>│ require-dir │ 0 │ 0.02M │<br>├───────────────┼─────────────┼────────┤<br>│ du │ 1 │ 0.01M │<br>├───────────────┼─────────────┼────────┤<br>│ pretty-bytes │ 0 │ 0.01M │<br>├───────────────┼─────────────┼────────┤<br>│ 9 modules │ 34 children │ 4.57M │<br>└───────────────┴─────────────┴────────┘</pre></details>
|
|
50
46
|
|
|
51
|
-
<details>
|
|
52
|
-
<summary>✅ <code>node_modules</code> has no changes (46.8 MB)</summary>
|
|
53
|
-
<br>
|
|
47
|
+
<details><summary>Notes</summary><br>
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
├───────────────┼─────────────┼────────┤
|
|
74
|
-
│ du │ 1 │ 0.01M │
|
|
75
|
-
├───────────────┼─────────────┼────────┤
|
|
76
|
-
│ pretty-bytes │ 0 │ 0.01M │
|
|
77
|
-
├───────────────┼─────────────┼────────┤
|
|
78
|
-
│ 9 modules │ 34 children │ 4.57M │
|
|
79
|
-
└───────────────┴─────────────┴────────┘
|
|
49
|
+
- PR branch: `testconfig` @ 21b66dfe6c3f6d09d4929ea2dec1e62cd1a7e7f2
|
|
50
|
+
- Base branch: `main`
|
|
51
|
+
- Sizewatcher v1.0.0
|
|
52
|
+
- Effective Configuration:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
limits:
|
|
56
|
+
fail: 100%
|
|
57
|
+
warn: 30%
|
|
58
|
+
ok: '-10%'
|
|
59
|
+
report:
|
|
60
|
+
githubComment: true
|
|
61
|
+
githubStatus: false
|
|
62
|
+
comparators:
|
|
63
|
+
git:
|
|
64
|
+
limits:
|
|
65
|
+
fail: 50%
|
|
66
|
+
custom: null
|
|
80
67
|
```
|
|
81
68
|
</details>
|
|
82
69
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
70
|
+
</blockquote></p>
|
|
71
|
+
</details>
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
And here if everything looks good:
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
<details ><summary>✅ <a href="https://github.com/adobe/sizewatcher">Sizewatcher</a> found no problematic size increases.</summary>
|
|
80
|
+
<p><blockquote>
|
|
86
81
|
|
|
87
|
-
|
|
82
|
+
<details><summary>✅ <code>git</code> <b>+97.9%</b> (186 kB => 368 kB)</summary><br>Largest files in new changes:<pre>ecaf42b1d55c 4.5KiB lib/report.js<br>9c1a82fa7efb 2.8KiB lib/render.js<br>710a7c687b06 2.8KiB lib/render.js<br>e7a5a58b23a6 2.7KiB test/config.test.js<br>0a8f1a2ddb4f 2.4KiB test/config.test.js<br>6846cf298cd4 2.3KiB test/config.test.js<br>461b7663fd23 1.6KiB lib/config.js<br>a643d322cc26 1.5KiB lib/config.js<br>6db7d5c27a66 69B .sizewatcher.yml</pre></details>
|
|
83
|
+
|
|
84
|
+
<details><summary>✅ <code>node_modules</code> <b>-6.1%</b> (42.5 MB => 39.9 MB)</summary><br>Largest node modules:<pre>┌───────────────┬─────────────┬────────┐<br>│ name │ children │ size │<br>├───────────────┼─────────────┼────────┤<br>│ @octokit/rest │ 33 │ 11.25M │<br>├───────────────┼─────────────┼────────┤<br>│ js-yaml │ 3 │ 0.72M │<br>├───────────────┼─────────────┼────────┤<br>│ simple-git │ 2 │ 0.24M │<br>├───────────────┼─────────────┼────────┤<br>│ tmp │ 13 │ 0.22M │<br>├───────────────┼─────────────┼────────┤<br>│ debug │ 1 │ 0.08M │<br>├───────────────┼─────────────┼────────┤<br>│ deepmerge │ 0 │ 0.03M │<br>├───────────────┼─────────────┼────────┤<br>│ require-dir │ 0 │ 0.02M │<br>├───────────────┼─────────────┼────────┤<br>│ du │ 1 │ 0.01M │<br>├───────────────┼─────────────┼────────┤<br>│ pretty-bytes │ 0 │ 0.01M │<br>├───────────────┼─────────────┼────────┤<br>│ 9 modules │ 34 children │ 4.57M │<br>└───────────────┴─────────────┴────────┘</pre></details>
|
|
85
|
+
|
|
86
|
+
<details><summary>Notes</summary><br>
|
|
87
|
+
|
|
88
|
+
- PR branch: `testconfig` @ 21b66dfe6c3f6d09d4929ea2dec1e62cd1a7e7f2
|
|
88
89
|
- Base branch: `main`
|
|
89
90
|
- Sizewatcher v1.0.0
|
|
90
|
-
- Configuration
|
|
91
|
-
|
|
91
|
+
- Effective Configuration:
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
92
94
|
limits:
|
|
93
|
-
fail:
|
|
94
|
-
warn:
|
|
95
|
+
fail: 100%
|
|
96
|
+
warn: 200%
|
|
95
97
|
ok: '-10%'
|
|
96
98
|
report:
|
|
97
99
|
githubComment: true
|
|
98
100
|
githubStatus: false
|
|
99
101
|
comparators: {}
|
|
100
|
-
|
|
102
|
+
```
|
|
103
|
+
</details>
|
|
104
|
+
|
|
105
|
+
</blockquote></p>
|
|
106
|
+
</details>
|
|
101
107
|
|
|
102
108
|
---
|
|
103
109
|
|
|
104
|
-
##
|
|
110
|
+
## How it works
|
|
105
111
|
|
|
106
112
|
By default `sizewatcher` will
|
|
107
113
|
- checkout the before and after branch versions in temporary directories
|
|
108
114
|
- go through all [comparators](#comparators-reference) that apply
|
|
109
115
|
- measure the sizes, compare and report
|
|
110
|
-
- fail ❌ at a
|
|
111
|
-
- warn ⚠️ at a
|
|
112
|
-
- report ok ✅ if the size
|
|
116
|
+
- fail ❌ at a 100%+ increase
|
|
117
|
+
- warn ⚠️ at a 30%+ increase
|
|
118
|
+
- report ok ✅ if the size change is between -10 and +30%
|
|
113
119
|
- cheer 🎉 if there is a 10% decrease
|
|
114
120
|
- print result in cli output
|
|
115
121
|
- report result as PR comment
|
|
@@ -121,6 +127,8 @@ By default `sizewatcher` will
|
|
|
121
127
|
|
|
122
128
|
- [Nodejs](https://nodejs.org) version 10+
|
|
123
129
|
- recommended to use latest stable version "LTS"
|
|
130
|
+
- Github or Github Enterprise
|
|
131
|
+
- if you want to run it on pull requests
|
|
124
132
|
- CI system running on Github pull requests
|
|
125
133
|
- [Github Actions](https://github.com/features/actions) (simplest setup)
|
|
126
134
|
- [Travis CI](https://travis-ci.org)
|
|
@@ -147,7 +155,7 @@ sizewatcher -h
|
|
|
147
155
|
|
|
148
156
|
Help output:
|
|
149
157
|
```
|
|
150
|
-
Usage:
|
|
158
|
+
Usage: sizewatcher [<options>] [<before> [<after>]]
|
|
151
159
|
|
|
152
160
|
Arguments:
|
|
153
161
|
<before> Before branch/commit for comparison. Defaults to default branch or main/master.
|
|
@@ -203,18 +211,31 @@ sizewatcher before after
|
|
|
203
211
|
|
|
204
212
|
## CI Setup
|
|
205
213
|
|
|
214
|
+
- [CI Overview](#ci-overview)
|
|
206
215
|
- [Github Actions](#github-actions)
|
|
207
216
|
- [Travis CI](#travis-ci)
|
|
208
217
|
- [CircleCI](#circleci)
|
|
209
218
|
- [Other CIs](#other-cis)
|
|
210
219
|
|
|
211
|
-
|
|
220
|
+
### CI Overview
|
|
221
|
+
|
|
222
|
+
#### Run using npx
|
|
223
|
+
|
|
224
|
+
To run `sizewatcher` in your CI, which is where it needs to run automatically for checking pull requests, it is best run using [npx](https://nodejs.dev/learn/the-npx-nodejs-package-runner), which comes pre-installed with nodejs and will automatically download and run the latest version in one go:
|
|
212
225
|
|
|
213
226
|
```
|
|
214
227
|
npx @adobe/sizewatcher
|
|
215
228
|
```
|
|
216
229
|
|
|
217
|
-
|
|
230
|
+
This command will always use the latest published version. In some cases it might be (temporarily) desireable to stick to a certain version, which can be achieved using:
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
npx @adobe/sizewatcher@1.0.0
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
#### GITHUB_TOKEN
|
|
237
|
+
|
|
238
|
+
To be able to automatically comment on the PR or report a commit status, a **github token** must be set as environment variable:
|
|
218
239
|
|
|
219
240
|
```
|
|
220
241
|
GITHUB_TOKEN=....
|
|
@@ -222,6 +243,19 @@ GITHUB_TOKEN=....
|
|
|
222
243
|
|
|
223
244
|
This token should be a service/bot user that has read/pull permission on the repository (allowing to comment). Note that the comments will be shown under that user's name. For example, you might want to create a user named `sizewatcher-bot` or the like. With Github Actions this is not required, it has a built-in `github-actions` bot user.
|
|
224
245
|
|
|
246
|
+
#### GITHUB_API_URL
|
|
247
|
+
|
|
248
|
+
If you use Github Enterprise, set the custom [Github API URL](https://docs.github.com/en/enterprise-server@2.21/rest/reference/enterprise-admin) in the `GITHUB_API_URL` environment variable:
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
GITHUB_API_URL=https://mygithub.company.com/api/v3/
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
This is not required for public github.com.
|
|
255
|
+
|
|
256
|
+
#### Order of steps
|
|
257
|
+
|
|
258
|
+
You can run `sizewatcher` before, after or in parallel to your main build step. It does not rely on output of a build, since it will check out the code (before and after PR versions) separately and needs to run any build steps itself, such as using `script` in the [custom comparator](#custom).
|
|
225
259
|
|
|
226
260
|
See below for CI specific setup.
|
|
227
261
|
|
|
@@ -245,8 +279,6 @@ jobs:
|
|
|
245
279
|
uses: actions/setup-node@v1
|
|
246
280
|
with:
|
|
247
281
|
node-version: '14'
|
|
248
|
-
# run your build/test first in case you want to measure build results
|
|
249
|
-
- run: npm install && npm test
|
|
250
282
|
# ---------- this runs sizewatcher ------------
|
|
251
283
|
- run: npx @adobe/sizewatcher
|
|
252
284
|
env:
|
|
@@ -258,7 +290,7 @@ jobs:
|
|
|
258
290
|
For [Travis CI](https://travis-ci.org) you need to
|
|
259
291
|
- use `language: node_js`
|
|
260
292
|
- if you already use a different language, find a way to ensure Nodejs 10+ is installed
|
|
261
|
-
- under `script` run `npx @adobe/sizewatcher
|
|
293
|
+
- under `script` run `npx @adobe/sizewatcher`
|
|
262
294
|
- set a secret environment variable `GITHUB_TOKEN` in the [Travis repository settings](https://docs.travis-ci.com/user/environment-variables/#defining-variables-in-repository-settings) with a github token with permission to comment on PRs and reporting commit statuses for the repository
|
|
263
295
|
|
|
264
296
|
Example `.travis.yml`:
|
|
@@ -270,8 +302,6 @@ install:
|
|
|
270
302
|
- npm install
|
|
271
303
|
|
|
272
304
|
script:
|
|
273
|
-
# run your build/test first in case you want to measure build results
|
|
274
|
-
- npm test
|
|
275
305
|
# ---------- this runs sizewatcher ------------
|
|
276
306
|
- npx @adobe/sizewatcher
|
|
277
307
|
```
|
|
@@ -281,7 +311,7 @@ script:
|
|
|
281
311
|
For [CircleCI](https://circleci.com) you need to
|
|
282
312
|
- use a docker image with Nodejs 10+ installed
|
|
283
313
|
- alternatively install [using nvm](https://www.google.com/search?q=circleci+use+nvm)
|
|
284
|
-
- run `npx @adobe/sizewatcher
|
|
314
|
+
- run `npx @adobe/sizewatcher`
|
|
285
315
|
- set a secret environment variable `GITHUB_TOKEN` in the [CircleCI project settings](https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-project) (or in a [Context](https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-context)) with a github token with permission to comment on PRs and reporting commit statuses for the repository
|
|
286
316
|
|
|
287
317
|
Example `.circleci/config.yml`:
|
|
@@ -296,10 +326,6 @@ jobs:
|
|
|
296
326
|
steps:
|
|
297
327
|
- checkout
|
|
298
328
|
|
|
299
|
-
# run your build/test first in case you want to measure build results
|
|
300
|
-
- run: npm install
|
|
301
|
-
- run: npm test
|
|
302
|
-
|
|
303
329
|
# ---------- this runs sizewatcher ------------
|
|
304
330
|
- run: npx @adobe/sizewatcher
|
|
305
331
|
```
|
|
@@ -316,7 +342,7 @@ Set these environment variables in the CI job:
|
|
|
316
342
|
- `GITHUB_HEAD_REF` name of the branch of the pull request
|
|
317
343
|
- `GITHUB_TOKEN` a github token with permission to comment on PRs and reporting commit statuses for the repository. This is a credential so use the proper CI credential management and never check this into the git repository!
|
|
318
344
|
|
|
319
|
-
|
|
345
|
+
Then simply run
|
|
320
346
|
|
|
321
347
|
```
|
|
322
348
|
npx @adobe/sizewatcher
|
|
@@ -345,10 +371,10 @@ report:
|
|
|
345
371
|
# see https://www.npmjs.com/package/xbytes
|
|
346
372
|
# - absolute limit, as byte number: 1000000
|
|
347
373
|
limits:
|
|
348
|
-
# when to fail - default:
|
|
374
|
+
# when to fail - default: 100%
|
|
349
375
|
fail: 50%
|
|
350
|
-
# when to warn - default:
|
|
351
|
-
warn:
|
|
376
|
+
# when to warn - default: 30%
|
|
377
|
+
warn: 10%
|
|
352
378
|
# below the ok limit you will get a cheers for making it notably smaller
|
|
353
379
|
# default: -10%
|
|
354
380
|
ok: -5%
|
|
@@ -369,6 +395,15 @@ comparators:
|
|
|
369
395
|
warn: 9 MB
|
|
370
396
|
ok: 1 MB
|
|
371
397
|
|
|
398
|
+
npm_package:
|
|
399
|
+
# `dir` is supported for all comparators that support it and
|
|
400
|
+
# specifies the relative directory inside the project inside which to run the comparator
|
|
401
|
+
dir: "sub/folder"
|
|
402
|
+
# can also be an array with multiple directories to check
|
|
403
|
+
dir:
|
|
404
|
+
- "sub/folder1"
|
|
405
|
+
- "folder2"
|
|
406
|
+
|
|
372
407
|
# custom comparator (only active if configured)
|
|
373
408
|
custom:
|
|
374
409
|
- name: my artifact
|
|
@@ -379,7 +414,10 @@ comparators:
|
|
|
379
414
|
|
|
380
415
|
# there can be multiple custom comparators
|
|
381
416
|
# name defaults to the path
|
|
382
|
-
|
|
417
|
+
# path can include glob patterns
|
|
418
|
+
- path: "artifact-*.tgz"
|
|
419
|
+
# run some custom build command before measuring
|
|
420
|
+
script: npm install
|
|
383
421
|
# limits can be configured as well
|
|
384
422
|
limits:
|
|
385
423
|
fail: 10 MB
|
|
@@ -389,6 +427,7 @@ comparators:
|
|
|
389
427
|
|
|
390
428
|
- [git](#git)
|
|
391
429
|
- [node_modules](#node_modules)
|
|
430
|
+
- [npm_package](#npm_package)
|
|
392
431
|
- [custom](#custom)
|
|
393
432
|
|
|
394
433
|
### git
|
|
@@ -451,6 +490,52 @@ Largest node modules:
|
|
|
451
490
|
└───────────────┴─────────────┴────────┘
|
|
452
491
|
```
|
|
453
492
|
|
|
493
|
+
Configuration: supports `dir`
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
### npm_package
|
|
498
|
+
|
|
499
|
+
Compares the size of an npm package tarball by running `npm publish --dry-run`.
|
|
500
|
+
|
|
501
|
+
Name: `npm_package`
|
|
502
|
+
|
|
503
|
+
Trigger: Runs if a `package.json` is found.
|
|
504
|
+
|
|
505
|
+
Details: Prints the package contents and metadata using the output of `npm publish --dry-run`.
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
Package contents:
|
|
509
|
+
|
|
510
|
+
```
|
|
511
|
+
📦 @adobe/sizewatcher@1.0.0
|
|
512
|
+
=== Tarball Contents ===
|
|
513
|
+
11.3kB LICENSE
|
|
514
|
+
4.8kB lib/checkout.js
|
|
515
|
+
5.2kB lib/compare.js
|
|
516
|
+
1.7kB lib/config.js
|
|
517
|
+
3.9kB lib/comparators/custom.js
|
|
518
|
+
2.3kB lib/comparators/git.js
|
|
519
|
+
2.6kB lib/github.js
|
|
520
|
+
2.1kB index.js
|
|
521
|
+
2.2kB lib/comparators/node_modules.js
|
|
522
|
+
3.9kB lib/render.js
|
|
523
|
+
4.6kB lib/report.js
|
|
524
|
+
1.1kB package.json
|
|
525
|
+
695B CHANGELOG.md
|
|
526
|
+
23.9kB README.md
|
|
527
|
+
=== Tarball Details ===
|
|
528
|
+
name: @adobe/sizewatcher
|
|
529
|
+
version: 1.0.0
|
|
530
|
+
package size: 18.6 kB
|
|
531
|
+
unpacked size: 70.3 kB
|
|
532
|
+
shasum: 80846caccca2194f3dd1122e8113206e20c202dc
|
|
533
|
+
integrity: sha512-c3VjMQQvqcqN8[...]ybMS6kg2chjpA==
|
|
534
|
+
total files: 14
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Configuration: supports `dir`
|
|
538
|
+
|
|
454
539
|
---
|
|
455
540
|
|
|
456
541
|
### custom
|
|
@@ -479,7 +564,16 @@ This comparator requires configuration in the `.sizewatcher.yml`:
|
|
|
479
564
|
```yaml
|
|
480
565
|
comparators:
|
|
481
566
|
custom:
|
|
482
|
-
path:
|
|
567
|
+
path: src/somefile
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
If a build step is required first, use `script`:
|
|
571
|
+
|
|
572
|
+
```yaml
|
|
573
|
+
comparators:
|
|
574
|
+
custom:
|
|
575
|
+
path: "build/artifact-*.tgz"
|
|
576
|
+
script: npm run build
|
|
483
577
|
```
|
|
484
578
|
|
|
485
579
|
To have multiple paths, with custom names:
|
|
@@ -488,14 +582,15 @@ To have multiple paths, with custom names:
|
|
|
488
582
|
comparators:
|
|
489
583
|
custom:
|
|
490
584
|
- name: my artifact 1
|
|
491
|
-
path: build/artifact
|
|
585
|
+
path: "build/artifact"
|
|
492
586
|
- name: my artifact 2
|
|
493
|
-
path: build/artifact2
|
|
587
|
+
path: "build/artifact2"
|
|
494
588
|
```
|
|
495
589
|
|
|
496
590
|
Options:
|
|
497
|
-
- `path` (required) relative path to file or folder to measure
|
|
591
|
+
- `path` (required) relative path to file or folder to measure; supports [glob patterns](https://www.npmjs.com/package/glob). make sure to use quotes in the yaml if you use glob patterns
|
|
498
592
|
- `name` (optional) custom label
|
|
593
|
+
- `script` (optional) shell command to run before measuring `path`
|
|
499
594
|
- `limits` can be set as usual
|
|
500
595
|
|
|
501
596
|
## Contribute
|
package/index.js
CHANGED
|
@@ -14,50 +14,5 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict';
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const gitCheckoutBeforeAndAfter = require("./lib/checkout");
|
|
20
|
-
const compare = require("./lib/compare");
|
|
21
|
-
const report = require("./lib/report");
|
|
22
|
-
|
|
23
|
-
function printUsage() {
|
|
24
|
-
console.log(`Usage: ${process.argv[1]} [<options>] [<before> [<after>]]`);
|
|
25
|
-
console.log();
|
|
26
|
-
console.log("Arguments:");
|
|
27
|
-
console.log(" <before> Before branch/commit for comparison. Defaults to default branch or main/master.");
|
|
28
|
-
console.log(" <after> After branch/commit for comparison. Defaults to current branch.");
|
|
29
|
-
console.log();
|
|
30
|
-
console.log("Options:");
|
|
31
|
-
console.log(" -h Show help");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function main(argv) {
|
|
35
|
-
try {
|
|
36
|
-
// yes, this should probably use a framework like oclif or yargs.
|
|
37
|
-
// but cli arguments are very limited now and so manual handling is enough
|
|
38
|
-
if (argv[0] === "-h") {
|
|
39
|
-
printUsage();
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// TODO: detect main = main case
|
|
44
|
-
|
|
45
|
-
console.log(`Cloning git repository...`);
|
|
46
|
-
const { before, after } = await gitCheckoutBeforeAndAfter(process.cwd(), argv[0], argv[1]);
|
|
47
|
-
|
|
48
|
-
console.log(`Comparing changes from '${before.branch}' to '${after.branch}'\n`);
|
|
49
|
-
|
|
50
|
-
const deltas = await compare(before, after);
|
|
51
|
-
|
|
52
|
-
await report(deltas);
|
|
53
|
-
|
|
54
|
-
} catch (e) {
|
|
55
|
-
console.error(e);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
} finally {
|
|
58
|
-
console.log("Done. Cleaning up...");
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
main(process.argv.slice(2));
|
|
17
|
+
const sizewatcher = require("./lib/sizewatcher");
|
|
18
|
+
sizewatcher(process.argv.slice(2));
|
package/lib/checkout.js
CHANGED
|
@@ -18,7 +18,13 @@ const debug = require("debug")("sizewatcher");
|
|
|
18
18
|
const tmp = require("tmp");
|
|
19
19
|
tmp.setGracefulCleanup();
|
|
20
20
|
const path = require("path");
|
|
21
|
-
const Git = require("simple-git
|
|
21
|
+
const Git = require("simple-git");
|
|
22
|
+
|
|
23
|
+
async function getGitRoot(dir) {
|
|
24
|
+
// --git-dir will return relative directories
|
|
25
|
+
const gitRootDir = await Git(dir).revparse(["--git-dir"]);
|
|
26
|
+
return path.resolve(dir, gitRootDir);
|
|
27
|
+
}
|
|
22
28
|
|
|
23
29
|
async function currentBranch(dir) {
|
|
24
30
|
const git = Git(dir);
|
|
@@ -37,10 +43,14 @@ async function currentSha(dir) {
|
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
async function getDefaultBranch(dir) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
try {
|
|
47
|
+
const originInfo = await Git(dir).remote(["show", "origin"]);
|
|
48
|
+
const m = originInfo.match(/HEAD branch: (.*)$/m);
|
|
49
|
+
if (m) {
|
|
50
|
+
return m[1];
|
|
51
|
+
}
|
|
52
|
+
} catch (e) {
|
|
53
|
+
debug(`ignoring error in getDefaultBranch(): ${e.message}`);
|
|
44
54
|
}
|
|
45
55
|
}
|
|
46
56
|
|
|
@@ -48,22 +58,12 @@ async function hasBranch(dir, branch) {
|
|
|
48
58
|
try {
|
|
49
59
|
await Git(dir).revparse(["--verify", branch]);
|
|
50
60
|
return branch;
|
|
51
|
-
} catch (e) {
|
|
61
|
+
} catch (e) {
|
|
62
|
+
debug(`ignoring error in hasBranch(): ${e.message}`);
|
|
52
63
|
return undefined;
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
async function hasRemoteTrackingBranch(dir) {
|
|
57
|
-
const git = Git(dir);
|
|
58
|
-
try {
|
|
59
|
-
const remoteBranch = await git.revparse(["--abbrev-ref", "--symbolic-full-name", "@{u}"]);
|
|
60
|
-
console.log(remoteBranch);
|
|
61
|
-
return true;
|
|
62
|
-
} catch (e) { // eslint-disable-line no-unused-vars
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
67
|
async function getPullRequestBaseBranch() {
|
|
68
68
|
return process.env.GITHUB_BASE_REF
|
|
69
69
|
|| (process.env.TRAVIS_PULL_REQUEST !== "false" && process.env.TRAVIS_BRANCH);
|
|
@@ -101,36 +101,65 @@ async function cloneRemoteAndCheckout(from, to, branch) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
async function cloneLocalAndCheckout(from, to, branch) {
|
|
104
|
-
|
|
105
|
-
await Git().clone(from, to, { local: true });
|
|
104
|
+
const gitRoot = await getGitRoot(from);
|
|
106
105
|
|
|
107
|
-
debug(`
|
|
108
|
-
await Git(
|
|
106
|
+
debug(`local cloning ${gitRoot} into ${to}`);
|
|
107
|
+
await Git().clone(gitRoot, to, { '--local' : true });
|
|
108
|
+
|
|
109
|
+
if (branch) {
|
|
110
|
+
debug(`checking out '${branch}'...`);
|
|
111
|
+
await Git(to).checkout(branch);
|
|
112
|
+
}
|
|
109
113
|
}
|
|
110
114
|
|
|
111
115
|
async function gitCheckoutBeforeAndAfter(gitDir, beforeBranch, afterBranch) {
|
|
116
|
+
debug(`retrieving before branch: ${beforeBranch}`);
|
|
112
117
|
beforeBranch = await getBeforeBranch(gitDir, beforeBranch);
|
|
118
|
+
debug(`retrieving after branch: ${afterBranch}`);
|
|
113
119
|
afterBranch = await getAfterBranch(gitDir, afterBranch);
|
|
120
|
+
if (beforeBranch === afterBranch) {
|
|
121
|
+
return {
|
|
122
|
+
before: {
|
|
123
|
+
branch: beforeBranch
|
|
124
|
+
},
|
|
125
|
+
after: {
|
|
126
|
+
branch: afterBranch,
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
114
130
|
|
|
115
131
|
debug(`cloning branches ${beforeBranch} (before) and ${afterBranch} (after)...`);
|
|
116
132
|
|
|
117
133
|
const tempDir = tmp.dirSync({unsafeCleanup: true}).name;
|
|
118
134
|
debug(`temporary directory: ${tempDir}`);
|
|
119
135
|
|
|
120
|
-
|
|
121
|
-
await cloneRemoteAndCheckout(gitDir, beforeDir, beforeBranch);
|
|
136
|
+
// before checkout --------------------------------------------------------------
|
|
122
137
|
|
|
123
|
-
const
|
|
124
|
-
if (process.env.CI || await hasRemoteTrackingBranch(afterDir)) {
|
|
125
|
-
await cloneRemoteAndCheckout(gitDir, afterDir, afterBranch);
|
|
138
|
+
const beforeDir = path.join(tempDir, "before");
|
|
126
139
|
|
|
140
|
+
// we can use the faster local clone only if the "branch" is present locally
|
|
141
|
+
if (await hasBranch(gitDir, beforeBranch)) {
|
|
142
|
+
debug(`[before] using local cloning because existing checkout already has before branch '${beforeBranch}'`);
|
|
143
|
+
await cloneLocalAndCheckout(gitDir, beforeDir, beforeBranch);
|
|
127
144
|
} else {
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
debug(`[before] using remote cloning, as existing checkout does not have before branch '${beforeBranch}'`);
|
|
146
|
+
// ...otherwise need to do a full remote clone
|
|
147
|
+
await cloneRemoteAndCheckout(gitDir, beforeDir, beforeBranch);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// after checkout --------------------------------------------------------------
|
|
130
151
|
|
|
152
|
+
const afterDir = path.join(tempDir, "after");
|
|
153
|
+
|
|
154
|
+
debug(`[after] using local cloning`);
|
|
155
|
+
await cloneLocalAndCheckout(gitDir, afterDir);
|
|
156
|
+
try {
|
|
131
157
|
// need to ensure before branch is available under local name
|
|
132
158
|
await Git(afterDir).branch([beforeBranch, "-t", `origin/${beforeBranch}`]);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
debug("ignoring error", e);
|
|
133
161
|
}
|
|
162
|
+
debug(`[after] current branch: ${await currentBranch(afterDir)}`);
|
|
134
163
|
|
|
135
164
|
debug("folders ready");
|
|
136
165
|
|
|
@@ -19,41 +19,97 @@ const du = require("du");
|
|
|
19
19
|
const path = require("path");
|
|
20
20
|
const fs = require("fs");
|
|
21
21
|
const pb = require('pretty-bytes');
|
|
22
|
+
const { spawn } = require('child_process');
|
|
23
|
+
const glob = require("glob");
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
async function spawnProcess(command, args, options) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const child = spawn(command, args, options);
|
|
24
28
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
child.on("error", (err) => reject(err));
|
|
30
|
+
child.on("exit", (code, signal) => {
|
|
31
|
+
if (code !== 0) {
|
|
32
|
+
let cmdString = command;
|
|
33
|
+
if (args.length > 0) {
|
|
34
|
+
cmdString += ` ${args.join(" ")}`;
|
|
35
|
+
}
|
|
36
|
+
const err = new Error(`\`${cmdString}\` failed with exit code ${code}${signal ? `(received signal: ${signal})` : ""}`);
|
|
37
|
+
err.exitCode = code;
|
|
38
|
+
err.signal = signal;
|
|
39
|
+
reject(err);
|
|
40
|
+
}
|
|
41
|
+
resolve();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function runScript(script, path) {
|
|
47
|
+
await spawnProcess(script, [], {
|
|
48
|
+
shell: true,
|
|
49
|
+
cwd: path,
|
|
50
|
+
// log / pass-through all output
|
|
51
|
+
stdio: "inherit"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function measure(baseDir, globPath, fileListing) {
|
|
56
|
+
let total = 0;
|
|
57
|
+
const files = glob.sync(path.join(baseDir, globPath));
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
if (fs.existsSync(file)) {
|
|
60
|
+
const fileSize = await du(file);
|
|
61
|
+
total += fileSize;
|
|
62
|
+
|
|
63
|
+
if (fileListing) {
|
|
64
|
+
fileListing.push(`${pb(fileSize)} ${path.relative(baseDir, file)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return total;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
28
72
|
|
|
29
73
|
shouldRun: async function(beforeDir, afterDir) {
|
|
30
|
-
|
|
31
|
-
if (!p) {
|
|
74
|
+
if (!this.config.path || typeof this.config.path !== "string") {
|
|
32
75
|
return false;
|
|
33
76
|
}
|
|
34
|
-
if (
|
|
35
|
-
|
|
77
|
+
// if it has a script, we have to run (the script might create the files)
|
|
78
|
+
if (typeof this.config.script === "string") {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (this.config.path.startsWith("/")) {
|
|
83
|
+
console.error("Error: custom comparator path must be relative:", this.config.path);
|
|
36
84
|
return false;
|
|
37
85
|
}
|
|
38
|
-
return
|
|
39
|
-
|
|
86
|
+
return glob.sync(path.join(beforeDir, this.config.path)).length > 0 ||
|
|
87
|
+
glob.sync(path.join(afterDir, this.config.path)).length > 0;
|
|
40
88
|
},
|
|
41
89
|
|
|
42
90
|
compare: async function(before, after) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
91
|
+
if (this.config.script) {
|
|
92
|
+
debug(`running script in ${before.dir}: ${this.config.script}`);
|
|
93
|
+
await runScript(this.config.script, before.dir);
|
|
94
|
+
|
|
95
|
+
debug(`running script in ${after.dir}: ${this.config.script}`);
|
|
96
|
+
await runScript(this.config.script, after.dir);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const fileListing = [];
|
|
100
|
+
|
|
101
|
+
debug(`measuring ${this.config.path} in ${before.dir}...`);
|
|
102
|
+
const beforeSize = await measure(before.dir, this.config.path);
|
|
46
103
|
|
|
47
|
-
debug(`measuring
|
|
48
|
-
const
|
|
49
|
-
const afterSize = fs.existsSync(afterPath) ? await du(afterPath) : 0;
|
|
104
|
+
debug(`measuring ${this.config.path} in ${after.dir}...`);
|
|
105
|
+
const afterSize = await measure(after.dir, this.config.path, fileListing);
|
|
50
106
|
|
|
51
107
|
return {
|
|
52
108
|
name: this.config.name || this.config.path,
|
|
53
109
|
beforeSize,
|
|
54
110
|
afterSize,
|
|
55
111
|
detailsLabel: "New size",
|
|
56
|
-
details:
|
|
112
|
+
details: fileListing.join("\n")
|
|
57
113
|
};
|
|
58
114
|
}
|
|
59
115
|
};
|
package/lib/comparators/git.js
CHANGED
|
@@ -19,7 +19,7 @@ const du = require("du");
|
|
|
19
19
|
const path = require("path");
|
|
20
20
|
const fs = require("fs");
|
|
21
21
|
const { execSync } = require("child_process");
|
|
22
|
-
const Git = require("simple-git
|
|
22
|
+
const Git = require("simple-git");
|
|
23
23
|
|
|
24
24
|
async function getSize(dir) {
|
|
25
25
|
debug(`git garbage collection of '${dir}'...`);
|
|
@@ -50,7 +50,7 @@ module.exports = {
|
|
|
50
50
|
compare: async function(before, after) {
|
|
51
51
|
const beforeSize = await getSize(before.dir);
|
|
52
52
|
const afterSize = await getSize(after.dir);
|
|
53
|
-
const details = await largestCommits(after.dir, before.branch, after.
|
|
53
|
+
const details = await largestCommits(after.dir, before.branch, after.sha);
|
|
54
54
|
|
|
55
55
|
return {
|
|
56
56
|
beforeSize,
|
|
@@ -20,34 +20,66 @@ const du = require("du");
|
|
|
20
20
|
const path = require("path");
|
|
21
21
|
const fs = require("fs");
|
|
22
22
|
|
|
23
|
+
function isEmptyObject(obj) {
|
|
24
|
+
return Object.keys(obj).length === 0 && obj.constructor === Object;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function hasPackageJson(dir) {
|
|
28
|
+
return fs.existsSync(path.join(dir, "package.json"));
|
|
29
|
+
}
|
|
30
|
+
|
|
23
31
|
async function getSize(dir) {
|
|
32
|
+
if (!hasPackageJson(dir)) {
|
|
33
|
+
return 0;
|
|
34
|
+
}
|
|
24
35
|
process.chdir(dir);
|
|
36
|
+
|
|
25
37
|
debug(`installing node dependencies inside ${dir}...`);
|
|
26
38
|
if (fs.existsSync("package-lock.json") || fs.existsSync("npm-shrinkwrap.json")) {
|
|
27
39
|
execSync("npm ci");
|
|
28
40
|
} else {
|
|
29
41
|
execSync("npm install");
|
|
30
42
|
}
|
|
31
|
-
|
|
32
|
-
|
|
43
|
+
|
|
44
|
+
const nodeModules = path.join(dir, "node_modules");
|
|
45
|
+
if (fs.existsSync(nodeModules)) {
|
|
46
|
+
debug(`calculating folder size of ${nodeModules}...`);
|
|
47
|
+
return du(nodeModules);
|
|
48
|
+
} else {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
async function costOfModules(dir) {
|
|
54
|
+
if (!hasPackageJson(dir)) {
|
|
55
|
+
return "(no package.json)";
|
|
56
|
+
}
|
|
36
57
|
process.chdir(dir);
|
|
37
|
-
debug(`running cost-of-modules...`);
|
|
38
|
-
const out = execSync("npx cost-of-modules --less --no-install").toString();
|
|
39
58
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
59
|
+
const pkgJsonPath = path.join(dir, "package.json");
|
|
60
|
+
const pkgJson = require(pkgJsonPath);
|
|
61
|
+
if (!pkgJson.dependencies || isEmptyObject(pkgJson.dependencies)) {
|
|
62
|
+
return "(no production dependencies)";
|
|
43
63
|
}
|
|
44
|
-
|
|
64
|
+
|
|
65
|
+
debug(`running cost-of-modules...`);
|
|
66
|
+
try {
|
|
67
|
+
const out = execSync("npx cost-of-modules --less --no-install").toString();
|
|
68
|
+
const PREFIX = "\nCalculating...\n\n\n";
|
|
69
|
+
if (out.startsWith(PREFIX)) {
|
|
70
|
+
return out.substring(PREFIX.length);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
throw new Error(`cost-of-modules failed with: ${e.stdout ? e.stdout.toString() : ""} ${e.stdout ? e.stderr.toString() : ""}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
45
77
|
}
|
|
46
78
|
|
|
47
79
|
module.exports = {
|
|
48
80
|
|
|
49
81
|
shouldRun: async function(beforeDir, afterDir) {
|
|
50
|
-
return
|
|
82
|
+
return hasPackageJson(beforeDir) || hasPackageJson(afterDir);
|
|
51
83
|
},
|
|
52
84
|
|
|
53
85
|
compare: async function(before, after) {
|
|
@@ -58,7 +90,7 @@ module.exports = {
|
|
|
58
90
|
return {
|
|
59
91
|
beforeSize,
|
|
60
92
|
afterSize,
|
|
61
|
-
detailsLabel: "Largest node modules",
|
|
93
|
+
detailsLabel: "Largest production node modules",
|
|
62
94
|
details
|
|
63
95
|
};
|
|
64
96
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// comparators/npm_package.js - compares npm package (tarball) size
|
|
16
|
+
|
|
17
|
+
const debug = require("debug")("sizewatcher:node_modules");
|
|
18
|
+
const util = require('util');
|
|
19
|
+
const exec = util.promisify(require('child_process').exec);
|
|
20
|
+
const path = require("path");
|
|
21
|
+
const fs = require("fs");
|
|
22
|
+
const xbytes = require("xbytes");
|
|
23
|
+
|
|
24
|
+
const PACKSIZE_REGEX = /package size: (.*)/;
|
|
25
|
+
|
|
26
|
+
async function getSize(dir) {
|
|
27
|
+
if (!fs.existsSync(path.join(dir, "package.json"))) {
|
|
28
|
+
return {
|
|
29
|
+
size: 0
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
debug(`running npm publish --dry-run inside ${dir}...`);
|
|
34
|
+
let { stderr } = await exec("npm publish --dry-run", { cwd: dir });
|
|
35
|
+
|
|
36
|
+
stderr = stderr.replace(/npm notice /g, "");
|
|
37
|
+
|
|
38
|
+
const found = stderr.match(PACKSIZE_REGEX);
|
|
39
|
+
const size = found ? xbytes.parseSize(found[1].trim()) : 0;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
size,
|
|
43
|
+
output: stderr
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = {
|
|
48
|
+
|
|
49
|
+
shouldRun: async function(beforeDir, afterDir) {
|
|
50
|
+
const pkgJsonPath = path.join(afterDir, "package.json");
|
|
51
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const pkgJson = require(pkgJsonPath);
|
|
56
|
+
if (pkgJson.private || !pkgJson.version) {
|
|
57
|
+
debug("skpping npm_package: private package or no version in package.json");
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
compare: async function(before, after) {
|
|
64
|
+
const beforeResult = await getSize(before.dir);
|
|
65
|
+
const afterResult = await getSize(after.dir);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
beforeSize: beforeResult.size,
|
|
69
|
+
afterSize: afterResult.size,
|
|
70
|
+
detailsLabel: "Package contents",
|
|
71
|
+
details: afterResult.output
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
package/lib/compare.js
CHANGED
|
@@ -18,18 +18,28 @@ const debug = require("debug")("sizewatcher");
|
|
|
18
18
|
const config = require("./config").get();
|
|
19
19
|
const COMPARATORS = require('require-dir')("./comparators");
|
|
20
20
|
const xbytes = require('xbytes');
|
|
21
|
+
const path = require("path");
|
|
21
22
|
|
|
22
23
|
async function runComparator(comparator, before, after, deltas) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
try {
|
|
25
|
+
if (await comparator.shouldRun(before.dir, after.dir)) {
|
|
26
|
+
console.log(`- ${comparator.name}`);
|
|
27
|
+
const delta = await comparator.compare(before, after);
|
|
28
|
+
// force same name for consistency with config
|
|
29
|
+
delta.name = comparator.name;
|
|
30
|
+
delta.config = comparator.config;
|
|
31
|
+
|
|
32
|
+
deltas.push(delta);
|
|
33
|
+
} else {
|
|
34
|
+
debug(`skipping '${comparator.name}' because it detected it should not run`);
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(`comparator ${comparator.name} failed:`, e);
|
|
38
|
+
|
|
39
|
+
deltas.push({
|
|
40
|
+
name: comparator.name,
|
|
41
|
+
error: e
|
|
42
|
+
});
|
|
33
43
|
}
|
|
34
44
|
}
|
|
35
45
|
|
|
@@ -63,10 +73,30 @@ async function compare(before, after) {
|
|
|
63
73
|
debug(`skipping '${name}' because disabled in config`);
|
|
64
74
|
continue;
|
|
65
75
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
76
|
+
let dirs = ["."];
|
|
77
|
+
if (comparatorConfig && comparatorConfig.dir) {
|
|
78
|
+
if (Array.isArray(comparatorConfig.dir)) {
|
|
79
|
+
dirs = comparatorConfig.dir;
|
|
80
|
+
} else {
|
|
81
|
+
dirs = [comparatorConfig.dir];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const dir of dirs) {
|
|
85
|
+
if (dir !== ".") {
|
|
86
|
+
comparator.name = `${name} [${dir}]`;
|
|
87
|
+
} else {
|
|
88
|
+
comparator.name = name;
|
|
89
|
+
}
|
|
90
|
+
comparator.config = comparatorConfig || {};
|
|
91
|
+
|
|
92
|
+
await runComparator(comparator, {
|
|
93
|
+
...before,
|
|
94
|
+
dir: path.join(before.dir, dir)
|
|
95
|
+
}, {
|
|
96
|
+
...after,
|
|
97
|
+
dir: path.join(after.dir, dir)
|
|
98
|
+
}, deltas);
|
|
99
|
+
}
|
|
70
100
|
}
|
|
71
101
|
}
|
|
72
102
|
|
|
@@ -101,17 +131,32 @@ function process(deltas) {
|
|
|
101
131
|
cheers: 0,
|
|
102
132
|
ok: 0,
|
|
103
133
|
warn: 0,
|
|
104
|
-
fail: 0
|
|
134
|
+
fail: 0,
|
|
135
|
+
error: 0
|
|
105
136
|
};
|
|
106
137
|
|
|
107
138
|
for (const d of deltas) {
|
|
139
|
+
debug("processing delta:", d);
|
|
140
|
+
if (d.error) {
|
|
141
|
+
summary.error++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
108
144
|
// merge limit config, comparator-specific one wins over global
|
|
109
145
|
const limits = {
|
|
110
146
|
...config.limits,
|
|
111
147
|
...d.config.limits
|
|
112
148
|
};
|
|
113
149
|
|
|
114
|
-
|
|
150
|
+
let increase;
|
|
151
|
+
if (d.beforeSize <= 0 && d.afterSize <= 0) {
|
|
152
|
+
increase = 0;
|
|
153
|
+
} else if (d.afterSize <= 0) {
|
|
154
|
+
increase = -100;
|
|
155
|
+
} else if (d.beforeSize <= 0) {
|
|
156
|
+
increase = 100;
|
|
157
|
+
} else {
|
|
158
|
+
increase = (d.afterSize - d.beforeSize) / d.beforeSize * 100;
|
|
159
|
+
}
|
|
115
160
|
d.increase = increase.toFixed(1);
|
|
116
161
|
|
|
117
162
|
if (exceeds(d.afterSize, increase, limits.fail)) {
|
|
@@ -134,7 +179,9 @@ function process(deltas) {
|
|
|
134
179
|
}
|
|
135
180
|
|
|
136
181
|
deltas.summary = "ok";
|
|
137
|
-
if (summary.
|
|
182
|
+
if (summary.error >= 1) {
|
|
183
|
+
deltas.summary = "error";
|
|
184
|
+
} else if (summary.fail >= 1) {
|
|
138
185
|
deltas.summary = "fail";
|
|
139
186
|
} else if (summary.warn >= 1) {
|
|
140
187
|
deltas.summary = "warn";
|
package/lib/config.js
CHANGED
|
@@ -22,8 +22,8 @@ const CONFIG_FILE = ".sizewatcher.yml";
|
|
|
22
22
|
|
|
23
23
|
const DEFAULT_CONFIG = {
|
|
24
24
|
limits: {
|
|
25
|
-
fail: "
|
|
26
|
-
warn: "
|
|
25
|
+
fail: "100%",
|
|
26
|
+
warn: "30%",
|
|
27
27
|
ok: "-10%"
|
|
28
28
|
},
|
|
29
29
|
report: {
|
|
@@ -39,7 +39,7 @@ function load() {
|
|
|
39
39
|
|
|
40
40
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
41
41
|
const data = fs.readFileSync(CONFIG_FILE, "utf8");
|
|
42
|
-
const loadedConfig = yaml.safeLoad(data);
|
|
42
|
+
const loadedConfig = yaml.safeLoad(data) || {};
|
|
43
43
|
|
|
44
44
|
config = deepmerge(DEFAULT_CONFIG, loadedConfig);
|
|
45
45
|
|
package/lib/render.js
CHANGED
|
@@ -21,9 +21,20 @@ const ICONS = {
|
|
|
21
21
|
cheers: "🎉",
|
|
22
22
|
ok: "✅",
|
|
23
23
|
warn: "⚠️",
|
|
24
|
-
fail: "❌"
|
|
24
|
+
fail: "❌",
|
|
25
|
+
error: "🚨"
|
|
25
26
|
};
|
|
26
27
|
|
|
28
|
+
const SUMMARY = {
|
|
29
|
+
cheers: "congratulates on the size improvement 📉:",
|
|
30
|
+
ok: "found no problematic size increases.",
|
|
31
|
+
warn: "detected a size increase 📈:",
|
|
32
|
+
fail: "detected a problematic size increase 📈:",
|
|
33
|
+
error: "had a measurement error:"
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const EXPANDED_CASES = ["cheers", "warn", "fail", "error"];
|
|
37
|
+
|
|
27
38
|
function indentEachLine(string, indent=" ") {
|
|
28
39
|
return string.replace(/^(?!\s*$)/gm, indent);
|
|
29
40
|
}
|
|
@@ -34,11 +45,15 @@ function renderAsText(deltas) {
|
|
|
34
45
|
text += ` '${deltas.before.branch}' => '${deltas.after.branch}' (sha ${deltas.after.sha})\n\n`;
|
|
35
46
|
|
|
36
47
|
for (const d of deltas) {
|
|
37
|
-
|
|
48
|
+
if (d.error) {
|
|
49
|
+
text += `+ ${ICONS.error} ${d.name}: measurement error: ${d.error.message || d.error}\n`;
|
|
50
|
+
} else {
|
|
51
|
+
text += `+ ${ICONS[d.result]} ${d.name}: ${d.increase}% (${pb(d.beforeSize)} => ${pb(d.afterSize)})\n\n`;
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
if (d.details) {
|
|
54
|
+
text += ` ${d.detailsLabel}:\n\n`;
|
|
55
|
+
text += indentEachLine(d.details.trim()) + "\n";
|
|
56
|
+
}
|
|
42
57
|
}
|
|
43
58
|
text += `\n`;
|
|
44
59
|
}
|
|
@@ -47,12 +62,20 @@ function renderAsText(deltas) {
|
|
|
47
62
|
}
|
|
48
63
|
|
|
49
64
|
function renderAsMarkdown(deltas) {
|
|
50
|
-
|
|
65
|
+
const summaryState = deltas.summary || "ok";
|
|
66
|
+
const detailState = EXPANDED_CASES.includes(summaryState) ? "open" : "";
|
|
67
|
+
|
|
68
|
+
let markdown = `<details ${detailState}><summary>${ICONS[summaryState]} <a href="https://github.com/adobe/sizewatcher">Sizewatcher</a> ${SUMMARY[summaryState]}</summary>\n`;
|
|
69
|
+
markdown += `<p><blockquote>\n\n`;
|
|
51
70
|
|
|
52
71
|
for (const d of deltas) {
|
|
53
72
|
let change;
|
|
54
73
|
|
|
55
|
-
if (d.
|
|
74
|
+
if (d.error) {
|
|
75
|
+
d.result = "error";
|
|
76
|
+
change = `measurement error: ${d.error.message || d.error}`;
|
|
77
|
+
|
|
78
|
+
} else if (d.increase >= 0.1) {
|
|
56
79
|
change = `<b>+${d.increase}%</b> (${pb(d.beforeSize)} => ${pb(d.afterSize)})`;
|
|
57
80
|
|
|
58
81
|
} else if (d.increase <= -0.1) {
|
|
@@ -63,22 +86,30 @@ function renderAsMarkdown(deltas) {
|
|
|
63
86
|
|
|
64
87
|
}
|
|
65
88
|
|
|
66
|
-
markdown += `<details>\n<summary>${ICONS[d.result]} <code>${d.name}</code> ${change}</summary>\n`;
|
|
67
89
|
if (d.details) {
|
|
68
|
-
markdown += `<
|
|
90
|
+
markdown += `<details><summary>`;
|
|
91
|
+
} else {
|
|
92
|
+
markdown += " ";
|
|
93
|
+
}
|
|
94
|
+
markdown += `${ICONS[d.result]} <code>${d.name}</code> ${change}`;
|
|
95
|
+
if (d.details) {
|
|
96
|
+
markdown += `</summary><br>${d.detailsLabel}:<pre>${d.details.trim().replace(/\n/g, "<br>")}</pre></details>`;
|
|
69
97
|
}
|
|
70
98
|
|
|
71
|
-
markdown +=
|
|
99
|
+
markdown += `\n\n`;
|
|
72
100
|
}
|
|
73
101
|
|
|
74
|
-
markdown += `<details
|
|
102
|
+
markdown += `<details><summary>Notes</summary><br>\n\n`;
|
|
75
103
|
markdown += `- PR branch: \`${deltas.after.branch}\` @ ${deltas.after.sha}\n`;
|
|
76
104
|
markdown += `- Base branch: \`${deltas.before.branch}\`\n`;
|
|
77
105
|
markdown += `- Sizewatcher v${require('../package.json').version}\n`;
|
|
78
|
-
markdown += `- Configuration
|
|
79
|
-
markdown +=
|
|
106
|
+
markdown += `- Effective Configuration:\n`;
|
|
107
|
+
markdown += `\n\`\`\`yaml\n${config.asYaml()}\`\`\`\n`;
|
|
80
108
|
markdown += `</details>\n\n`;
|
|
81
109
|
|
|
110
|
+
markdown += `</blockquote></p>\n`;
|
|
111
|
+
markdown += `</details>\n`;
|
|
112
|
+
|
|
82
113
|
return markdown;
|
|
83
114
|
}
|
|
84
115
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2020 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// main cli entry point
|
|
16
|
+
|
|
17
|
+
const debug = require("debug")("sizewatcher");
|
|
18
|
+
const gitCheckoutBeforeAndAfter = require("./checkout");
|
|
19
|
+
const compare = require("./compare");
|
|
20
|
+
const report = require("./report");
|
|
21
|
+
const Git = require("simple-git");
|
|
22
|
+
|
|
23
|
+
function printUsage() {
|
|
24
|
+
console.error(`Usage: sizewatcher [<options>] [<before> [<after>]]`);
|
|
25
|
+
console.error();
|
|
26
|
+
console.error("Arguments:");
|
|
27
|
+
console.error(" <before> Before branch/commit for comparison. Defaults to default branch or main/master.");
|
|
28
|
+
console.error(" <after> After branch/commit for comparison. Defaults to current branch.");
|
|
29
|
+
console.error();
|
|
30
|
+
console.error("Options:");
|
|
31
|
+
console.error(" -h Show help");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function sizewatcher(argv) {
|
|
35
|
+
try {
|
|
36
|
+
if (!argv) argv = [];
|
|
37
|
+
|
|
38
|
+
// yes, this should probably use a framework like oclif or yargs.
|
|
39
|
+
// but cli arguments are very limited now and so manual handling is enough
|
|
40
|
+
if (argv[0] === "-h") {
|
|
41
|
+
printUsage();
|
|
42
|
+
return process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
debug(`Running inside ${process.cwd()}`);
|
|
46
|
+
|
|
47
|
+
if (!await Git().checkIsRepo()) {
|
|
48
|
+
throw new Error(`Not inside a git checkout: ${process.cwd()}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`Checking out git branches...`);
|
|
52
|
+
const { before, after } = await gitCheckoutBeforeAndAfter(process.cwd(), argv[0], argv[1]);
|
|
53
|
+
|
|
54
|
+
if (before.branch === after.branch) {
|
|
55
|
+
console.log(`Branches are identical, nothing to compare (${before.branch}=${after.branch}). Compare selected branches using 'sizewatcher <before> <after>'.`);
|
|
56
|
+
return process.exit();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(`Comparing changes from '${before.branch}' to '${after.branch}'\n`);
|
|
60
|
+
|
|
61
|
+
const deltas = await compare(before, after);
|
|
62
|
+
|
|
63
|
+
await report(deltas);
|
|
64
|
+
|
|
65
|
+
console.log("Done. Cleaning up...");
|
|
66
|
+
|
|
67
|
+
} catch (e) {
|
|
68
|
+
debug(e);
|
|
69
|
+
console.error("Error:", e.message || e);
|
|
70
|
+
return process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = sizewatcher;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/sizewatcher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Warns if your pull requests introduce large size increases.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,17 +30,19 @@
|
|
|
30
30
|
"eslint": "^7.1.0",
|
|
31
31
|
"mocha": "^7.2.0",
|
|
32
32
|
"mock-fs": "^4.13.0",
|
|
33
|
-
"nyc": "^15.1.0"
|
|
33
|
+
"nyc": "^15.1.0",
|
|
34
|
+
"supports-color": "^8.1.1"
|
|
34
35
|
},
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"@octokit/rest": "^18.0.3",
|
|
37
38
|
"debug": "^4.1.1",
|
|
38
39
|
"deepmerge": "^4.2.2",
|
|
39
40
|
"du": "^1.0.0",
|
|
41
|
+
"glob": "^7.1.6",
|
|
40
42
|
"js-yaml": "^3.14.0",
|
|
41
43
|
"pretty-bytes": "^5.3.0",
|
|
42
44
|
"require-dir": "^1.2.0",
|
|
43
|
-
"simple-git": "^
|
|
45
|
+
"simple-git": "^3.7.1",
|
|
44
46
|
"tmp": "^0.2.1",
|
|
45
47
|
"xbytes": "^1.6.2"
|
|
46
48
|
}
|