poof 2.2.0 → 3.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Hiroki Osame <hiroki.osame@gmail.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,310 +1,253 @@
1
- # Poof
2
-
3
- > Simple data processing with decorators
4
-
5
- [![Build Status](https://travis-ci.org/fatfisz/poof.svg?branch=master)](https://travis-ci.org/fatfisz/poof)
6
- [![Dependency Status](https://david-dm.org/fatfisz/poof.svg?path=packages/poof)](https://david-dm.org/fatfisz/poof?path=packages/poof)
7
- [![devDependency Status](https://david-dm.org/fatfisz/poof/dev-status.svg?path=packages/poof)](https://david-dm.org/fatfisz/poof?path=packages/poof#info=devDependencies)
8
-
9
- Poof is a tool for creating data processing functions in a declarative way by utilising an upcoming JS feature - decorators.
10
-
11
- It makes use of the awesome [validator](https://www.npmjs.com/package/validator) lib to provide assertion functions. Without it Poof probably wouldn't exist.
12
-
13
- ## Contents
14
-
15
- - [Getting started](#getting-started)
16
- - [Why use Poof?](#why-use-poof)
17
- - [Example](#example)
18
- - [Example explained](#example-explained)
19
- - [The difference between poof and poof-cast](#the-difference-between-poof-and-poof-cast)
20
- - [API](#api)
21
- - [createProcessor(config)](#createprocessorconfig)
22
- - [decorators](#decorators)
23
- - [decorators.assert.method and decorators.assert.not.method](#decoratorsassertmethod-and-decoratorsassertnotmethod)
24
- - [decorators.assert.hasType and decorators.assert.not.hasType](#decoratorsasserthastype-and-decoratorsassertnothastype)
25
- - [decorators.assert.isInstanceOf and decorators.assert.not.isInstanceOf](#decoratorsassertisinstanceof-and-decoratorsassertnotisinstanceof)
26
- - [decorators.assign](#decoratorsassign)
27
- - [decorators.assignTo(key)](#decoratorsassigntokey)
28
- - [decorators.filter(predicate)](#decoratorsfilterpredicate)
29
- - [decorators.from(key)](#decoratorsfromkey)
30
- - [decorators.ignoreIf(predicate)](#decoratorsignoreifpredicate)
31
- - [decorators.ignoreIfUndefined](#decoratorsignoreifundefined)
32
- - [decorators.map(mapper)](#decoratorsmapmapper)
33
- - [decorators.set(value)](#decoratorssetvalue)
34
- - [decorators.transform(transformer)](#decoratorstransformtransformer)
35
- - [Some questions you might have](#some-questions-you-might-have)
36
- - [How can I use decorators in my code?](#how-can-i-use-decorators-in-my-code)
37
- - [Why the explicit assignment decorator?](#why-the-explicit-assignment-decorator)
38
- - [What about nested structures?](#what-about-nested-structures)
39
- - [Why is the field error exception a separate package?](#why-is-the-field-error-exception-a-separate-package)
40
- - [Be careful with decorators!](#be-careful-with-decorators)
41
- - [Contributing](#contributing)
42
- - [License](#license)
43
-
44
- ## Getting started
45
-
46
- Install the `poof` package with this command:
47
- ```shell
48
- npm install poof field-validation-error --save
49
- ```
50
- and/or install the `poof-cast` package with this command:
51
- ```shell
52
- npm install poof-cast field-validation-error -save
53
- ```
54
-
55
- `field-validation-error` is a peer dependency of both `poof` packages.
56
-
57
- ### ES6 Data Structures
58
-
59
- Poof makes use of ES6 data structures, e.g. `WeakMap`, but doesn't include any polyfill.
60
- Make sure you add a polyfill yourself if you want to support older browsers.
1
+ <h2 align="center">
2
+ <img width="240" src=".github/logo.webp">
3
+ <br><br>
4
+ <a href="https://npm.im/poof"><img src="https://badgen.net/npm/v/poof"></a> <a href="https://npm.im/poof"><img src="https://badgen.net/npm/dm/poof"></a> <a href="https://packagephobia.now.sh/result?p=poof"><img src="https://packagephobia.now.sh/badge?p=poof"></a>
5
+ </h2>
61
6
 
62
- ## Why use Poof?
7
+ Ever `rm -rf`'d a large directory and sat there waiting for it to finish? With `poof` you no longer need to wait:
63
8
 
64
- - it lets you describe data processing in a straightforward, declarative way
65
- - it is especially useful for isomorphic websites - you can declare data validator once and use it both on the client-side and the on the server-side
66
- - Poof processors are composable - you can pass the result from one processor to another, e.g. when you want to separate validation and transforming
9
+ ```sh
10
+ $ poof ./large-file ./large-directory "**/globs"
11
+ ```
67
12
 
68
- ## Example
13
+ ### What's "poof"?
14
+ `poof` is a fast, non-blocking CLI alternative to `rm -rf`, designed for deleting large files and directories without waiting for cleanup to complete.
69
15
 
70
- ```js
71
- import FieldValidationError from 'field-validation-error';
72
- import { createProcessor, decorators } from 'poof-cast';
16
+ It works by quickly moving files and directories out of the way, then deleting them in the background.
17
+ This means large deletions finish instantly from your perspective, even when there's a lot of data to clean up.
73
18
 
74
- const processIdAndIndex = createProcessor({
75
- @decorators.from('postId')
76
- @decorators.assert.not.isNull('Missing post id')
77
- @decorators.assert.isMongoId('Invalid id')
78
- @decorators.transform(ObjectID)
79
- @decorators.assign
80
- _id() {},
19
+ On a large filesystem (4.7 GB, 190k files), `poof` returns control in ~0.6s while `rm -rf` takes ~28s and `rimraf` ~12s. Full benchmarks below.
81
20
 
82
- @decorators.assert.not.isNull('Missing index')
83
- @decorators.assert.isInt('Invalid index', { min: 0 })
84
- @decorators.transform(Number)
85
- @decorators.assign
86
- index() {},
87
- });
21
+ ### Features
22
+ * ⚡ **Immediate return**: deletion doesn't block your shell
23
+ * 🗂 **Move-then-delete**: fast, atomic rename before cleanup
24
+ * 🛡 **Built-in safeguards**: root protection and directory scoping
25
+ * 🖥 **Cross-platform**: macOS, Linux, and Windows
88
26
 
89
- try {
90
- const result = processIdAndIndex(request.body);
27
+ ## Install
91
28
 
92
- // Do something with the result...
93
- } catch(error) {
94
- if (error instanceof FieldValidationError) {
95
- // Do something with error messages contained in `error.fields`
96
- }
97
- }
29
+ ```sh
30
+ npm install -g poof
98
31
  ```
99
32
 
100
- ## Example explained
101
-
102
- Poof library has two versions: [`poof`, and `poof-cast`](#the-difference-between-poof-and-poof-cast). Both of them have two exports: the [`createProcessor` function](#createprocessorconfig) and the [`decorators` object](#decorators).
103
-
104
- ```js
105
- // First import the FieldValidationError and also tools from Poof. The
106
- // `poof-cast` version additionaly casts data to String for assertions. More
107
- // about it later.
108
- import FieldValidationError from 'field-validation-error';
109
- import { createProcessor, decorators } from 'poof-cast';
33
+ ## CLI Usage
110
34
 
111
- // The `createProcessor` function returns a processor based on the passed
112
- // config object.
113
- const processIdAndIndex = createProcessor({
35
+ ```sh
36
+ # Delete files or directories
37
+ $ poof node_modules dist
114
38
 
115
- // This decorator tells to pick the data from the `postId` property of
116
- // a passed object.
117
- @decorators.from('postId')
39
+ # Use glob patterns
40
+ $ poof "*.log" "temp-*"
118
41
 
119
- // This decorator checks if the value is empty; if not, the error for this
120
- // field is set to 'Missing post id' and this field's processing stops here.
121
- @decorators.assert.not.isNull('Missing post id')
42
+ # Recursive match with ** (searches all subdirectories)
43
+ $ poof "**/node_modules" "**/dist"
122
44
 
123
- // Similarly, check if the value is a MongoDB id.
124
- @decorators.assert.isMongoId('Invalid id')
125
-
126
- // This decorator takes a function and uses it to transforms the value.
127
- // In this case the ObjectID constructor is used.
128
- @decorators.transform(ObjectID)
129
-
130
- // If you want to have the result of the processing contained in the result
131
- // object, you have to do it explicitly with the `assign` decorator.
132
- @decorators.assign
133
-
134
- // This defines the output property which will have the processed value.
135
- _id() {},
45
+ # Verbose output
46
+ $ poof --verbose ./large-directory
47
+ ```
136
48
 
49
+ ### Options
137
50
 
138
- // Assert that the value is not empty.
139
- @decorators.assert.not.isNull('Missing index')
51
+ | Flag | Alias | Description |
52
+ | ------------- | ----- | ---------------------------------------------- |
53
+ | `--dry` | `-d` | Preview files without deleting |
54
+ | `--verbose` | `-v` | Log each file as it's deleted |
55
+ | `--dangerous` | | Allow deleting paths outside current directory |
56
+ | `--version` | | Show version |
57
+ | `--help` | | Show help |
140
58
 
141
- // Assert that the value is an integer, with a value of at least 0.
142
- @decorators.assert.isInt('Invalid index', { min: 0 })
59
+ ## File matching
143
60
 
144
- // Cast to Number
145
- @decorators.transform(Number)
61
+ `poof` accepts explicit paths or [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29) for flexible file matching.
146
62
 
147
- // Pass to the output
148
- @decorators.assign
63
+ > [!TIP]
64
+ > When using glob patterns, start with `--dry` to preview what will match before deleting.
149
65
 
150
- // The result will land in the `index` property.
151
- // Note that here we initially get the value from the `index` property, so
152
- // the `from` decorator is not needed.
153
- index() {},
154
- });
66
+ ### Quick refresher: shell quoting
155
67
 
68
+ How unquoted glob patterns are expanded depends on your shell's settings (`dotglob`, `globstar`, etc.).
69
+ To get consistent behavior, quote the pattern so `poof` handles the matching itself.
156
70
 
157
- try {
158
- // Now the processor can be used with an object.
159
- const result = processIdAndIndex(request.body);
71
+ ```sh
72
+ # The shell expands the glob before poof runs
73
+ $ poof **/node_modules
160
74
 
161
- // Do something with the result...
162
- } catch(error) {
163
- if (error instanceof FieldValidationError) {
164
- // Do something with error messages contained in `error.fields`
165
- }
166
- }
75
+ # Recommended: poof expands the glob
76
+ $ poof "**/node_modules"
167
77
  ```
168
78
 
169
- So if `request.body` is:
170
- ```js
171
- {
172
- postId: '507f1f77bcf86cd799439011',
173
- index: '12',
174
- }
175
- ```
176
- then `result` would be:
177
- ```js
178
- {
179
- _id: ObjectID('507f1f77bcf86cd799439011'),
180
- index: 12,
181
- }
182
- ```
79
+ ### Basic patterns
183
80
 
184
- If instead `request.body` is:
185
- ```js
186
- {
187
- index: '-1',
188
- }
189
- ```
190
- then you'd get a `FieldValidationError` with the `fields` property equal to:
191
- ```js
192
- {
193
- _id: 'Missing post id',
194
- index: 'Invalid index',
195
- }
196
- ```
81
+ ```sh
82
+ # Explicit paths
83
+ $ poof node_modules
197
84
 
198
- ## The difference between poof and poof-cast
85
+ # Multiple paths
86
+ $ poof dist coverage
199
87
 
200
- While `poof` passes processed data for asserts without change, `poof-cast` casts it to string beforehand. The casting works the same as default JS string casting, with this exception: `null`, `undefined`, and `NaN` become `''` (empty string).
88
+ # Wildcards in current directory
89
+ $ poof "*.log"
90
+ $ poof "temp-*"
91
+ ```
201
92
 
202
- This is quite useful for parsing request bodies which usually consist of strings - missing fields assume the value of `''` when auto-casting is on. Because of this `decorators.assert.isNull` from `poof-cast` can be used to check both for field presence and whether the value is a non-empty string. This of course reduces otherwise necessary boilerplate code.
93
+ ### Recursive patterns
203
94
 
204
- The [validator](https://www.npmjs.com/package/validator) library used to make the casting by itself before v. 5. Now it instead throws when the passed argument is not a string. Those exceptions can be thrown when using `poof` (without casting), so be careful when using that version.
95
+ Use `**` to match across nested directories:
205
96
 
206
- ## API
97
+ ```sh
98
+ # All node_modules in a monorepo
99
+ $ poof "**/node_modules"
207
100
 
208
- Both `poof` and `poof-cast` export:
101
+ # All .log files recursively
102
+ $ poof "**/*.log"
209
103
 
210
- ### createProcessor(config)
104
+ # Multiple patterns
105
+ $ poof "**/dist" "**/coverage" "**/*.tmp"
106
+ ```
211
107
 
212
- Use this function with a config object to get a simple data processor.
108
+ ### Dotfiles (hidden files)
213
109
 
214
- The processor either returns an output object or throws `FieldValidationError` if there was a validation error.
110
+ By default, glob wildcards (`*`, `**`) don't match dotfiles (files starting with `.`). This helps avoid accidentally deleting `.git`, `.env`, or other hidden files.
215
111
 
216
- ### decorators
112
+ To target dotfiles, explicitly include the `.` in your pattern:
217
113
 
218
- #### decorators.assert.method and decorators.assert.not.method
114
+ ```sh
115
+ # Delete all dotfiles in current directory
116
+ $ poof ".*"
219
117
 
220
- Those contain all validation functions from the validator lib: `contains`, `equals`, `isAfter`, `isAlpha`, `isAlphanumeric`, `isAscii`, `isBase64`, `isBefore`, `isBoolean`, `isByteLength`, `isCreditCard`, `isCurrency`, `isDataURI`, `isDate`, `isDecimal`, `isDivisibleBy`, `isEmail`, `isFloat`, `isFQDN`, `isFullWidth`, `isHalfWidth`, `isHexadecimal`, `isHexColor`, `isIn`, `isInt`, `isIP`, `isISBN`, `isISIN`, `isISO8601`, `isJSON`, `isLength`, `isLowercase`, `isMACAddress`, `isMobilePhone`, `isMongoId`, `isMultibyte`, `isNull`, `isNumeric`, `isSurrogatePair`, `isUppercase`, `isURL`, `isUUID`, `isVariableWidth`, `isWhitelisted`, and `matches`.
118
+ # Delete .cache directories (not inside hidden dirs)
119
+ $ poof "**/.cache"
221
120
 
222
- *The validator methods will be updated if necessary, but the new ones should be auto-detected in most cases.*
121
+ # Delete a specific dotfile
122
+ $ poof .env.local
123
+ ```
223
124
 
224
- The first argument is always a message that can be found in the thrown exception in case the validation failed.
125
+ #### Searching inside hidden directories
225
126
 
226
- That is followed by other arguments, which are passed to the validation function as the second, third, and so on arguments. The first argument passed is the currently processed value.
127
+ `**/.cache` finds `.cache` directories in regular directories, but does **not** scan inside other hidden directories like `.git`. This is intentional—scanning hidden directories is slow and rarely useful.
227
128
 
228
- So for example for `@decorators.assert.equals('Is different', 'expected')` the arguments passed to the `equals` validator will be the current value and `'expected'`, and if the currently processed value is different from `'expected'`, the error message for this field will be set to `'Is different'`.
129
+ To search inside hidden directories, start your pattern with `.*`:
229
130
 
230
- `decorators.assert.not` works almost the same, only the validation fails if the function returns `true`.
131
+ ```sh
132
+ # Find .cache inside any hidden directory
133
+ $ poof ".*/**/.cache"
134
+ ```
231
135
 
232
- #### decorators.assert.hasType and decorators.assert.not.hasType
136
+ Or use brace expansion to target specific directories:
233
137
 
234
- An additional assertion function that performs the `typeof` check on the current value and the passed argument.
138
+ ```sh
139
+ # Search inside .config and src
140
+ $ poof "{.config,src}/**/.cache"
141
+ ```
235
142
 
236
- #### decorators.assert.isInstanceOf and decorators.assert.not.isInstanceOf
143
+ ### Negation patterns
237
144
 
238
- An additional assertion function that performs the `instanceof` check on the current value and the passed argument.
145
+ Extglob negations like `!(pattern)` match everything *except* the specified pattern:
239
146
 
240
- #### decorators.assign
147
+ ```sh
148
+ # Delete everything except .gitkeep
149
+ $ poof "!(.gitkeep)"
150
+ ```
241
151
 
242
- Used to assign the processed value to the output object. When using, it's best to put this decorator last, because any further processing results won't be saved.
152
+ > [!WARNING]
153
+ > Negations match dotfiles. `!(important.txt)` will match `.env`, `.git`, and other hidden files. Always use `--dry` first.
243
154
 
244
- #### decorators.assignTo(key)
155
+ ## Safety
245
156
 
246
- Just like `decorators.assign`, but assigns to a property named after the `key` argument. Use sparingly, only when the output property name needs to be computed.
157
+ `poof` includes guards to help prevent common accidents:
247
158
 
248
- #### decorators.filter(predicate)
159
+ - **Root protection**: refuses to delete the filesystem root
160
+ - **Directory scoping**: won't delete paths outside `cwd` unless `--dangerous` is passed
161
+ - **Typo protection**: explicit paths that don't exist are reported as errors, so `poof ndoe_modoules` won't silently succeed
162
+ - **Script-friendly globs**: glob patterns with no matches exit silently, so `poof "*.log"` won't break your scripts
249
163
 
250
- Filters the current value using `predicate`. The current value is assumed to be an array.
164
+ ## How it works
251
165
 
252
- #### decorators.from(key)
166
+ Traditional `rm -rf` blocks until every file is unlinked.
167
+ For large directories, this means waiting on thousands of filesystem operations.
253
168
 
254
- The initial value for the processed field is taken from the input object using its key. If you want to use a value of a different field instead, use this decorator.
169
+ `poof` uses a different strategy:
255
170
 
256
- #### decorators.ignoreIf(predicate)
171
+ 1. Resolve glob patterns and validate paths
172
+ 2. Spawn a detached cleanup process
173
+ 3. Rename files to a temp directory (constant-time, atomic)
174
+ 4. Stream renamed paths to the cleanup process as renames complete
175
+ 5. Exit process to return your shell while cleanup process continues in the background
257
176
 
258
- The predicate is called with the processed value. If the result is `true`, then the processing is stopped for that field and it is omitted from the output object.
177
+ ### Cross-device fallback
259
178
 
260
- #### decorators.ignoreIfUndefined
179
+ If the target is on a different filesystem (`EXDEV`), `poof` falls back to renaming in place with a hidden prefix (e.g., `.poof-uuid-large-directory`) and streams it directly to the cleaner.
261
180
 
262
- Sometimes you might want to process some data only if it's present. Use this decorator to avoid validation and transforming in case the processed value is `undefined`. It's equivalent to `decorators.ignoreIf((value) => typeof value === 'undefined')`.
181
+ ## Benchmarks
263
182
 
264
- #### decorators.map(mapper)
183
+ Deleting `**/node_modules` directories from a synthetic fixture:
265
184
 
266
- Maps the current value using `mapper`. The current value is assumed to be an array.
185
+ | Tool | Time | vs poof |
186
+ | -------- | ------- | ---------- |
187
+ | `poof` | 0.59s | — |
188
+ | `rimraf` | 12.32s | 21x slower |
189
+ | `rm -rf` | 27.82s | 48x slower |
267
190
 
268
- #### decorators.set(value)
191
+ Fixture: 4.7 GB, 190k files
269
192
 
270
- Simply sets the processed value to the one passed in the argument.
193
+ Environment: macOS 15.2, Apple M2 Max, SSD
271
194
 
272
- #### decorators.transform(transformer)
195
+ > [!NOTE]
196
+ > These benchmarks measure how long it takes to return control of your terminal, not actual deletion time. Background deletion continues after `poof` exits. `rm -rf` and `rimraf` measure actual deletion time.
273
197
 
274
- Takes transformer, applies it to the processed value, and sets it as a new processed value. Useful for type casting or otherwise transforming the validated data.
198
+ ## JS API
275
199
 
276
- ## Some questions you might have
200
+ `poof` can also be used programmatically:
277
201
 
278
- ### How can I use decorators in my code?
202
+ ```ts
203
+ import poof from 'poof'
279
204
 
280
- You can use Babel 5 with an optional "es7.decorators" transformer, or use Babel 6 with [this legacy plugin](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy). There are some differences though, you can read about them [here](https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy/blob/master/README.md).
205
+ await poof('./large-directory')
206
+ await poof(['**/dist', 'coverage'])
281
207
 
282
- ### Why the explicit assignment decorator?
208
+ const { deleted, errors } = await poof('./large-directory', { dry: true })
209
+ ```
283
210
 
284
- When I started creating what later became Poof I didn't always want to have all of the fields in the output object, and so I decided for the explicit assignment. Maybe in the future I'll add some options to enable auto-assignment for all fields in the object though.
211
+ ### Types
285
212
 
286
- ### What about nested structures?
213
+ ```ts
214
+ type Options = {
215
+ cwd?: string
216
+ dry?: boolean
217
+ dangerous?: boolean
218
+ }
287
219
 
288
- This was supposed to be a simple lib, and I didn't have a need to support nested structures with it. I might do it someday and I'm open to suggestions/PRs.
220
+ type Failure = {
221
+ path: string
222
+ error: Error
223
+ }
289
224
 
290
- ### Why is the field error exception a separate package?
225
+ type Result = {
226
+ deleted: string[]
227
+ errors: Failure[]
228
+ }
229
+ ```
291
230
 
292
- It makes sense to decouple the exception from the `poof` package because another package that handles this exception shouldn't be tied to `poof`. This problem is not contrived, it's an actual problem I had.
231
+ ## Alternatives
293
232
 
294
- ## Be careful with decorators!
233
+ Some tools provide fast, non-blocking removal by moving files to the system trash:
295
234
 
296
- The decorators feature is still at stage 1, a proposal.
297
- This means that the [current decorator spec](https://github.com/wycats/javascript-decorators) can change dramatically before it advances to the next stage.
235
+ - [trash](https://formulae.brew.sh/formula/trash) (macOS)
236
+ - [trash-cli](https://github.com/sindresorhus/trash-cli) (cross-platform)
298
237
 
299
- With this said, no changes should affect your use of this library much. You will be mostly using the exterior (decorator application) which now has a final form more or less, while the interior (how the decorating works in Poof) will be kept up-to-date with the spec.
238
+ These are useful for recoverable deletes, but large directories can accumulate in the trash and consume disk space.
300
239
 
301
- While some things may be set in stone, there is still a room for improvement. So if you care about decorators, please can take part in discussions at [wycats/javascript-decorators](https://github.com/wycats/javascript-decorators) and [jeffmo/es-class-fields-and-static-properties](https://github.com/jeffmo/es-class-fields-and-static-properties).
240
+ `poof` permanently deletes files, freeing space immediately.
302
241
 
303
- ## Contributing
242
+ ## Requirements
304
243
 
305
- In lieu of a formal style guide, take care to maintain the existing coding style.
306
- Add unit tests for any new or changed functionality. Lint and test your code with `npm test`.
244
+ Node.js >= 20.19.6
307
245
 
308
246
  ## License
309
247
 
310
- Copyright (c) 2016 Rafał Ruciński. Licensed under the MIT license.
248
+ MIT
249
+
250
+ <br>
251
+ <p align="center">
252
+ <img width="830" src=".github/banner.webp">
253
+ </p>