├── .gitignore
├── History.md
├── LICENSE
├── Readme.md
├── go.mod
├── go.sum
├── internal
└── prune
│ └── prune.go
└── main.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .envrc
2 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 |
2 | v1.2.0 / 2020-05-12
3 | ===================
4 |
5 | * add `--include` and `--exclude` flags
6 | * remove goreleaser.yml
7 |
8 | v1.1.0 / 2020-02-10
9 | ===================
10 |
11 | * move `./cmd/node-prune/main.go` to root
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | (The MIT License)
2 |
3 | Copyright (c) 2017 TJ Holowaychuk tj@tjholowaychuk.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | 'Software'), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
24 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## What?
4 |
5 | node-prune is a small tool to prune unnecessary files from ./node_modules, such as markdown, typescript source files, and so on. Primarily built for [Up](https://github.com/apex/up) which lets you deploy serverless web applications in seconds.
6 |
7 | ## Installation
8 |
9 | From [gobinaries.com](https://gobinaries.com):
10 |
11 | ```sh
12 | $ curl -sf https://gobinaries.com/tj/node-prune | sh
13 | ```
14 |
15 | From source:
16 |
17 | ```
18 | $ go get github.com/tj/node-prune
19 | ```
20 |
21 | ## Usage
22 |
23 | In your app directory:
24 |
25 | ```
26 | $ node-prune
27 |
28 | files total 27,330
29 | files removed 3,990
30 | size removed 13 MB
31 | duration 200ms
32 | ```
33 |
34 | Somewhere else:
35 |
36 | ```
37 | $ node-prune path/to/node_modules
38 |
39 | files total 27,330
40 | files removed 3,990
41 | size removed 13 MB
42 | duration 200ms
43 | ```
44 |
45 | Or add to the ``package.json`` scripts field
46 |
47 | ```
48 | "scripts": {
49 | "postinstall": "node-prune"
50 | }
51 | ```
52 |
53 | ## Why?
54 |
55 | 
56 |
57 | ---
58 |
59 | [](https://godoc.org/github.com/tj/node-prune)
60 | 
61 | 
62 |
63 |
64 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/tj/node-prune
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/apex/log v1.1.1
7 | github.com/dustin/go-humanize v1.0.0
8 | )
9 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/apex/log v1.1.1 h1:BwhRZ0qbjYtTob0I+2M+smavV0kOC8XgcnGZcyL9liA=
2 | github.com/apex/log v1.1.1/go.mod h1:Ls949n1HFtXfbDcjiTTFQqkVUrte0puoIBfO3SVgwOA=
3 | github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
4 | github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys=
5 | github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
6 | github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
10 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
11 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
12 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
13 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
14 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
15 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
16 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
17 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
18 | github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
19 | github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
20 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
21 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
22 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
23 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
24 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
25 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
26 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
27 | github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
28 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
29 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
30 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
31 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
32 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
34 | github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
35 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
36 | github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
37 | github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
38 | github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
39 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
40 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
41 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
42 | github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
43 | github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
44 | github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
45 | github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
46 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
47 | golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
48 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
49 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
50 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
51 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
52 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
53 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
54 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
55 | golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
56 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
57 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
59 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
60 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
61 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
62 |
--------------------------------------------------------------------------------
/internal/prune/prune.go:
--------------------------------------------------------------------------------
1 | // Package prune provides node_modules pruning of unnecessary files.
2 | package prune
3 |
4 | import (
5 | "os"
6 | "path/filepath"
7 | "runtime"
8 | "sync"
9 | "sync/atomic"
10 |
11 | "github.com/apex/log"
12 | )
13 |
14 | // DefaultFiles pruned.
15 | //
16 | // Copied from yarn (mostly).
17 | var DefaultFiles = []string{
18 | "Jenkinsfile",
19 | "Makefile",
20 | "Gulpfile.js",
21 | "Gruntfile.js",
22 | "gulpfile.js",
23 | ".DS_Store",
24 | ".tern-project",
25 | ".gitattributes",
26 | ".editorconfig",
27 | ".eslintrc",
28 | "eslint",
29 | ".eslintrc.js",
30 | ".eslintrc.json",
31 | ".eslintrc.yml",
32 | ".eslintignore",
33 | ".stylelintrc",
34 | "stylelint.config.js",
35 | ".stylelintrc.json",
36 | ".stylelintrc.yaml",
37 | ".stylelintrc.yml",
38 | ".stylelintrc.js",
39 | ".htmllintrc",
40 | "htmllint.js",
41 | ".lint",
42 | ".npmrc",
43 | ".npmignore",
44 | ".jshintrc",
45 | ".flowconfig",
46 | ".documentup.json",
47 | ".yarn-metadata.json",
48 | ".travis.yml",
49 | "appveyor.yml",
50 | ".gitlab-ci.yml",
51 | "circle.yml",
52 | ".coveralls.yml",
53 | "CHANGES",
54 | "changelog",
55 | "LICENSE.txt",
56 | "LICENSE",
57 | "LICENSE-MIT",
58 | "LICENSE.BSD",
59 | "license",
60 | "LICENCE.txt",
61 | "LICENCE",
62 | "LICENCE-MIT",
63 | "LICENCE.BSD",
64 | "licence",
65 | "AUTHORS",
66 | "CONTRIBUTORS",
67 | ".yarn-integrity",
68 | ".yarnclean",
69 | "_config.yml",
70 | ".babelrc",
71 | ".yo-rc.json",
72 | "jest.config.js",
73 | "karma.conf.js",
74 | "wallaby.js",
75 | "wallaby.conf.js",
76 | ".prettierrc",
77 | ".prettierrc.yml",
78 | ".prettierrc.toml",
79 | ".prettierrc.js",
80 | ".prettierrc.json",
81 | "prettier.config.js",
82 | ".appveyor.yml",
83 | "tsconfig.json",
84 | "tslint.json",
85 | }
86 |
87 | // DefaultDirectories pruned.
88 | //
89 | // Copied from yarn (mostly).
90 | var DefaultDirectories = []string{
91 | "__tests__",
92 | "test",
93 | "tests",
94 | "powered-test",
95 | "docs",
96 | "doc",
97 | ".idea",
98 | ".vscode",
99 | "website",
100 | "images",
101 | "assets",
102 | "example",
103 | "examples",
104 | "coverage",
105 | ".nyc_output",
106 | ".circleci",
107 | ".github",
108 | }
109 |
110 | // DefaultExtensions pruned.
111 | var DefaultExtensions = []string{
112 | ".markdown",
113 | ".md",
114 | ".mkd",
115 | ".ts",
116 | ".jst",
117 | ".coffee",
118 | ".tgz",
119 | ".swp",
120 | }
121 |
122 | // Stats for a prune.
123 | type Stats struct {
124 | FilesTotal int64
125 | FilesRemoved int64
126 | SizeRemoved int64
127 | }
128 |
129 | // Pruner is a module pruner.
130 | type Pruner struct {
131 | dir string
132 | log log.Interface
133 | dirs map[string]struct{}
134 | exts map[string]struct{}
135 | excepts []string
136 | globs []string
137 | files map[string]struct{}
138 | ch chan func()
139 | wg sync.WaitGroup
140 | }
141 |
142 | // Option function.
143 | type Option func(*Pruner)
144 |
145 | // New with the given options.
146 | func New(options ...Option) *Pruner {
147 | v := &Pruner{
148 | dir: "node_modules",
149 | log: log.Log,
150 | exts: toMap(DefaultExtensions),
151 | excepts: []string{},
152 | globs: []string{},
153 | dirs: toMap(DefaultDirectories),
154 | files: toMap(DefaultFiles),
155 | ch: make(chan func()),
156 | }
157 |
158 | for _, o := range options {
159 | o(v)
160 | }
161 |
162 | return v
163 | }
164 |
165 | // WithDir option.
166 | func WithDir(s string) Option {
167 | return func(v *Pruner) {
168 | v.dir = s
169 | }
170 | }
171 |
172 | // WithGlobs option.
173 | func WithGlobs(s []string) Option {
174 | return func(v *Pruner) {
175 | v.globs = s
176 | }
177 | }
178 |
179 | // WithExceptions option.
180 | func WithExceptions(s []string) Option {
181 | return func(v *Pruner) {
182 | v.excepts = s
183 | }
184 | }
185 |
186 | // WithExtensions option.
187 | func WithExtensions(s []string) Option {
188 | return func(v *Pruner) {
189 | v.exts = toMap(s)
190 | }
191 | }
192 |
193 | // WithDirectories option.
194 | func WithDirectories(s []string) Option {
195 | return func(v *Pruner) {
196 | v.dirs = toMap(s)
197 | }
198 | }
199 |
200 | // WithFiles option.
201 | func WithFiles(s []string) Option {
202 | return func(v *Pruner) {
203 | v.files = toMap(s)
204 | }
205 | }
206 |
207 | // Prune performs the pruning.
208 | func (p *Pruner) Prune() (*Stats, error) {
209 | var stats Stats
210 |
211 | p.startN(runtime.NumCPU())
212 | defer p.stop()
213 |
214 | err := filepath.Walk(p.dir, func(path string, info os.FileInfo, err error) error {
215 | if err != nil {
216 | return err
217 | }
218 |
219 | stats.FilesTotal++
220 |
221 | ctx := p.log.WithFields(log.Fields{
222 | "path": path,
223 | "size": info.Size(),
224 | "dir": info.IsDir(),
225 | })
226 |
227 | // keep
228 | if !p.prune(path, info) {
229 | ctx.Debug("keep")
230 | return nil
231 | }
232 |
233 | // prune
234 | ctx.Info("prune")
235 | atomic.AddInt64(&stats.FilesRemoved, 1)
236 | atomic.AddInt64(&stats.SizeRemoved, info.Size())
237 |
238 | // remove and skip dir
239 | if info.IsDir() {
240 | p.ch <- func() {
241 | s, _ := dirStats(path)
242 |
243 | atomic.AddInt64(&stats.FilesTotal, s.FilesTotal)
244 | atomic.AddInt64(&stats.FilesRemoved, s.FilesRemoved)
245 | atomic.AddInt64(&stats.SizeRemoved, s.SizeRemoved)
246 |
247 | if err := os.RemoveAll(path); err != nil {
248 | ctx.WithError(err).Error("removing directory")
249 | }
250 | }
251 | return filepath.SkipDir
252 | }
253 |
254 | // remove file
255 | p.ch <- func() {
256 | if err := os.Remove(path); err != nil {
257 | ctx.WithError(err).Error("removing file")
258 | }
259 | }
260 |
261 | return nil
262 | })
263 |
264 | return &stats, err
265 | }
266 |
267 | // prune returns true if the file or dir should be pruned.
268 | func (p *Pruner) prune(path string, info os.FileInfo) bool {
269 | // exceptions
270 | for _, glob := range p.excepts {
271 | matched, _ := filepath.Match(glob, info.Name())
272 | if matched {
273 | return false
274 | }
275 | }
276 |
277 | // globs
278 | for _, glob := range p.globs {
279 | matched, _ := filepath.Match(glob, info.Name())
280 | if matched {
281 | return true
282 | }
283 | }
284 |
285 | // directories
286 | if info.IsDir() {
287 | _, ok := p.dirs[info.Name()]
288 | return ok
289 | }
290 |
291 | // files
292 | if _, ok := p.files[info.Name()]; ok {
293 | return true
294 | }
295 |
296 | // files exact match
297 | if _, ok := p.files[path]; ok {
298 | return true
299 | }
300 |
301 | // extensions
302 | ext := filepath.Ext(path)
303 | _, ok := p.exts[ext]
304 | return ok
305 | }
306 |
307 | // startN starts n loops.
308 | func (p *Pruner) startN(n int) {
309 | for i := 0; i < n; i++ {
310 | p.wg.Add(1)
311 | go p.start()
312 | }
313 | }
314 |
315 | // start loop.
316 | func (p *Pruner) start() {
317 | defer p.wg.Done()
318 | for fn := range p.ch {
319 | fn()
320 | }
321 | }
322 |
323 | // stop loop.
324 | func (p *Pruner) stop() {
325 | close(p.ch)
326 | p.wg.Wait()
327 | }
328 |
329 | // dirStats returns stats for files in dir.
330 | func dirStats(dir string) (*Stats, error) {
331 | var stats Stats
332 |
333 | err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
334 | stats.FilesTotal++
335 | stats.FilesRemoved++
336 | stats.SizeRemoved += info.Size()
337 | return err
338 | })
339 |
340 | return &stats, err
341 | }
342 |
343 | // toMap returns a map from slice.
344 | func toMap(s []string) map[string]struct{} {
345 | m := make(map[string]struct{})
346 | for _, v := range s {
347 | m[v] = struct{}{}
348 | }
349 | return m
350 | }
351 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "strings"
7 | "time"
8 |
9 | "github.com/apex/log"
10 | "github.com/apex/log/handlers/cli"
11 | "github.com/dustin/go-humanize"
12 |
13 | "github.com/tj/node-prune/internal/prune"
14 | )
15 |
16 | func init() {
17 | log.SetHandler(cli.Default)
18 | log.SetLevel(log.WarnLevel)
19 | }
20 |
21 | type arrayFlags []string
22 |
23 | func (i *arrayFlags) String() string {
24 | return strings.Join(*i, ", ")
25 | }
26 |
27 | func (i *arrayFlags) Set(value string) error {
28 | *i = append(*i, value)
29 | return nil
30 | }
31 |
32 | // Globs of files that should not be pruned
33 | var exclusionGlobs arrayFlags
34 |
35 | // Globs of files that should always be pruned in addition to the defaults
36 | var inclusionGlobs arrayFlags
37 |
38 | func main() {
39 | debug := flag.Bool("verbose", false, "Verbose log output.")
40 | flag.Var(&exclusionGlobs, "exclude", "Glob of files that should not be pruned. Can be specified multiple times.")
41 | flag.Var(&inclusionGlobs, "include", "Globs of files that should always be pruned in addition to the defaults. Can be specified multiple times.")
42 | flag.Parse()
43 | dir := flag.Arg(0)
44 |
45 | start := time.Now()
46 |
47 | if *debug {
48 | log.SetLevel(log.DebugLevel)
49 | }
50 |
51 | var options []prune.Option
52 |
53 | if dir != "" {
54 | options = append(options, prune.WithDir(dir))
55 | }
56 |
57 | if len(exclusionGlobs) > 0 {
58 | options = append(options, prune.WithExceptions(exclusionGlobs))
59 | }
60 |
61 | if len(inclusionGlobs) > 0 {
62 | options = append(options, prune.WithGlobs(inclusionGlobs))
63 | }
64 |
65 | p := prune.New(options...)
66 |
67 | stats, err := p.Prune()
68 | if err != nil {
69 | log.Fatalf("error: %s", err)
70 | }
71 |
72 | println()
73 | defer println()
74 |
75 | output("files total", humanize.Comma(stats.FilesTotal))
76 | output("files removed", humanize.Comma(stats.FilesRemoved))
77 | output("size removed", humanize.Bytes(uint64(stats.SizeRemoved)))
78 | output("duration", time.Since(start).Round(time.Millisecond).String())
79 | }
80 |
81 | func output(name, val string) {
82 | fmt.Printf("\x1b[1m%20s\x1b[0m %s\n", name, val)
83 | }
84 |
--------------------------------------------------------------------------------