This commit is contained in:
darenhsu
2022-07-17 13:16:16 +08:00
parent 84759556ff
commit befd344ab0
28070 changed files with 4008428 additions and 1 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2018, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+640
View File
@@ -0,0 +1,640 @@
# braces [![NPM version](https://img.shields.io/npm/v/braces.svg?style=flat)](https://www.npmjs.com/package/braces) [![NPM monthly downloads](https://img.shields.io/npm/dm/braces.svg?style=flat)](https://npmjs.org/package/braces) [![NPM total downloads](https://img.shields.io/npm/dt/braces.svg?style=flat)](https://npmjs.org/package/braces) [![Linux Build Status](https://img.shields.io/travis/micromatch/braces.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/braces) [![Windows Build Status](https://img.shields.io/appveyor/ci/micromatch/braces.svg?style=flat&label=AppVeyor)](https://ci.appveyor.com/project/micromatch/braces)
> Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.
Please consider following this project's author, [Jon Schlinkert](https://github.com/jonschlinkert), and consider starring the project to show your :heart: and support.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save braces
```
## Why use braces?
Brace patterns are great for matching ranges. Users (and implementors) shouldn't have to think about whether or not they will break their application (or yours) from accidentally defining an aggressive brace pattern. _Braces is the only library that offers a [solution to this problem](#performance)_.
* **Safe(r)**: Braces isn't vulnerable to DoS attacks like [brace-expansion](https://github.com/juliangruber/brace-expansion), [minimatch](https://github.com/isaacs/minimatch) and [multimatch](https://github.com/sindresorhus/multimatch) (a different bug than the [other regex DoS bug](https://medium.com/node-security/minimatch-redos-vulnerability-590da24e6d3c#.jew0b6mpc)).
* **Accurate**: complete support for the [Bash 4.3 Brace Expansion](www.gnu.org/software/bash/) specification (passes all of the Bash braces tests)
* **[fast and performant](#benchmarks)**: Starts fast, runs fast and [scales well](#performance) as patterns increase in complexity.
* **Organized code base**: with parser and compiler that are eas(y|ier) to maintain and update when edge cases crop up.
* **Well-tested**: thousands of test assertions. Passes 100% of the [minimatch](https://github.com/isaacs/minimatch) and [brace-expansion](https://github.com/juliangruber/brace-expansion) unit tests as well (as of the writing of this).
## Usage
The main export is a function that takes one or more brace `patterns` and `options`.
```js
var braces = require('braces');
braces(pattern[, options]);
```
By default, braces returns an optimized regex-source string. To get an array of brace patterns, use `brace.expand()`.
The following section explains the difference in more detail. _(If you're curious about "why" braces does this by default, see [brace matching pitfalls](#brace-matching-pitfalls)_.
### Optimized vs. expanded braces
**Optimized**
By default, patterns are optimized for regex and matching:
```js
console.log(braces('a/{x,y,z}/b'));
//=> ['a/(x|y|z)/b']
```
**Expanded**
To expand patterns the same way as Bash or [minimatch](https://github.com/isaacs/minimatch), use the [.expand](#expand) method:
```js
console.log(braces.expand('a/{x,y,z}/b'));
//=> ['a/x/b', 'a/y/b', 'a/z/b']
```
Or use [options.expand](#optionsexpand):
```js
console.log(braces('a/{x,y,z}/b', {expand: true}));
//=> ['a/x/b', 'a/y/b', 'a/z/b']
```
## Features
* [lists](#lists): Supports "lists": `a/{b,c}/d` => `['a/b/d', 'a/c/d']`
* [sequences](#sequences): Supports alphabetical or numerical "sequences" (ranges): `{1..3}` => `['1', '2', '3']`
* [steps](#steps): Supports "steps" or increments: `{2..10..2}` => `['2', '4', '6', '8', '10']`
* [escaping](#escaping)
* [options](#options)
### Lists
Uses [fill-range](https://github.com/jonschlinkert/fill-range) for expanding alphabetical or numeric lists:
```js
console.log(braces('a/{foo,bar,baz}/*.js'));
//=> ['a/(foo|bar|baz)/*.js']
console.log(braces.expand('a/{foo,bar,baz}/*.js'));
//=> ['a/foo/*.js', 'a/bar/*.js', 'a/baz/*.js']
```
### Sequences
Uses [fill-range](https://github.com/jonschlinkert/fill-range) for expanding alphabetical or numeric ranges (bash "sequences"):
```js
console.log(braces.expand('{1..3}')); // ['1', '2', '3']
console.log(braces.expand('a{01..03}b')); // ['a01b', 'a02b', 'a03b']
console.log(braces.expand('a{1..3}b')); // ['a1b', 'a2b', 'a3b']
console.log(braces.expand('{a..c}')); // ['a', 'b', 'c']
console.log(braces.expand('foo/{a..c}')); // ['foo/a', 'foo/b', 'foo/c']
// supports padded ranges
console.log(braces('a{01..03}b')); //=> [ 'a(0[1-3])b' ]
console.log(braces('a{001..300}b')); //=> [ 'a(0{2}[1-9]|0[1-9][0-9]|[12][0-9]{2}|300)b' ]
```
### Steps
Steps, or increments, may be used with ranges:
```js
console.log(braces.expand('{2..10..2}'));
//=> ['2', '4', '6', '8', '10']
console.log(braces('{2..10..2}'));
//=> ['(2|4|6|8|10)']
```
When the [.optimize](#optimize) method is used, or [options.optimize](#optionsoptimize) is set to true, sequences are passed to [to-regex-range](https://github.com/jonschlinkert/to-regex-range) for expansion.
### Nesting
Brace patterns may be nested. The results of each expanded string are not sorted, and left to right order is preserved.
**"Expanded" braces**
```js
console.log(braces.expand('a{b,c,/{x,y}}/e'));
//=> ['ab/e', 'ac/e', 'a/x/e', 'a/y/e']
console.log(braces.expand('a/{x,{1..5},y}/c'));
//=> ['a/x/c', 'a/1/c', 'a/2/c', 'a/3/c', 'a/4/c', 'a/5/c', 'a/y/c']
```
**"Optimized" braces**
```js
console.log(braces('a{b,c,/{x,y}}/e'));
//=> ['a(b|c|/(x|y))/e']
console.log(braces('a/{x,{1..5},y}/c'));
//=> ['a/(x|([1-5])|y)/c']
```
### Escaping
**Escaping braces**
A brace pattern will not be expanded or evaluted if _either the opening or closing brace is escaped_:
```js
console.log(braces.expand('a\\{d,c,b}e'));
//=> ['a{d,c,b}e']
console.log(braces.expand('a{d,c,b\\}e'));
//=> ['a{d,c,b}e']
```
**Escaping commas**
Commas inside braces may also be escaped:
```js
console.log(braces.expand('a{b\\,c}d'));
//=> ['a{b,c}d']
console.log(braces.expand('a{d\\,c,b}e'));
//=> ['ad,ce', 'abe']
```
**Single items**
Following bash conventions, a brace pattern is also not expanded when it contains a single character:
```js
console.log(braces.expand('a{b}c'));
//=> ['a{b}c']
```
## Options
### options.maxLength
**Type**: `Number`
**Default**: `65,536`
**Description**: Limit the length of the input string. Useful when the input string is generated or your application allows users to pass a string, et cetera.
```js
console.log(braces('a/{b,c}/d', { maxLength: 3 })); //=> throws an error
```
### options.expand
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Generate an "expanded" brace pattern (this option is unncessary with the `.expand` method, which does the same thing).
```js
console.log(braces('a/{b,c}/d', {expand: true}));
//=> [ 'a/b/d', 'a/c/d' ]
```
### options.optimize
**Type**: `Boolean`
**Default**: `true`
**Description**: Enabled by default.
```js
console.log(braces('a/{b,c}/d'));
//=> [ 'a/(b|c)/d' ]
```
### options.nodupes
**Type**: `Boolean`
**Default**: `true`
**Description**: Duplicates are removed by default. To keep duplicates, pass `{nodupes: false}` on the options
### options.rangeLimit
**Type**: `Number`
**Default**: `250`
**Description**: When `braces.expand()` is used, or `options.expand` is true, brace patterns will automatically be [optimized](#optionsoptimize) when the difference between the range minimum and range maximum exceeds the `rangeLimit`. This is to prevent huge ranges from freezing your application.
You can set this to any number, or change `options.rangeLimit` to `Inifinity` to disable this altogether.
**Examples**
```js
// pattern exceeds the "rangeLimit", so it's optimized automatically
console.log(braces.expand('{1..1000}'));
//=> ['([1-9]|[1-9][0-9]{1,2}|1000)']
// pattern does not exceed "rangeLimit", so it's NOT optimized
console.log(braces.expand('{1..100}'));
//=> ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100']
```
### options.transform
**Type**: `Function`
**Default**: `undefined`
**Description**: Customize range expansion.
```js
var range = braces.expand('x{a..e}y', {
transform: function(str) {
return 'foo' + str;
}
});
console.log(range);
//=> [ 'xfooay', 'xfooby', 'xfoocy', 'xfoody', 'xfooey' ]
```
### options.quantifiers
**Type**: `Boolean`
**Default**: `undefined`
**Description**: In regular expressions, quanitifiers can be used to specify how many times a token can be repeated. For example, `a{1,3}` will match the letter `a` one to three times.
Unfortunately, regex quantifiers happen to share the same syntax as [Bash lists](#lists)
The `quantifiers` option tells braces to detect when [regex quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#quantifiers) are defined in the given pattern, and not to try to expand them as lists.
**Examples**
```js
var braces = require('braces');
console.log(braces('a/b{1,3}/{x,y,z}'));
//=> [ 'a/b(1|3)/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true}));
//=> [ 'a/b{1,3}/(x|y|z)' ]
console.log(braces('a/b{1,3}/{x,y,z}', {quantifiers: true, expand: true}));
//=> [ 'a/b{1,3}/x', 'a/b{1,3}/y', 'a/b{1,3}/z' ]
```
### options.unescape
**Type**: `Boolean`
**Default**: `undefined`
**Description**: Strip backslashes that were used for escaping from the result.
## What is "brace expansion"?
Brace expansion is a type of parameter expansion that was made popular by unix shells for generating lists of strings, as well as regex-like matching when used alongside wildcards (globs).
In addition to "expansion", braces are also used for matching. In other words:
* [brace expansion](#brace-expansion) is for generating new lists
* [brace matching](#brace-matching) is for filtering existing lists
<details>
<summary><strong>More about brace expansion</strong> (click to expand)</summary>
There are two main types of brace expansion:
1. **lists**: which are defined using comma-separated values inside curly braces: `{a,b,c}`
2. **sequences**: which are defined using a starting value and an ending value, separated by two dots: `a{1..3}b`. Optionally, a third argument may be passed to define a "step" or increment to use: `a{1..100..10}b`. These are also sometimes referred to as "ranges".
Here are some example brace patterns to illustrate how they work:
**Sets**
```
{a,b,c} => a b c
{a,b,c}{1,2} => a1 a2 b1 b2 c1 c2
```
**Sequences**
```
{1..9} => 1 2 3 4 5 6 7 8 9
{4..-4} => 4 3 2 1 0 -1 -2 -3 -4
{1..20..3} => 1 4 7 10 13 16 19
{a..j} => a b c d e f g h i j
{j..a} => j i h g f e d c b a
{a..z..3} => a d g j m p s v y
```
**Combination**
Sets and sequences can be mixed together or used along with any other strings.
```
{a,b,c}{1..3} => a1 a2 a3 b1 b2 b3 c1 c2 c3
foo/{a,b,c}/bar => foo/a/bar foo/b/bar foo/c/bar
```
The fact that braces can be "expanded" from relatively simple patterns makes them ideal for quickly generating test fixtures, file paths, and similar use cases.
## Brace matching
In addition to _expansion_, brace patterns are also useful for performing regular-expression-like matching.
For example, the pattern `foo/{1..3}/bar` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
```
But not:
```
baz/1/qux
baz/2/qux
baz/3/qux
```
Braces can also be combined with [glob patterns](https://github.com/jonschlinkert/micromatch) to perform more advanced wildcard matching. For example, the pattern `*/{1..3}/*` would match any of following strings:
```
foo/1/bar
foo/2/bar
foo/3/bar
baz/1/qux
baz/2/qux
baz/3/qux
```
## Brace matching pitfalls
Although brace patterns offer a user-friendly way of matching ranges or sets of strings, there are also some major disadvantages and potential risks you should be aware of.
### tldr
**"brace bombs"**
* brace expansion can eat up a huge amount of processing resources
* as brace patterns increase _linearly in size_, the system resources required to expand the pattern increase exponentially
* users can accidentally (or intentially) exhaust your system's resources resulting in the equivalent of a DoS attack (bonus: no programming knowledge is required!)
For a more detailed explanation with examples, see the [geometric complexity](#geometric-complexity) section.
### The solution
Jump to the [performance section](#performance) to see how Braces solves this problem in comparison to other libraries.
### Geometric complexity
At minimum, brace patterns with sets limited to two elements have quadradic or `O(n^2)` complexity. But the complexity of the algorithm increases exponentially as the number of sets, _and elements per set_, increases, which is `O(n^c)`.
For example, the following sets demonstrate quadratic (`O(n^2)`) complexity:
```
{1,2}{3,4} => (2X2) => 13 14 23 24
{1,2}{3,4}{5,6} => (2X2X2) => 135 136 145 146 235 236 245 246
```
But add an element to a set, and we get a n-fold Cartesian product with `O(n^c)` complexity:
```
{1,2,3}{4,5,6}{7,8,9} => (3X3X3) => 147 148 149 157 158 159 167 168 169 247 248
249 257 258 259 267 268 269 347 348 349 357
358 359 367 368 369
```
Now, imagine how this complexity grows given that each element is a n-tuple:
```
{1..100}{1..100} => (100X100) => 10,000 elements (38.4 kB)
{1..100}{1..100}{1..100} => (100X100X100) => 1,000,000 elements (5.76 MB)
```
Although these examples are clearly contrived, they demonstrate how brace patterns can quickly grow out of control.
**More information**
Interested in learning more about brace expansion?
* [linuxjournal/bash-brace-expansion](http://www.linuxjournal.com/content/bash-brace-expansion)
* [rosettacode/Brace_expansion](https://rosettacode.org/wiki/Brace_expansion)
* [cartesian product](https://en.wikipedia.org/wiki/Cartesian_product)
</details>
## Performance
Braces is not only screaming fast, it's also more accurate the other brace expansion libraries.
### Better algorithms
Fortunately there is a solution to the ["brace bomb" problem](#brace-matching-pitfalls): _don't expand brace patterns into an array when they're used for matching_.
Instead, convert the pattern into an optimized regular expression. This is easier said than done, and braces is the only library that does this currently.
**The proof is in the numbers**
Minimatch gets exponentially slower as patterns increase in complexity, braces does not. The following results were generated using `braces()` and `minimatch.braceExpand()`, respectively.
| **Pattern** | **braces** | **[minimatch](https://github.com/isaacs/minimatch)** |
| --- | --- | --- |
| `{1..9007199254740991}`<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup> | `298 B` (5ms 459μs) | N/A (freezes) |
| `{1..1000000000000000}` | `41 B` (1ms 15μs) | N/A (freezes) |
| `{1..100000000000000}` | `40 B` (890μs) | N/A (freezes) |
| `{1..10000000000000}` | `39 B` (2ms 49μs) | N/A (freezes) |
| `{1..1000000000000}` | `38 B` (608μs) | N/A (freezes) |
| `{1..100000000000}` | `37 B` (397μs) | N/A (freezes) |
| `{1..10000000000}` | `35 B` (983μs) | N/A (freezes) |
| `{1..1000000000}` | `34 B` (798μs) | N/A (freezes) |
| `{1..100000000}` | `33 B` (733μs) | N/A (freezes) |
| `{1..10000000}` | `32 B` (5ms 632μs) | `78.89 MB` (16s 388ms 569μs) |
| `{1..1000000}` | `31 B` (1ms 381μs) | `6.89 MB` (1s 496ms 887μs) |
| `{1..100000}` | `30 B` (950μs) | `588.89 kB` (146ms 921μs) |
| `{1..10000}` | `29 B` (1ms 114μs) | `48.89 kB` (14ms 187μs) |
| `{1..1000}` | `28 B` (760μs) | `3.89 kB` (1ms 453μs) |
| `{1..100}` | `22 B` (345μs) | `291 B` (196μs) |
| `{1..10}` | `10 B` (533μs) | `20 B` (37μs) |
| `{1..3}` | `7 B` (190μs) | `5 B` (27μs) |
### Faster algorithms
When you need expansion, braces is still much faster.
_(the following results were generated using `braces.expand()` and `minimatch.braceExpand()`, respectively)_
| **Pattern** | **braces** | **[minimatch](https://github.com/isaacs/minimatch)** |
| --- | --- | --- |
| `{1..10000000}` | `78.89 MB` (2s 698ms 642μs) | `78.89 MB` (18s 601ms 974μs) |
| `{1..1000000}` | `6.89 MB` (458ms 576μs) | `6.89 MB` (1s 491ms 621μs) |
| `{1..100000}` | `588.89 kB` (20ms 728μs) | `588.89 kB` (156ms 919μs) |
| `{1..10000}` | `48.89 kB` (2ms 202μs) | `48.89 kB` (13ms 641μs) |
| `{1..1000}` | `3.89 kB` (1ms 796μs) | `3.89 kB` (1ms 958μs) |
| `{1..100}` | `291 B` (424μs) | `291 B` (211μs) |
| `{1..10}` | `20 B` (487μs) | `20 B` (72μs) |
| `{1..3}` | `5 B` (166μs) | `5 B` (27μs) |
If you'd like to run these comparisons yourself, see [test/support/generate.js](test/support/generate.js).
## Benchmarks
### Running benchmarks
Install dev dependencies:
```bash
npm i -d && npm benchmark
```
### Latest results
```bash
Benchmarking: (8 of 8)
· combination-nested
· combination
· escaped
· list-basic
· list-multiple
· no-braces
· sequence-basic
· sequence-multiple
# benchmark/fixtures/combination-nested.js (52 bytes)
brace-expansion x 4,756 ops/sec ±1.09% (86 runs sampled)
braces x 11,202,303 ops/sec ±1.06% (88 runs sampled)
minimatch x 4,816 ops/sec ±0.99% (87 runs sampled)
fastest is braces
# benchmark/fixtures/combination.js (51 bytes)
brace-expansion x 625 ops/sec ±0.87% (87 runs sampled)
braces x 11,031,884 ops/sec ±0.72% (90 runs sampled)
minimatch x 637 ops/sec ±0.84% (88 runs sampled)
fastest is braces
# benchmark/fixtures/escaped.js (44 bytes)
brace-expansion x 163,325 ops/sec ±1.05% (87 runs sampled)
braces x 10,655,071 ops/sec ±1.22% (88 runs sampled)
minimatch x 147,495 ops/sec ±0.96% (88 runs sampled)
fastest is braces
# benchmark/fixtures/list-basic.js (40 bytes)
brace-expansion x 99,726 ops/sec ±1.07% (83 runs sampled)
braces x 10,596,584 ops/sec ±0.98% (88 runs sampled)
minimatch x 100,069 ops/sec ±1.17% (86 runs sampled)
fastest is braces
# benchmark/fixtures/list-multiple.js (52 bytes)
brace-expansion x 34,348 ops/sec ±1.08% (88 runs sampled)
braces x 9,264,131 ops/sec ±1.12% (88 runs sampled)
minimatch x 34,893 ops/sec ±0.87% (87 runs sampled)
fastest is braces
# benchmark/fixtures/no-braces.js (48 bytes)
brace-expansion x 275,368 ops/sec ±1.18% (89 runs sampled)
braces x 9,134,677 ops/sec ±0.95% (88 runs sampled)
minimatch x 3,755,954 ops/sec ±1.13% (89 runs sampled)
fastest is braces
# benchmark/fixtures/sequence-basic.js (41 bytes)
brace-expansion x 5,492 ops/sec ±1.35% (87 runs sampled)
braces x 8,485,034 ops/sec ±1.28% (89 runs sampled)
minimatch x 5,341 ops/sec ±1.17% (87 runs sampled)
fastest is braces
# benchmark/fixtures/sequence-multiple.js (51 bytes)
brace-expansion x 116 ops/sec ±0.77% (77 runs sampled)
braces x 9,445,118 ops/sec ±1.32% (84 runs sampled)
minimatch x 109 ops/sec ±1.16% (76 runs sampled)
fastest is braces
```
## About
<details>
<summary><strong>Contributing</strong></summary>
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
</details>
<details>
<summary><strong>Running Tests</strong></summary>
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
</details>
<details>
<summary><strong>Building docs</strong></summary>
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
</details>
### Related projects
You might also be interested in these projects:
* [expand-brackets](https://www.npmjs.com/package/expand-brackets): Expand POSIX bracket expressions (character classes) in glob patterns. | [homepage](https://github.com/jonschlinkert/expand-brackets "Expand POSIX bracket expressions (character classes) in glob patterns.")
* [extglob](https://www.npmjs.com/package/extglob): Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob… [more](https://github.com/micromatch/extglob) | [homepage](https://github.com/micromatch/extglob "Extended glob support for JavaScript. Adds (almost) the expressive power of regular expressions to glob patterns.")
* [fill-range](https://www.npmjs.com/package/fill-range): Fill in a range of numbers or letters, optionally passing an increment or `step` to… [more](https://github.com/jonschlinkert/fill-range) | [homepage](https://github.com/jonschlinkert/fill-range "Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`")
* [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/micromatch/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.")
* [nanomatch](https://www.npmjs.com/package/nanomatch): Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash… [more](https://github.com/micromatch/nanomatch) | [homepage](https://github.com/micromatch/nanomatch "Fast, minimal glob matcher for node.js. Similar to micromatch, minimatch and multimatch, but complete Bash 4.3 wildcard support only (no support for exglobs, posix brackets or braces)")
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 188 | [jonschlinkert](https://github.com/jonschlinkert) |
| 4 | [doowb](https://github.com/doowb) |
| 1 | [es128](https://github.com/es128) |
| 1 | [eush77](https://github.com/eush77) |
| 1 | [hemanth](https://github.com/hemanth) |
### Author
**Jon Schlinkert**
* [linkedin/in/jonschlinkert](https://linkedin.com/in/jonschlinkert)
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
### License
Copyright © 2018, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on February 17, 2018._
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item">this is the largest safe integer allowed in JavaScript. <a href="#fnref1" class="footnote-backref">↩</a>
</li>
</ol>
</section>
+318
View File
@@ -0,0 +1,318 @@
'use strict';
/**
* Module dependencies
*/
var toRegex = require('to-regex');
var unique = require('array-unique');
var extend = require('extend-shallow');
/**
* Local dependencies
*/
var compilers = require('./lib/compilers');
var parsers = require('./lib/parsers');
var Braces = require('./lib/braces');
var utils = require('./lib/utils');
var MAX_LENGTH = 1024 * 64;
var cache = {};
/**
* Convert the given `braces` pattern into a regex-compatible string. By default, only one string is generated for every input string. Set `options.expand` to true to return an array of patterns (similar to Bash or minimatch. Before using `options.expand`, it's recommended that you read the [performance notes](#performance)).
*
* ```js
* var braces = require('braces');
* console.log(braces('{a,b,c}'));
* //=> ['(a|b|c)']
*
* console.log(braces('{a,b,c}', {expand: true}));
* //=> ['a', 'b', 'c']
* ```
* @param {String} `str`
* @param {Object} `options`
* @return {String}
* @api public
*/
function braces(pattern, options) {
var key = utils.createKey(String(pattern), options);
var arr = [];
var disabled = options && options.cache === false;
if (!disabled && cache.hasOwnProperty(key)) {
return cache[key];
}
if (Array.isArray(pattern)) {
for (var i = 0; i < pattern.length; i++) {
arr.push.apply(arr, braces.create(pattern[i], options));
}
} else {
arr = braces.create(pattern, options);
}
if (options && options.nodupes === true) {
arr = unique(arr);
}
if (!disabled) {
cache[key] = arr;
}
return arr;
}
/**
* Expands a brace pattern into an array. This method is called by the main [braces](#braces) function when `options.expand` is true. Before using this method it's recommended that you read the [performance notes](#performance)) and advantages of using [.optimize](#optimize) instead.
*
* ```js
* var braces = require('braces');
* console.log(braces.expand('a/{b,c}/d'));
* //=> ['a/b/d', 'a/c/d'];
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.expand = function(pattern, options) {
return braces.create(pattern, extend({}, options, {expand: true}));
};
/**
* Expands a brace pattern into a regex-compatible, optimized string. This method is called by the main [braces](#braces) function by default.
*
* ```js
* var braces = require('braces');
* console.log(braces.expand('a/{b,c}/d'));
* //=> ['a/(b|c)/d']
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.optimize = function(pattern, options) {
return braces.create(pattern, options);
};
/**
* Processes a brace pattern and returns either an expanded array (if `options.expand` is true), a highly optimized regex-compatible string. This method is called by the main [braces](#braces) function.
*
* ```js
* var braces = require('braces');
* console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}'))
* //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)'
* ```
* @param {String} `pattern` Brace pattern
* @param {Object} `options`
* @return {Array} Returns an array of expanded values.
* @api public
*/
braces.create = function(pattern, options) {
if (typeof pattern !== 'string') {
throw new TypeError('expected a string');
}
var maxLength = (options && options.maxLength) || MAX_LENGTH;
if (pattern.length >= maxLength) {
throw new Error('expected pattern to be less than ' + maxLength + ' characters');
}
function create() {
if (pattern === '' || pattern.length < 3) {
return [pattern];
}
if (utils.isEmptySets(pattern)) {
return [];
}
if (utils.isQuotedString(pattern)) {
return [pattern.slice(1, -1)];
}
var proto = new Braces(options);
var result = !options || options.expand !== true
? proto.optimize(pattern, options)
: proto.expand(pattern, options);
// get the generated pattern(s)
var arr = result.output;
// filter out empty strings if specified
if (options && options.noempty === true) {
arr = arr.filter(Boolean);
}
// filter out duplicates if specified
if (options && options.nodupes === true) {
arr = unique(arr);
}
Object.defineProperty(arr, 'result', {
enumerable: false,
value: result
});
return arr;
}
return memoize('create', pattern, options, create);
};
/**
* Create a regular expression from the given string `pattern`.
*
* ```js
* var braces = require('braces');
*
* console.log(braces.makeRe('id-{200..300}'));
* //=> /^(?:id-(20[0-9]|2[1-9][0-9]|300))$/
* ```
* @param {String} `pattern` The pattern to convert to regex.
* @param {Object} `options`
* @return {RegExp}
* @api public
*/
braces.makeRe = function(pattern, options) {
if (typeof pattern !== 'string') {
throw new TypeError('expected a string');
}
var maxLength = (options && options.maxLength) || MAX_LENGTH;
if (pattern.length >= maxLength) {
throw new Error('expected pattern to be less than ' + maxLength + ' characters');
}
function makeRe() {
var arr = braces(pattern, options);
var opts = extend({strictErrors: false}, options);
return toRegex(arr, opts);
}
return memoize('makeRe', pattern, options, makeRe);
};
/**
* Parse the given `str` with the given `options`.
*
* ```js
* var braces = require('braces');
* var ast = braces.parse('a/{b,c}/d');
* console.log(ast);
* // { type: 'root',
* // errors: [],
* // input: 'a/{b,c}/d',
* // nodes:
* // [ { type: 'bos', val: '' },
* // { type: 'text', val: 'a/' },
* // { type: 'brace',
* // nodes:
* // [ { type: 'brace.open', val: '{' },
* // { type: 'text', val: 'b,c' },
* // { type: 'brace.close', val: '}' } ] },
* // { type: 'text', val: '/d' },
* // { type: 'eos', val: '' } ] }
* ```
* @param {String} `pattern` Brace pattern to parse
* @param {Object} `options`
* @return {Object} Returns an AST
* @api public
*/
braces.parse = function(pattern, options) {
var proto = new Braces(options);
return proto.parse(pattern, options);
};
/**
* Compile the given `ast` or string with the given `options`.
*
* ```js
* var braces = require('braces');
* var ast = braces.parse('a/{b,c}/d');
* console.log(braces.compile(ast));
* // { options: { source: 'string' },
* // state: {},
* // compilers:
* // { eos: [Function],
* // noop: [Function],
* // bos: [Function],
* // brace: [Function],
* // 'brace.open': [Function],
* // text: [Function],
* // 'brace.close': [Function] },
* // output: [ 'a/(b|c)/d' ],
* // ast:
* // { ... },
* // parsingErrors: [] }
* ```
* @param {Object|String} `ast` AST from [.parse](#parse). If a string is passed it will be parsed first.
* @param {Object} `options`
* @return {Object} Returns an object that has an `output` property with the compiled string.
* @api public
*/
braces.compile = function(ast, options) {
var proto = new Braces(options);
return proto.compile(ast, options);
};
/**
* Clear the regex cache.
*
* ```js
* braces.clearCache();
* ```
* @api public
*/
braces.clearCache = function() {
cache = braces.cache = {};
};
/**
* Memoize a generated regex or function. A unique key is generated
* from the method name, pattern, and user-defined options. Set
* options.memoize to false to disable.
*/
function memoize(type, pattern, options, fn) {
var key = utils.createKey(type + ':' + pattern, options);
var disabled = options && options.cache === false;
if (disabled) {
braces.clearCache();
return fn(pattern, options);
}
if (cache.hasOwnProperty(key)) {
return cache[key];
}
var res = fn(pattern, options);
cache[key] = res;
return res;
}
/**
* Expose `Braces` constructor and methods
* @type {Function}
*/
braces.Braces = Braces;
braces.compilers = compilers;
braces.parsers = parsers;
braces.cache = cache;
/**
* Expose `braces`
* @type {Function}
*/
module.exports = braces;
+104
View File
@@ -0,0 +1,104 @@
'use strict';
var extend = require('extend-shallow');
var Snapdragon = require('snapdragon');
var compilers = require('./compilers');
var parsers = require('./parsers');
var utils = require('./utils');
/**
* Customize Snapdragon parser and renderer
*/
function Braces(options) {
this.options = extend({}, options);
}
/**
* Initialize braces
*/
Braces.prototype.init = function(options) {
if (this.isInitialized) return;
this.isInitialized = true;
var opts = utils.createOptions({}, this.options, options);
this.snapdragon = this.options.snapdragon || new Snapdragon(opts);
this.compiler = this.snapdragon.compiler;
this.parser = this.snapdragon.parser;
compilers(this.snapdragon, opts);
parsers(this.snapdragon, opts);
/**
* Call Snapdragon `.parse` method. When AST is returned, we check to
* see if any unclosed braces are left on the stack and, if so, we iterate
* over the stack and correct the AST so that compilers are called in the correct
* order and unbalance braces are properly escaped.
*/
utils.define(this.snapdragon, 'parse', function(pattern, options) {
var parsed = Snapdragon.prototype.parse.apply(this, arguments);
this.parser.ast.input = pattern;
var stack = this.parser.stack;
while (stack.length) {
addParent({type: 'brace.close', val: ''}, stack.pop());
}
function addParent(node, parent) {
utils.define(node, 'parent', parent);
parent.nodes.push(node);
}
// add non-enumerable parser reference
utils.define(parsed, 'parser', this.parser);
return parsed;
});
};
/**
* Decorate `.parse` method
*/
Braces.prototype.parse = function(ast, options) {
if (ast && typeof ast === 'object' && ast.nodes) return ast;
this.init(options);
return this.snapdragon.parse(ast, options);
};
/**
* Decorate `.compile` method
*/
Braces.prototype.compile = function(ast, options) {
if (typeof ast === 'string') {
ast = this.parse(ast, options);
} else {
this.init(options);
}
return this.snapdragon.compile(ast, options);
};
/**
* Expand
*/
Braces.prototype.expand = function(pattern) {
var ast = this.parse(pattern, {expand: true});
return this.compile(ast, {expand: true});
};
/**
* Optimize
*/
Braces.prototype.optimize = function(pattern) {
var ast = this.parse(pattern, {optimize: true});
return this.compile(ast, {optimize: true});
};
/**
* Expose `Braces`
*/
module.exports = Braces;
+282
View File
@@ -0,0 +1,282 @@
'use strict';
var utils = require('./utils');
module.exports = function(braces, options) {
braces.compiler
/**
* bos
*/
.set('bos', function() {
if (this.output) return;
this.ast.queue = isEscaped(this.ast) ? [this.ast.val] : [];
this.ast.count = 1;
})
/**
* Square brackets
*/
.set('bracket', function(node) {
var close = node.close;
var open = !node.escaped ? '[' : '\\[';
var negated = node.negated;
var inner = node.inner;
inner = inner.replace(/\\(?=[\\\w]|$)/g, '\\\\');
if (inner === ']-') {
inner = '\\]\\-';
}
if (negated && inner.indexOf('.') === -1) {
inner += '.';
}
if (negated && inner.indexOf('/') === -1) {
inner += '/';
}
var val = open + negated + inner + close;
var queue = node.parent.queue;
var last = utils.arrayify(queue.pop());
queue.push(utils.join(last, val));
queue.push.apply(queue, []);
})
/**
* Brace
*/
.set('brace', function(node) {
node.queue = isEscaped(node) ? [node.val] : [];
node.count = 1;
return this.mapVisit(node.nodes);
})
/**
* Open
*/
.set('brace.open', function(node) {
node.parent.open = node.val;
})
/**
* Inner
*/
.set('text', function(node) {
var queue = node.parent.queue;
var escaped = node.escaped;
var segs = [node.val];
if (node.optimize === false) {
options = utils.extend({}, options, {optimize: false});
}
if (node.multiplier > 1) {
node.parent.count *= node.multiplier;
}
if (options.quantifiers === true && utils.isQuantifier(node.val)) {
escaped = true;
} else if (node.val.length > 1) {
if (isType(node.parent, 'brace') && !isEscaped(node)) {
var expanded = utils.expand(node.val, options);
segs = expanded.segs;
if (expanded.isOptimized) {
node.parent.isOptimized = true;
}
// if nothing was expanded, we probably have a literal brace
if (!segs.length) {
var val = (expanded.val || node.val);
if (options.unescape !== false) {
// unescape unexpanded brace sequence/set separators
val = val.replace(/\\([,.])/g, '$1');
// strip quotes
val = val.replace(/["'`]/g, '');
}
segs = [val];
escaped = true;
}
}
} else if (node.val === ',') {
if (options.expand) {
node.parent.queue.push(['']);
segs = [''];
} else {
segs = ['|'];
}
} else {
escaped = true;
}
if (escaped && isType(node.parent, 'brace')) {
if (node.parent.nodes.length <= 4 && node.parent.count === 1) {
node.parent.escaped = true;
} else if (node.parent.length <= 3) {
node.parent.escaped = true;
}
}
if (!hasQueue(node.parent)) {
node.parent.queue = segs;
return;
}
var last = utils.arrayify(queue.pop());
if (node.parent.count > 1 && options.expand) {
last = multiply(last, node.parent.count);
node.parent.count = 1;
}
queue.push(utils.join(utils.flatten(last), segs.shift()));
queue.push.apply(queue, segs);
})
/**
* Close
*/
.set('brace.close', function(node) {
var queue = node.parent.queue;
var prev = node.parent.parent;
var last = prev.queue.pop();
var open = node.parent.open;
var close = node.val;
if (open && close && isOptimized(node, options)) {
open = '(';
close = ')';
}
// if a close brace exists, and the previous segment is one character
// don't wrap the result in braces or parens
var ele = utils.last(queue);
if (node.parent.count > 1 && options.expand) {
ele = multiply(queue.pop(), node.parent.count);
node.parent.count = 1;
queue.push(ele);
}
if (close && typeof ele === 'string' && ele.length === 1) {
open = '';
close = '';
}
if ((isLiteralBrace(node, options) || noInner(node)) && !node.parent.hasEmpty) {
queue.push(utils.join(open, queue.pop() || ''));
queue = utils.flatten(utils.join(queue, close));
}
if (typeof last === 'undefined') {
prev.queue = [queue];
} else {
prev.queue.push(utils.flatten(utils.join(last, queue)));
}
})
/**
* eos
*/
.set('eos', function(node) {
if (this.input) return;
if (options.optimize !== false) {
this.output = utils.last(utils.flatten(this.ast.queue));
} else if (Array.isArray(utils.last(this.ast.queue))) {
this.output = utils.flatten(this.ast.queue.pop());
} else {
this.output = utils.flatten(this.ast.queue);
}
if (node.parent.count > 1 && options.expand) {
this.output = multiply(this.output, node.parent.count);
}
this.output = utils.arrayify(this.output);
this.ast.queue = [];
});
};
/**
* Multiply the segments in the current brace level
*/
function multiply(queue, n, options) {
return utils.flatten(utils.repeat(utils.arrayify(queue), n));
}
/**
* Return true if `node` is escaped
*/
function isEscaped(node) {
return node.escaped === true;
}
/**
* Returns true if regex parens should be used for sets. If the parent `type`
* is not `brace`, then we're on a root node, which means we should never
* expand segments and open/close braces should be `{}` (since this indicates
* a brace is missing from the set)
*/
function isOptimized(node, options) {
if (node.parent.isOptimized) return true;
return isType(node.parent, 'brace')
&& !isEscaped(node.parent)
&& options.expand !== true;
}
/**
* Returns true if the value in `node` should be wrapped in a literal brace.
* @return {Boolean}
*/
function isLiteralBrace(node, options) {
return isEscaped(node.parent) || options.optimize !== false;
}
/**
* Returns true if the given `node` does not have an inner value.
* @return {Boolean}
*/
function noInner(node, type) {
if (node.parent.queue.length === 1) {
return true;
}
var nodes = node.parent.nodes;
return nodes.length === 3
&& isType(nodes[0], 'brace.open')
&& !isType(nodes[1], 'text')
&& isType(nodes[2], 'brace.close');
}
/**
* Returns true if the given `node` is the given `type`
* @return {Boolean}
*/
function isType(node, type) {
return typeof node !== 'undefined' && node.type === type;
}
/**
* Returns true if the given `node` has a non-empty queue.
* @return {Boolean}
*/
function hasQueue(node) {
return Array.isArray(node.queue) && node.queue.length;
}
+360
View File
@@ -0,0 +1,360 @@
'use strict';
var Node = require('snapdragon-node');
var utils = require('./utils');
/**
* Braces parsers
*/
module.exports = function(braces, options) {
braces.parser
.set('bos', function() {
if (!this.parsed) {
this.ast = this.nodes[0] = new Node(this.ast);
}
})
/**
* Character parsers
*/
.set('escape', function() {
var pos = this.position();
var m = this.match(/^(?:\\(.)|\$\{)/);
if (!m) return;
var prev = this.prev();
var last = utils.last(prev.nodes);
var node = pos(new Node({
type: 'text',
multiplier: 1,
val: m[0]
}));
if (node.val === '\\\\') {
return node;
}
if (node.val === '${') {
var str = this.input;
var idx = -1;
var ch;
while ((ch = str[++idx])) {
this.consume(1);
node.val += ch;
if (ch === '\\') {
node.val += str[++idx];
continue;
}
if (ch === '}') {
break;
}
}
}
if (this.options.unescape !== false) {
node.val = node.val.replace(/\\([{}])/g, '$1');
}
if (last.val === '"' && this.input.charAt(0) === '"') {
last.val = node.val;
this.consume(1);
return;
}
return concatNodes.call(this, pos, node, prev, options);
})
/**
* Brackets: "[...]" (basic, this is overridden by
* other parsers in more advanced implementations)
*/
.set('bracket', function() {
var isInside = this.isInside('brace');
var pos = this.position();
var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]-)(\]|[^*+?]+)|\[)/);
if (!m) return;
var prev = this.prev();
var val = m[0];
var negated = m[1] ? '^' : '';
var inner = m[2] || '';
var close = m[3] || '';
if (isInside && prev.type === 'brace') {
prev.text = prev.text || '';
prev.text += val;
}
var esc = this.input.slice(0, 2);
if (inner === '' && esc === '\\]') {
inner += esc;
this.consume(2);
var str = this.input;
var idx = -1;
var ch;
while ((ch = str[++idx])) {
this.consume(1);
if (ch === ']') {
close = ch;
break;
}
inner += ch;
}
}
return pos(new Node({
type: 'bracket',
val: val,
escaped: close !== ']',
negated: negated,
inner: inner,
close: close
}));
})
/**
* Empty braces (we capture these early to
* speed up processing in the compiler)
*/
.set('multiplier', function() {
var isInside = this.isInside('brace');
var pos = this.position();
var m = this.match(/^\{((?:,|\{,+\})+)\}/);
if (!m) return;
this.multiplier = true;
var prev = this.prev();
var val = m[0];
if (isInside && prev.type === 'brace') {
prev.text = prev.text || '';
prev.text += val;
}
var node = pos(new Node({
type: 'text',
multiplier: 1,
match: m,
val: val
}));
return concatNodes.call(this, pos, node, prev, options);
})
/**
* Open
*/
.set('brace.open', function() {
var pos = this.position();
var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/);
if (!m) return;
var prev = this.prev();
var last = utils.last(prev.nodes);
// if the last parsed character was an extglob character
// we need to _not optimize_ the brace pattern because
// it might be mistaken for an extglob by a downstream parser
if (last && last.val && isExtglobChar(last.val.slice(-1))) {
last.optimize = false;
}
var open = pos(new Node({
type: 'brace.open',
val: m[0]
}));
var node = pos(new Node({
type: 'brace',
nodes: []
}));
node.push(open);
prev.push(node);
this.push('brace', node);
})
/**
* Close
*/
.set('brace.close', function() {
var pos = this.position();
var m = this.match(/^\}/);
if (!m || !m[0]) return;
var brace = this.pop('brace');
var node = pos(new Node({
type: 'brace.close',
val: m[0]
}));
if (!this.isType(brace, 'brace')) {
if (this.options.strict) {
throw new Error('missing opening "{"');
}
node.type = 'text';
node.multiplier = 0;
node.escaped = true;
return node;
}
var prev = this.prev();
var last = utils.last(prev.nodes);
if (last.text) {
var lastNode = utils.last(last.nodes);
if (lastNode.val === ')' && /[!@*?+]\(/.test(last.text)) {
var open = last.nodes[0];
var text = last.nodes[1];
if (open.type === 'brace.open' && text && text.type === 'text') {
text.optimize = false;
}
}
}
if (brace.nodes.length > 2) {
var first = brace.nodes[1];
if (first.type === 'text' && first.val === ',') {
brace.nodes.splice(1, 1);
brace.nodes.push(first);
}
}
brace.push(node);
})
/**
* Capture boundary characters
*/
.set('boundary', function() {
var pos = this.position();
var m = this.match(/^[$^](?!\{)/);
if (!m) return;
return pos(new Node({
type: 'text',
val: m[0]
}));
})
/**
* One or zero, non-comma characters wrapped in braces
*/
.set('nobrace', function() {
var isInside = this.isInside('brace');
var pos = this.position();
var m = this.match(/^\{[^,]?\}/);
if (!m) return;
var prev = this.prev();
var val = m[0];
if (isInside && prev.type === 'brace') {
prev.text = prev.text || '';
prev.text += val;
}
return pos(new Node({
type: 'text',
multiplier: 0,
val: val
}));
})
/**
* Text
*/
.set('text', function() {
var isInside = this.isInside('brace');
var pos = this.position();
var m = this.match(/^((?!\\)[^${}[\]])+/);
if (!m) return;
var prev = this.prev();
var val = m[0];
if (isInside && prev.type === 'brace') {
prev.text = prev.text || '';
prev.text += val;
}
var node = pos(new Node({
type: 'text',
multiplier: 1,
val: val
}));
return concatNodes.call(this, pos, node, prev, options);
});
};
/**
* Returns true if the character is an extglob character.
*/
function isExtglobChar(ch) {
return ch === '!' || ch === '@' || ch === '*' || ch === '?' || ch === '+';
}
/**
* Combine text nodes, and calculate empty sets (`{,,}`)
* @param {Function} `pos` Function to calculate node position
* @param {Object} `node` AST node
* @return {Object}
*/
function concatNodes(pos, node, parent, options) {
node.orig = node.val;
var prev = this.prev();
var last = utils.last(prev.nodes);
var isEscaped = false;
if (node.val.length > 1) {
var a = node.val.charAt(0);
var b = node.val.slice(-1);
isEscaped = (a === '"' && b === '"')
|| (a === "'" && b === "'")
|| (a === '`' && b === '`');
}
if (isEscaped && options.unescape !== false) {
node.val = node.val.slice(1, node.val.length - 1);
node.escaped = true;
}
if (node.match) {
var match = node.match[1];
if (!match || match.indexOf('}') === -1) {
match = node.match[0];
}
// replace each set with a single ","
var val = match.replace(/\{/g, ',').replace(/\}/g, '');
node.multiplier *= val.length;
node.val = '';
}
var simpleText = last.type === 'text'
&& last.multiplier === 1
&& node.multiplier === 1
&& node.val;
if (simpleText) {
last.val += node.val;
return;
}
prev.push(node);
}
+343
View File
@@ -0,0 +1,343 @@
'use strict';
var splitString = require('split-string');
var utils = module.exports;
/**
* Module dependencies
*/
utils.extend = require('extend-shallow');
utils.flatten = require('arr-flatten');
utils.isObject = require('isobject');
utils.fillRange = require('fill-range');
utils.repeat = require('repeat-element');
utils.unique = require('array-unique');
utils.define = function(obj, key, val) {
Object.defineProperty(obj, key, {
writable: true,
configurable: true,
enumerable: false,
value: val
});
};
/**
* Returns true if the given string contains only empty brace sets.
*/
utils.isEmptySets = function(str) {
return /^(?:\{,\})+$/.test(str);
};
/**
* Returns true if the given string contains only empty brace sets.
*/
utils.isQuotedString = function(str) {
var open = str.charAt(0);
if (open === '\'' || open === '"' || open === '`') {
return str.slice(-1) === open;
}
return false;
};
/**
* Create the key to use for memoization. The unique key is generated
* by iterating over the options and concatenating key-value pairs
* to the pattern string.
*/
utils.createKey = function(pattern, options) {
var id = pattern;
if (typeof options === 'undefined') {
return id;
}
var keys = Object.keys(options);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
id += ';' + key + '=' + String(options[key]);
}
return id;
};
/**
* Normalize options
*/
utils.createOptions = function(options) {
var opts = utils.extend.apply(null, arguments);
if (typeof opts.expand === 'boolean') {
opts.optimize = !opts.expand;
}
if (typeof opts.optimize === 'boolean') {
opts.expand = !opts.optimize;
}
if (opts.optimize === true) {
opts.makeRe = true;
}
return opts;
};
/**
* Join patterns in `a` to patterns in `b`
*/
utils.join = function(a, b, options) {
options = options || {};
a = utils.arrayify(a);
b = utils.arrayify(b);
if (!a.length) return b;
if (!b.length) return a;
var len = a.length;
var idx = -1;
var arr = [];
while (++idx < len) {
var val = a[idx];
if (Array.isArray(val)) {
for (var i = 0; i < val.length; i++) {
val[i] = utils.join(val[i], b, options);
}
arr.push(val);
continue;
}
for (var j = 0; j < b.length; j++) {
var bval = b[j];
if (Array.isArray(bval)) {
arr.push(utils.join(val, bval, options));
} else {
arr.push(val + bval);
}
}
}
return arr;
};
/**
* Split the given string on `,` if not escaped.
*/
utils.split = function(str, options) {
var opts = utils.extend({sep: ','}, options);
if (typeof opts.keepQuotes !== 'boolean') {
opts.keepQuotes = true;
}
if (opts.unescape === false) {
opts.keepEscaping = true;
}
return splitString(str, opts, utils.escapeBrackets(opts));
};
/**
* Expand ranges or sets in the given `pattern`.
*
* @param {String} `str`
* @param {Object} `options`
* @return {Object}
*/
utils.expand = function(str, options) {
var opts = utils.extend({rangeLimit: 10000}, options);
var segs = utils.split(str, opts);
var tok = { segs: segs };
if (utils.isQuotedString(str)) {
return tok;
}
if (opts.rangeLimit === true) {
opts.rangeLimit = 10000;
}
if (segs.length > 1) {
if (opts.optimize === false) {
tok.val = segs[0];
return tok;
}
tok.segs = utils.stringifyArray(tok.segs);
} else if (segs.length === 1) {
var arr = str.split('..');
if (arr.length === 1) {
tok.val = tok.segs[tok.segs.length - 1] || tok.val || str;
tok.segs = [];
return tok;
}
if (arr.length === 2 && arr[0] === arr[1]) {
tok.escaped = true;
tok.val = arr[0];
tok.segs = [];
return tok;
}
if (arr.length > 1) {
if (opts.optimize !== false) {
opts.optimize = true;
delete opts.expand;
}
if (opts.optimize !== true) {
var min = Math.min(arr[0], arr[1]);
var max = Math.max(arr[0], arr[1]);
var step = arr[2] || 1;
if (opts.rangeLimit !== false && ((max - min) / step >= opts.rangeLimit)) {
throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.');
}
}
arr.push(opts);
tok.segs = utils.fillRange.apply(null, arr);
if (!tok.segs.length) {
tok.escaped = true;
tok.val = str;
return tok;
}
if (opts.optimize === true) {
tok.segs = utils.stringifyArray(tok.segs);
}
if (tok.segs === '') {
tok.val = str;
} else {
tok.val = tok.segs[0];
}
return tok;
}
} else {
tok.val = str;
}
return tok;
};
/**
* Ensure commas inside brackets and parens are not split.
* @param {Object} `tok` Token from the `split-string` module
* @return {undefined}
*/
utils.escapeBrackets = function(options) {
return function(tok) {
if (tok.escaped && tok.val === 'b') {
tok.val = '\\b';
return;
}
if (tok.val !== '(' && tok.val !== '[') return;
var opts = utils.extend({}, options);
var brackets = [];
var parens = [];
var stack = [];
var val = tok.val;
var str = tok.str;
var i = tok.idx - 1;
while (++i < str.length) {
var ch = str[i];
if (ch === '\\') {
val += (opts.keepEscaping === false ? '' : ch) + str[++i];
continue;
}
if (ch === '(') {
parens.push(ch);
stack.push(ch);
}
if (ch === '[') {
brackets.push(ch);
stack.push(ch);
}
if (ch === ')') {
parens.pop();
stack.pop();
if (!stack.length) {
val += ch;
break;
}
}
if (ch === ']') {
brackets.pop();
stack.pop();
if (!stack.length) {
val += ch;
break;
}
}
val += ch;
}
tok.split = false;
tok.val = val.slice(1);
tok.idx = i;
};
};
/**
* Returns true if the given string looks like a regex quantifier
* @return {Boolean}
*/
utils.isQuantifier = function(str) {
return /^(?:[0-9]?,[0-9]|[0-9],)$/.test(str);
};
/**
* Cast `val` to an array.
* @param {*} `val`
*/
utils.stringifyArray = function(arr) {
return [utils.arrayify(arr).join('|')];
};
/**
* Cast `val` to an array.
* @param {*} `val`
*/
utils.arrayify = function(arr) {
if (typeof arr === 'undefined') {
return [];
}
if (typeof arr === 'string') {
return [arr];
}
return arr;
};
/**
* Returns true if the given `str` is a non-empty string
* @return {Boolean}
*/
utils.isString = function(str) {
return str != null && typeof str === 'string';
};
/**
* Get the last element from `array`
* @param {Array} `array`
* @return {*}
*/
utils.last = function(arr, n) {
return arr[arr.length - (n || 1)];
};
utils.escapeRegex = function(str) {
return str.replace(/\\?([!^*?()[\]{}+?/])/g, '\\$1');
};
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2015, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,61 @@
# extend-shallow [![NPM version](https://badge.fury.io/js/extend-shallow.svg)](http://badge.fury.io/js/extend-shallow) [![Build Status](https://travis-ci.org/jonschlinkert/extend-shallow.svg)](https://travis-ci.org/jonschlinkert/extend-shallow)
> Extend an object with the properties of additional objects. node.js/javascript util.
## Install
Install with [npm](https://www.npmjs.com/)
```sh
$ npm i extend-shallow --save
```
## Usage
```js
var extend = require('extend-shallow');
extend({a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
Pass an empty object to shallow clone:
```js
var obj = {};
extend(obj, {a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
## Related
* [extend-shallow](https://github.com/jonschlinkert/extend-shallow): Extend an object with the properties of additional objects. node.js/javascript util.
* [for-own](https://github.com/jonschlinkert/for-own): Iterate over the own enumerable properties of an object, and return an object with properties… [more](https://github.com/jonschlinkert/for-own)
* [for-in](https://github.com/jonschlinkert/for-in): Iterate over the own and inherited enumerable properties of an objecte, and return an object… [more](https://github.com/jonschlinkert/for-in)
* [is-plain-object](https://github.com/jonschlinkert/is-plain-object): Returns true if an object was created by the `Object` constructor.
* [isobject](https://github.com/jonschlinkert/isobject): Returns true if the value is an object and not an array or null.
* [kind-of](https://github.com/jonschlinkert/kind-of): Get the native type of a value.
## Running tests
Install dev dependencies:
```sh
$ npm i -d && npm test
```
## Author
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
## License
Copyright © 2015 Jon Schlinkert
Released under the MIT license.
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on June 29, 2015._
@@ -0,0 +1,33 @@
'use strict';
var isObject = require('is-extendable');
module.exports = function extend(o/*, objects*/) {
if (!isObject(o)) { o = {}; }
var len = arguments.length;
for (var i = 1; i < len; i++) {
var obj = arguments[i];
if (isObject(obj)) {
assign(o, obj);
}
}
return o;
};
function assign(a, b) {
for (var key in b) {
if (hasOwn(b, key)) {
a[key] = b[key];
}
}
}
/**
* Returns true if the given `key` is an own property of `obj`.
*/
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
@@ -0,0 +1,90 @@
{
"_args": [
[
"extend-shallow@2.0.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "extend-shallow@2.0.1",
"_id": "extend-shallow@2.0.1",
"_inBundle": false,
"_integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"_location": "/webpack/braces/extend-shallow",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "extend-shallow@2.0.1",
"name": "extend-shallow",
"escapedName": "extend-shallow",
"rawSpec": "2.0.1",
"saveSpec": null,
"fetchSpec": "2.0.1"
},
"_requiredBy": [
"/webpack/braces"
],
"_resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"_spec": "2.0.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/jonschlinkert/extend-shallow/issues"
},
"dependencies": {
"is-extendable": "^0.1.0"
},
"description": "Extend an object with the properties of additional objects. node.js/javascript util.",
"devDependencies": {
"array-slice": "^0.2.3",
"benchmarked": "^0.1.4",
"chalk": "^1.0.0",
"for-own": "^0.1.3",
"glob": "^5.0.12",
"is-plain-object": "^2.0.1",
"kind-of": "^2.0.0",
"minimist": "^1.1.1",
"mocha": "^2.2.5",
"should": "^7.0.1"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/jonschlinkert/extend-shallow",
"keywords": [
"assign",
"extend",
"javascript",
"js",
"keys",
"merge",
"obj",
"object",
"prop",
"properties",
"property",
"props",
"shallow",
"util",
"utility",
"utils",
"value"
],
"license": "MIT",
"main": "index.js",
"name": "extend-shallow",
"repository": {
"type": "git",
"url": "git+https://github.com/jonschlinkert/extend-shallow.git"
},
"scripts": {
"test": "mocha"
},
"version": "2.0.1"
}
+159
View File
@@ -0,0 +1,159 @@
{
"_args": [
[
"braces@2.3.2",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "braces@2.3.2",
"_id": "braces@2.3.2",
"_inBundle": false,
"_integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"_location": "/webpack/braces",
"_phantomChildren": {
"is-extendable": "0.1.1"
},
"_requested": {
"type": "version",
"registry": true,
"raw": "braces@2.3.2",
"name": "braces",
"escapedName": "braces",
"rawSpec": "2.3.2",
"saveSpec": null,
"fetchSpec": "2.3.2"
},
"_requiredBy": [
"/webpack/micromatch"
],
"_resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"_spec": "2.3.2",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/micromatch/braces/issues"
},
"contributors": [
{
"name": "Brian Woodward",
"url": "https://twitter.com/doowb"
},
{
"name": "Elan Shanker",
"url": "https://github.com/es128"
},
{
"name": "Eugene Sharygin",
"url": "https://github.com/eush77"
},
{
"name": "hemanth.hm",
"url": "http://h3manth.com"
},
{
"name": "Jon Schlinkert",
"url": "http://twitter.com/jonschlinkert"
}
],
"dependencies": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"description": "Bash-like brace expansion, implemented in JavaScript. Safer than other brace expansion libs, with complete support for the Bash 4.3 braces specification, without sacrificing speed.",
"devDependencies": {
"ansi-cyan": "^0.1.1",
"benchmarked": "^2.0.0",
"brace-expansion": "^1.1.8",
"cross-spawn": "^5.1.0",
"gulp": "^3.9.1",
"gulp-eslint": "^4.0.0",
"gulp-format-md": "^1.0.0",
"gulp-istanbul": "^1.1.2",
"gulp-mocha": "^3.0.1",
"gulp-unused": "^0.2.1",
"is-windows": "^1.0.1",
"minimatch": "^3.0.4",
"mocha": "^3.2.0",
"noncharacters": "^1.1.0",
"text-table": "^0.2.0",
"time-diff": "^0.3.1",
"yargs-parser": "^8.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js",
"lib"
],
"homepage": "https://github.com/micromatch/braces",
"keywords": [
"alpha",
"alphabetical",
"bash",
"brace",
"braces",
"expand",
"expansion",
"filepath",
"fill",
"fs",
"glob",
"globbing",
"letter",
"match",
"matches",
"matching",
"number",
"numerical",
"path",
"range",
"ranges",
"sh"
],
"license": "MIT",
"main": "index.js",
"name": "braces",
"repository": {
"type": "git",
"url": "git+https://github.com/micromatch/braces.git"
},
"scripts": {
"benchmark": "node benchmark",
"test": "mocha"
},
"verb": {
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"lint": {
"reflinks": true
},
"plugins": [
"gulp-format-md"
],
"related": {
"list": [
"expand-brackets",
"extglob",
"fill-range",
"micromatch",
"nanomatch"
]
}
},
"version": "2.3.2"
}
+661
View File
@@ -0,0 +1,661 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [12.0.4](https://github.com/npm/cacache/compare/v12.0.3...v12.0.4) (2020-03-24)
### [12.0.3](https://github.com/npm/cacache/compare/v12.0.2...v12.0.3) (2019-08-19)
### Bug Fixes
* do not chown if not running as root ([2d80af9](https://github.com/npm/cacache/commit/2d80af9))
### [12.0.2](https://github.com/npm/cacache/compare/v12.0.1...v12.0.2) (2019-07-19)
### [12.0.1](https://github.com/npm/cacache/compare/v12.0.0...v12.0.1) (2019-07-19)
* **deps** Abstracted out `lib/util/infer-owner.js` to
[@npmcli/infer-owner](https://www.npmjs.com/package/@npmcli/infer-owner)
so that it could be more easily used in other parts of the npm CLI.
## [12.0.0](https://github.com/npm/cacache/compare/v11.3.3...v12.0.0) (2019-07-15)
### Features
* infer uid/gid instead of accepting as options ([ac84d14](https://github.com/npm/cacache/commit/ac84d14))
* **i18n:** add another error message ([676cb32](https://github.com/npm/cacache/commit/676cb32))
### BREAKING CHANGES
* the uid gid options are no longer respected or
necessary. As of this change, cacache will always match the cache
contents to the ownership of the cache directory (or its parent
directory), regardless of what the caller passes in.
Reasoning:
The number one reason to use a uid or gid option was to keep root-owned
files from causing problems in the cache. In npm's case, this meant
that CLI's ./lib/command.js had to work out the appropriate uid and gid,
then pass it to the libnpmcommand module, which had to in turn pass the
uid and gid to npm-registry-fetch, which then passed it to
make-fetch-happen, which passed it to cacache. (For package fetching,
pacote would be in that mix as well.)
Added to that, `cacache.rm()` will actually _write_ a file into the
cache index, but has no way to accept an option so that its call to
entry-index.js will write the index with the appropriate uid/gid.
Little ownership bugs were all over the place, and tricky to trace
through. (Why should make-fetch-happen even care about accepting or
passing uids and gids? It's an http library.)
This change allows us to keep the cache from having mixed ownership in
any situation.
Of course, this _does_ mean that if you have a root-owned but
user-writable folder (for example, `/tmp`), then the cache will try to
chown everything to root.
The solution is for the user to create a folder, make it user-owned, and
use that, rather than relying on cacache to create the root cache folder.
If we decide to restore the uid/gid opts, and use ownership inferrence
only when uid/gid are unset, then take care to also make rm take an
option object, and pass it through to entry-index.js.
### [11.3.3](https://github.com/npm/cacache/compare/v11.3.2...v11.3.3) (2019-06-17)
### Bug Fixes
* **audit:** npm audit fix ([200a6d5](https://github.com/npm/cacache/commit/200a6d5))
* **config:** Add ssri config 'error' option ([#146](https://github.com/npm/cacache/issues/146)) ([47de8f5](https://github.com/npm/cacache/commit/47de8f5))
* **deps:** npm audit fix ([481a7dc](https://github.com/npm/cacache/commit/481a7dc))
* **standard:** standard --fix ([7799149](https://github.com/npm/cacache/commit/7799149))
* **write:** avoid another cb never called situation ([5156561](https://github.com/npm/cacache/commit/5156561))
<a name="11.3.2"></a>
## [11.3.2](https://github.com/npm/cacache/compare/v11.3.1...v11.3.2) (2018-12-21)
### Bug Fixes
* **get:** make sure to handle errors in the .then ([b10bcd0](https://github.com/npm/cacache/commit/b10bcd0))
<a name="11.3.1"></a>
## [11.3.1](https://github.com/npm/cacache/compare/v11.3.0...v11.3.1) (2018-11-05)
### Bug Fixes
* **get:** export hasContent.sync properly ([d76c920](https://github.com/npm/cacache/commit/d76c920))
<a name="11.3.0"></a>
# [11.3.0](https://github.com/npm/cacache/compare/v11.2.0...v11.3.0) (2018-11-05)
### Features
* **get:** add sync API for reading ([db1e094](https://github.com/npm/cacache/commit/db1e094))
<a name="11.2.0"></a>
# [11.2.0](https://github.com/npm/cacache/compare/v11.1.0...v11.2.0) (2018-08-08)
### Features
* **read:** add sync support to other internal read.js fns ([fe638b6](https://github.com/npm/cacache/commit/fe638b6))
<a name="11.1.0"></a>
# [11.1.0](https://github.com/npm/cacache/compare/v11.0.3...v11.1.0) (2018-08-01)
### Features
* **read:** add sync support for low-level content read ([b43af83](https://github.com/npm/cacache/commit/b43af83))
<a name="11.0.3"></a>
## [11.0.3](https://github.com/npm/cacache/compare/v11.0.2...v11.0.3) (2018-08-01)
### Bug Fixes
* **config:** add ssri config options ([#136](https://github.com/npm/cacache/issues/136)) ([10d5d9a](https://github.com/npm/cacache/commit/10d5d9a))
* **perf:** refactor content.read to avoid lstats ([c5ac10e](https://github.com/npm/cacache/commit/c5ac10e))
* **test:** oops when removing safe-buffer ([1950490](https://github.com/npm/cacache/commit/1950490))
<a name="11.0.2"></a>
## [11.0.2](https://github.com/npm/cacache/compare/v11.0.1...v11.0.2) (2018-05-07)
### Bug Fixes
* **verify:** size param no longer lost in a verify ([#131](https://github.com/npm/cacache/issues/131)) ([c614a19](https://github.com/npm/cacache/commit/c614a19)), closes [#130](https://github.com/npm/cacache/issues/130)
<a name="11.0.1"></a>
## [11.0.1](https://github.com/npm/cacache/compare/v11.0.0...v11.0.1) (2018-04-10)
<a name="11.0.0"></a>
# [11.0.0](https://github.com/npm/cacache/compare/v10.0.4...v11.0.0) (2018-04-09)
### Features
* **opts:** use figgy-pudding for opts ([#128](https://github.com/npm/cacache/issues/128)) ([33d4eed](https://github.com/npm/cacache/commit/33d4eed))
### meta
* drop support for node@4 ([529f347](https://github.com/npm/cacache/commit/529f347))
### BREAKING CHANGES
* node@4 is no longer supported
<a name="10.0.4"></a>
## [10.0.4](https://github.com/npm/cacache/compare/v10.0.3...v10.0.4) (2018-02-16)
<a name="10.0.3"></a>
## [10.0.3](https://github.com/npm/cacache/compare/v10.0.2...v10.0.3) (2018-02-16)
### Bug Fixes
* **content:** rethrow aggregate errors as ENOENT ([fa918f5](https://github.com/npm/cacache/commit/fa918f5))
<a name="10.0.2"></a>
## [10.0.2](https://github.com/npm/cacache/compare/v10.0.1...v10.0.2) (2018-01-07)
### Bug Fixes
* **ls:** deleted entries could cause a premature stream EOF ([347dc36](https://github.com/npm/cacache/commit/347dc36))
<a name="10.0.1"></a>
## [10.0.1](https://github.com/npm/cacache/compare/v10.0.0...v10.0.1) (2017-11-15)
### Bug Fixes
* **move-file:** actually use the fallback to `move-concurrently` (#110) ([073fbe1](https://github.com/npm/cacache/commit/073fbe1))
<a name="10.0.0"></a>
# [10.0.0](https://github.com/npm/cacache/compare/v9.3.0...v10.0.0) (2017-10-23)
### Features
* **license:** relicense to ISC (#111) ([fdbb4e5](https://github.com/npm/cacache/commit/fdbb4e5))
### Performance Improvements
* more copyFile benchmarks ([63787bb](https://github.com/npm/cacache/commit/63787bb))
### BREAKING CHANGES
* **license:** the license has been changed from CC0-1.0 to ISC.
<a name="9.3.0"></a>
# [9.3.0](https://github.com/npm/cacache/compare/v9.2.9...v9.3.0) (2017-10-07)
### Features
* **copy:** added cacache.get.copy api for fast copies (#107) ([067b5f6](https://github.com/npm/cacache/commit/067b5f6))
<a name="9.2.9"></a>
## [9.2.9](https://github.com/npm/cacache/compare/v9.2.8...v9.2.9) (2017-06-17)
<a name="9.2.8"></a>
## [9.2.8](https://github.com/npm/cacache/compare/v9.2.7...v9.2.8) (2017-06-05)
### Bug Fixes
* **ssri:** bump ssri for bugfix ([c3232ea](https://github.com/npm/cacache/commit/c3232ea))
<a name="9.2.7"></a>
## [9.2.7](https://github.com/npm/cacache/compare/v9.2.6...v9.2.7) (2017-06-05)
### Bug Fixes
* **content:** make verified content completely read-only (#96) ([4131196](https://github.com/npm/cacache/commit/4131196))
<a name="9.2.6"></a>
## [9.2.6](https://github.com/npm/cacache/compare/v9.2.5...v9.2.6) (2017-05-31)
### Bug Fixes
* **node:** update ssri to prevent old node 4 crash ([5209ffe](https://github.com/npm/cacache/commit/5209ffe))
<a name="9.2.5"></a>
## [9.2.5](https://github.com/npm/cacache/compare/v9.2.4...v9.2.5) (2017-05-25)
### Bug Fixes
* **deps:** fix lockfile issues and bump ssri ([84e1d7e](https://github.com/npm/cacache/commit/84e1d7e))
<a name="9.2.4"></a>
## [9.2.4](https://github.com/npm/cacache/compare/v9.2.3...v9.2.4) (2017-05-24)
### Bug Fixes
* **deps:** bumping deps ([bbccb12](https://github.com/npm/cacache/commit/bbccb12))
<a name="9.2.3"></a>
## [9.2.3](https://github.com/npm/cacache/compare/v9.2.2...v9.2.3) (2017-05-24)
### Bug Fixes
* **rm:** stop crashing if content is missing on rm ([ac90bc0](https://github.com/npm/cacache/commit/ac90bc0))
<a name="9.2.2"></a>
## [9.2.2](https://github.com/npm/cacache/compare/v9.2.1...v9.2.2) (2017-05-14)
### Bug Fixes
* **i18n:** lets pretend this didn't happen ([519b4ee](https://github.com/npm/cacache/commit/519b4ee))
<a name="9.2.1"></a>
## [9.2.1](https://github.com/npm/cacache/compare/v9.2.0...v9.2.1) (2017-05-14)
### Bug Fixes
* **docs:** fixing translation messup ([bb9e4f9](https://github.com/npm/cacache/commit/bb9e4f9))
<a name="9.2.0"></a>
# [9.2.0](https://github.com/npm/cacache/compare/v9.1.0...v9.2.0) (2017-05-14)
### Features
* **i18n:** add Spanish translation for API ([531f9a4](https://github.com/npm/cacache/commit/531f9a4))
<a name="9.1.0"></a>
# [9.1.0](https://github.com/npm/cacache/compare/v9.0.0...v9.1.0) (2017-05-14)
### Features
* **i18n:** Add Spanish translation and i18n setup (#91) ([323b90c](https://github.com/npm/cacache/commit/323b90c))
<a name="9.0.0"></a>
# [9.0.0](https://github.com/npm/cacache/compare/v8.0.0...v9.0.0) (2017-04-28)
### Bug Fixes
* **memoization:** actually use the LRU ([0e55dc9](https://github.com/npm/cacache/commit/0e55dc9))
### Features
* **memoization:** memoizers can be injected through opts.memoize (#90) ([e5614c7](https://github.com/npm/cacache/commit/e5614c7))
### BREAKING CHANGES
* **memoization:** If you were passing an object to opts.memoize, it will now be used as an injected memoization object. If you were only passing booleans and other non-objects through that option, no changes are needed.
<a name="8.0.0"></a>
# [8.0.0](https://github.com/npm/cacache/compare/v7.1.0...v8.0.0) (2017-04-22)
### Features
* **read:** change hasContent to return {sri, size} (#88) ([bad6c49](https://github.com/npm/cacache/commit/bad6c49)), closes [#87](https://github.com/npm/cacache/issues/87)
### BREAKING CHANGES
* **read:** hasContent now returns an object with `{sri, size}` instead of `sri`. Use `result.sri` anywhere that needed the old return value.
<a name="7.1.0"></a>
# [7.1.0](https://github.com/npm/cacache/compare/v7.0.5...v7.1.0) (2017-04-20)
### Features
* **size:** handle content size info (#49) ([91230af](https://github.com/npm/cacache/commit/91230af))
<a name="7.0.5"></a>
## [7.0.5](https://github.com/npm/cacache/compare/v7.0.4...v7.0.5) (2017-04-18)
### Bug Fixes
* **integrity:** new ssri with fixed integrity stream ([6d13e8e](https://github.com/npm/cacache/commit/6d13e8e))
* **write:** wrap stuff in promises to improve errors ([3624fc5](https://github.com/npm/cacache/commit/3624fc5))
<a name="7.0.4"></a>
## [7.0.4](https://github.com/npm/cacache/compare/v7.0.3...v7.0.4) (2017-04-15)
### Bug Fixes
* **fix-owner:** throw away ENOENTs on chownr ([d49bbcd](https://github.com/npm/cacache/commit/d49bbcd))
<a name="7.0.3"></a>
## [7.0.3](https://github.com/npm/cacache/compare/v7.0.2...v7.0.3) (2017-04-05)
### Bug Fixes
* **read:** fixing error message for integrity verification failures ([9d4f0a5](https://github.com/npm/cacache/commit/9d4f0a5))
<a name="7.0.2"></a>
## [7.0.2](https://github.com/npm/cacache/compare/v7.0.1...v7.0.2) (2017-04-03)
### Bug Fixes
* **integrity:** use EINTEGRITY error code and update ssri ([8dc2e62](https://github.com/npm/cacache/commit/8dc2e62))
<a name="7.0.1"></a>
## [7.0.1](https://github.com/npm/cacache/compare/v7.0.0...v7.0.1) (2017-04-03)
### Bug Fixes
* **docs:** fix header name conflict in readme ([afcd456](https://github.com/npm/cacache/commit/afcd456))
<a name="7.0.0"></a>
# [7.0.0](https://github.com/npm/cacache/compare/v6.3.0...v7.0.0) (2017-04-03)
### Bug Fixes
* **test:** fix content.write tests when running in docker ([d2e9b6a](https://github.com/npm/cacache/commit/d2e9b6a))
### Features
* **integrity:** subresource integrity support (#78) ([b1e731f](https://github.com/npm/cacache/commit/b1e731f))
### BREAKING CHANGES
* **integrity:** The entire API has been overhauled to use SRI hashes instead of digest/hashAlgorithm pairs. SRI hashes follow the Subresource Integrity standard and support strings and objects compatible with [`ssri`](https://npm.im/ssri).
* This change bumps the index version, which will invalidate all previous index entries. Content entries will remain intact, and existing caches will automatically reuse any content from before this breaking change.
* `cacache.get.info()`, `cacache.ls()`, and `cacache.ls.stream()` will now return objects that looks like this:
```
{
key: String,
integrity: '<algorithm>-<base64hash>',
path: ContentPath,
time: Date<ms>,
metadata: Any
}
```
* `opts.digest` and `opts.hashAlgorithm` are obsolete for any API calls that used them.
* Anywhere `opts.digest` was accepted, `opts.integrity` is now an option. Any valid SRI hash is accepted here -- multiple hash entries will be resolved according to the standard: first, the "strongest" hash algorithm will be picked, and then each of the entries for that algorithm will be matched against the content. Content will be validated if *any* of the entries match (so, a single integrity string can be used for multiple "versions" of the same document/data).
* `put.byDigest()`, `put.stream.byDigest`, `get.byDigest()` and `get.stream.byDigest()` now expect an SRI instead of a `digest` + `opts.hashAlgorithm` pairing.
* `get.hasContent()` now expects an integrity hash instead of a digest. If content exists, it will return the specific single integrity hash that was found in the cache.
* `verify()` has learned to handle integrity-based caches, and forgotten how to handle old-style cache indices due to the format change.
* `cacache.rm.content()` now expects an integrity hash instead of a hex digest.
<a name="6.3.0"></a>
# [6.3.0](https://github.com/npm/cacache/compare/v6.2.0...v6.3.0) (2017-04-01)
### Bug Fixes
* **fixOwner:** ignore EEXIST race condition from mkdirp ([4670e9b](https://github.com/npm/cacache/commit/4670e9b))
* **index:** ignore index removal races when inserting ([b9d2fa2](https://github.com/npm/cacache/commit/b9d2fa2))
* **memo:** use lru-cache for better mem management (#75) ([d8ac5aa](https://github.com/npm/cacache/commit/d8ac5aa))
### Features
* **dependencies:** Switch to move-concurrently (#77) ([dc6482d](https://github.com/npm/cacache/commit/dc6482d))
<a name="6.2.0"></a>
# [6.2.0](https://github.com/npm/cacache/compare/v6.1.2...v6.2.0) (2017-03-15)
### Bug Fixes
* **index:** additional bucket entry verification with checksum (#72) ([f8e0f25](https://github.com/npm/cacache/commit/f8e0f25))
* **verify:** return fixOwner.chownr promise ([6818521](https://github.com/npm/cacache/commit/6818521))
### Features
* **tmp:** safe tmp dir creation/management util (#73) ([c42da71](https://github.com/npm/cacache/commit/c42da71))
<a name="6.1.2"></a>
## [6.1.2](https://github.com/npm/cacache/compare/v6.1.1...v6.1.2) (2017-03-13)
### Bug Fixes
* **index:** set default hashAlgorithm ([d6eb2f0](https://github.com/npm/cacache/commit/d6eb2f0))
<a name="6.1.1"></a>
## [6.1.1](https://github.com/npm/cacache/compare/v6.1.0...v6.1.1) (2017-03-13)
### Bug Fixes
* **coverage:** bumping coverage for verify (#71) ([0b7faf6](https://github.com/npm/cacache/commit/0b7faf6))
* **deps:** glob should have been a regular dep :< ([0640bc4](https://github.com/npm/cacache/commit/0640bc4))
<a name="6.1.0"></a>
# [6.1.0](https://github.com/npm/cacache/compare/v6.0.2...v6.1.0) (2017-03-12)
### Bug Fixes
* **coverage:** more coverage for content reads (#70) ([ef4f70a](https://github.com/npm/cacache/commit/ef4f70a))
* **tests:** use safe-buffer because omfg (#69) ([6ab8132](https://github.com/npm/cacache/commit/6ab8132))
### Features
* **rm:** limited rm.all and fixed bugs (#66) ([d5d25ba](https://github.com/npm/cacache/commit/d5d25ba)), closes [#66](https://github.com/npm/cacache/issues/66)
* **verify:** tested, working cache verifier/gc (#68) ([45ad77a](https://github.com/npm/cacache/commit/45ad77a))
<a name="6.0.2"></a>
## [6.0.2](https://github.com/npm/cacache/compare/v6.0.1...v6.0.2) (2017-03-11)
### Bug Fixes
* **index:** segment cache items with another subbucket (#64) ([c3644e5](https://github.com/npm/cacache/commit/c3644e5))
<a name="6.0.1"></a>
## [6.0.1](https://github.com/npm/cacache/compare/v6.0.0...v6.0.1) (2017-03-05)
### Bug Fixes
* **docs:** Missed spots in README ([8ffb7fa](https://github.com/npm/cacache/commit/8ffb7fa))
<a name="6.0.0"></a>
# [6.0.0](https://github.com/npm/cacache/compare/v5.0.3...v6.0.0) (2017-03-05)
### Bug Fixes
* **api:** keep memo cache mostly-internal ([2f72d0a](https://github.com/npm/cacache/commit/2f72d0a))
* **content:** use the rest of the string, not the whole string ([fa8f3c3](https://github.com/npm/cacache/commit/fa8f3c3))
* **deps:** removed `format-number@2.0.2` ([1187791](https://github.com/npm/cacache/commit/1187791))
* **deps:** removed inflight@1.0.6 ([0d1819c](https://github.com/npm/cacache/commit/0d1819c))
* **deps:** rimraf@2.6.1 ([9efab6b](https://github.com/npm/cacache/commit/9efab6b))
* **deps:** standard@9.0.0 ([4202cba](https://github.com/npm/cacache/commit/4202cba))
* **deps:** tap@10.3.0 ([aa03088](https://github.com/npm/cacache/commit/aa03088))
* **deps:** weallcontribute@1.0.8 ([ad4f4dc](https://github.com/npm/cacache/commit/ad4f4dc))
* **docs:** add security note to hashKey ([03f81ba](https://github.com/npm/cacache/commit/03f81ba))
* **hashes:** change default hashAlgorithm to sha512 ([ea00ba6](https://github.com/npm/cacache/commit/ea00ba6))
* **hashes:** missed a spot for hashAlgorithm defaults ([45997d8](https://github.com/npm/cacache/commit/45997d8))
* **index:** add length header before JSON for verification ([fb8cb4d](https://github.com/npm/cacache/commit/fb8cb4d))
* **index:** change index filenames to sha1s of keys ([bbc5fca](https://github.com/npm/cacache/commit/bbc5fca))
* **index:** who cares about race conditions anyway ([b1d3888](https://github.com/npm/cacache/commit/b1d3888))
* **perf:** bulk-read get+read for massive speed ([d26cdf9](https://github.com/npm/cacache/commit/d26cdf9))
* **perf:** use bulk file reads for index reads ([79a8891](https://github.com/npm/cacache/commit/79a8891))
* **put-stream:** remove tmp file on stream insert error ([65f6632](https://github.com/npm/cacache/commit/65f6632))
* **put-stream:** robustified and predictibilized ([daf9e08](https://github.com/npm/cacache/commit/daf9e08))
* **put-stream:** use new promise API for moves ([1d36013](https://github.com/npm/cacache/commit/1d36013))
* **readme:** updated to reflect new default hashAlgo ([c60a2fa](https://github.com/npm/cacache/commit/c60a2fa))
* **verify:** tiny typo fix ([db22d05](https://github.com/npm/cacache/commit/db22d05))
### Features
* **api:** converted external api ([7bf032f](https://github.com/npm/cacache/commit/7bf032f))
* **cacache:** exported clearMemoized() utility ([8d2c5b6](https://github.com/npm/cacache/commit/8d2c5b6))
* **cache:** add versioning to content and index ([31bc549](https://github.com/npm/cacache/commit/31bc549))
* **content:** collate content files into subdirs ([c094d9f](https://github.com/npm/cacache/commit/c094d9f))
* **deps:** `@npmcorp/move@1.0.0` ([bdd00bf](https://github.com/npm/cacache/commit/bdd00bf))
* **deps:** `bluebird@3.4.7` ([3a17aff](https://github.com/npm/cacache/commit/3a17aff))
* **deps:** `promise-inflight@1.0.1` ([a004fe6](https://github.com/npm/cacache/commit/a004fe6))
* **get:** added memoization support for get ([c77d794](https://github.com/npm/cacache/commit/c77d794))
* **get:** export hasContent ([2956ec3](https://github.com/npm/cacache/commit/2956ec3))
* **index:** add hashAlgorithm and format insert ret val ([b639746](https://github.com/npm/cacache/commit/b639746))
* **index:** collate index files into subdirs ([e8402a5](https://github.com/npm/cacache/commit/e8402a5))
* **index:** promisify entry index ([cda3335](https://github.com/npm/cacache/commit/cda3335))
* **memo:** added memoization lib ([da07b92](https://github.com/npm/cacache/commit/da07b92))
* **memo:** export memoization api ([954b1b3](https://github.com/npm/cacache/commit/954b1b3))
* **move-file:** add move fallback for weird errors ([5cf4616](https://github.com/npm/cacache/commit/5cf4616))
* **perf:** bulk content write api ([51b536e](https://github.com/npm/cacache/commit/51b536e))
* **put:** added memoization support to put ([b613a70](https://github.com/npm/cacache/commit/b613a70))
* **read:** switched to promises ([a869362](https://github.com/npm/cacache/commit/a869362))
* **rm:** added memoization support to rm ([4205cf0](https://github.com/npm/cacache/commit/4205cf0))
* **rm:** switched to promises ([a000d24](https://github.com/npm/cacache/commit/a000d24))
* **util:** promise-inflight ownership fix requests ([9517cd7](https://github.com/npm/cacache/commit/9517cd7))
* **util:** use promises for api ([ae204bb](https://github.com/npm/cacache/commit/ae204bb))
* **verify:** converted to Promises ([f0b3974](https://github.com/npm/cacache/commit/f0b3974))
### BREAKING CHANGES
* cache: index/content directories are now versioned. Previous caches are no longer compatible and cannot be migrated.
* util: fix-owner now uses Promises instead of callbacks
* index: Previously-generated index entries are no longer compatible and the index must be regenerated.
* index: The index format has changed and previous caches are no longer compatible. Existing caches will need to be regenerated.
* hashes: Default hashAlgorithm changed from sha1 to sha512. If you
rely on the prior setting, pass `opts.hashAlgorithm` in explicitly.
* content: Previously-generated content directories are no longer compatible
and must be regenerated.
* verify: API is now promise-based
* read: Switches to a Promise-based API and removes callback stuff
* rm: Switches to a Promise-based API and removes callback stuff
* index: this changes the API to work off promises instead of callbacks
* api: this means we are going all in on promises now
+16
View File
@@ -0,0 +1,16 @@
ISC License
Copyright (c) npm, Inc.
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE COPYRIGHT HOLDER DISCLAIMS
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
USE OR PERFORMANCE OF THIS SOFTWARE.
+628
View File
@@ -0,0 +1,628 @@
# cacache [![npm version](https://img.shields.io/npm/v/cacache.svg)](https://npm.im/cacache) [![license](https://img.shields.io/npm/l/cacache.svg)](https://npm.im/cacache) [![Travis](https://img.shields.io/travis/zkat/cacache.svg)](https://travis-ci.org/zkat/cacache) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/zkat/cacache?svg=true)](https://ci.appveyor.com/project/zkat/cacache) [![Coverage Status](https://coveralls.io/repos/github/zkat/cacache/badge.svg?branch=latest)](https://coveralls.io/github/zkat/cacache?branch=latest)
[`cacache`](https://github.com/zkat/cacache) es una librería de Node.js para
manejar caches locales en disco, con acceso tanto con claves únicas como
direcciones de contenido (hashes/hacheos). Es súper rápida, excelente con el
acceso concurrente, y jamás te dará datos incorrectos, aún si se corrompen o
manipulan directamente los ficheros del cache.
El propósito original era reemplazar el caché local de
[npm](https://npm.im/npm), pero se puede usar por su propia cuenta.
_Traducciones: [English](README.md)_
## Instalación
`$ npm install --save cacache`
## Índice
* [Ejemplo](#ejemplo)
* [Características](#características)
* [Cómo Contribuir](#cómo-contribuir)
* [API](#api)
* [Usando el API en español](#localized-api)
* Leer
* [`ls`](#ls)
* [`ls.flujo`](#ls-stream)
* [`saca`](#get-data)
* [`saca.flujo`](#get-stream)
* [`saca.info`](#get-info)
* [`saca.tieneDatos`](#get-hasContent)
* Escribir
* [`mete`](#put-data)
* [`mete.flujo`](#put-stream)
* [opciones para `mete*`](#put-options)
* [`rm.todo`](#rm-all)
* [`rm.entrada`](#rm-entry)
* [`rm.datos`](#rm-content)
* Utilidades
* [`ponLenguaje`](#set-locale)
* [`limpiaMemoizado`](#clear-memoized)
* [`tmp.hazdir`](#tmp-mkdir)
* [`tmp.conTmp`](#with-tmp)
* Integridad
* [Subresource Integrity](#integrity)
* [`verifica`](#verify)
* [`verifica.ultimaVez`](#verify-last-run)
### Ejemplo
```javascript
const cacache = require('cacache/es')
const fs = require('fs')
const tarbol = '/ruta/a/mi-tar.tgz'
const rutaCache = '/tmp/my-toy-cache'
const clave = 'mi-clave-única-1234'
// ¡Añádelo al caché! Usa `rutaCache` como raíz del caché.
cacache.mete(rutaCache, clave, '10293801983029384').then(integrity => {
console.log(`Saved content to ${rutaCache}.`)
})
const destino = '/tmp/mytar.tgz'
// Copia el contenido del caché a otro fichero, pero esta vez con flujos.
cacache.saca.flujo(
rutaCache, clave
).pipe(
fs.createWriteStream(destino)
).on('finish', () => {
console.log('extracción completada')
})
// La misma cosa, pero accesando el contenido directamente, sin tocar el índice.
cacache.saca.porHacheo(rutaCache, integridad).then(datos => {
fs.writeFile(destino, datos, err => {
console.log('datos del tarbol sacados basado en su sha512, y escrito a otro fichero')
})
})
```
### Características
* Extracción por clave o por dirección de contenido (shasum, etc)
* Usa el estándard de web, [Subresource Integrity](#integrity)
* Compatible con multiples algoritmos - usa sha1, sha512, etc, en el mismo caché sin problema
* Entradas con contenido idéntico comparten ficheros
* Tolerancia de fallas (inmune a corrupción, ficheros parciales, carreras de proceso, etc)
* Verificación completa de datos cuando (escribiendo y leyendo)
* Concurrencia rápida, segura y "lockless"
* Compatible con `stream`s (flujos)
* Compatible con `Promise`s (promesas)
* Bastante rápida -- acceso, incluyendo verificación, en microsegundos
* Almacenaje de metadatos arbitrarios
* Colección de basura y verificación adicional fuera de banda
* Cobertura rigurosa de pruebas
* Probablente hay un "Bloom filter" por ahí en algún lado. Eso le mola a la gente, ¿Verdad? 🤔
### Cómo Contribuir
El equipo de cacache felizmente acepta contribuciones de código y otras maneras de participación. ¡Hay muchas formas diferentes de contribuir! La [Guía de Colaboradores](CONTRIBUTING.md) (en inglés) tiene toda la información que necesitas para cualquier tipo de contribución: todo desde cómo reportar errores hasta cómo someter parches con nuevas características. Con todo y eso, no se preocupe por si lo que haces está exáctamente correcto: no hay ningún problema en hacer preguntas si algo no está claro, o no lo encuentras.
El equipo de cacache tiene miembros hispanohablantes: es completamente aceptable crear `issues` y `pull requests` en español/castellano.
Todos los participantes en este proyecto deben obedecer el [Código de Conducta](CODE_OF_CONDUCT.md) (en inglés), y en general actuar de forma amable y respetuosa mientras participan en esta comunidad.
Por favor refiérase al [Historial de Cambios](CHANGELOG.md) (en inglés) para detalles sobre cambios importantes incluídos en cada versión.
Finalmente, cacache tiene un sistema de localización de lenguaje. Si te interesa añadir lenguajes o mejorar los que existen, mira en el directorio `./locales` para comenzar.
Happy hacking!
### API
#### <a name="localized-api"></a> Usando el API en español
cacache incluye una traducción completa de su API al castellano, con las mismas
características. Para usar el API como está documentado en este documento, usa
`require('cacache/es')`
cacache también tiene otros lenguajes: encuéntralos bajo `./locales`, y podrás
usar el API en ese lenguaje con `require('cacache/<lenguaje>')`
#### <a name="ls"></a> `> cacache.ls(cache) -> Promise<Object>`
Enumera todas las entradas en el caché, dentro de un solo objeto. Cada entrada
en el objeto tendrá como clave la clave única usada para el índice, el valor
siendo un objeto de [`saca.info`](#get-info).
##### Ejemplo
```javascript
cacache.ls(rutaCache).then(console.log)
// Salida
{
'my-thing': {
key: 'my-thing',
integrity: 'sha512-BaSe64/EnCoDED+HAsh=='
path: '.testcache/content/deadbeef', // unido con `rutaCache`
time: 12345698490,
size: 4023948,
metadata: {
name: 'blah',
version: '1.2.3',
description: 'this was once a package but now it is my-thing'
}
},
'other-thing': {
key: 'other-thing',
integrity: 'sha1-ANothER+hasH=',
path: '.testcache/content/bada55',
time: 11992309289,
size: 111112
}
}
```
#### <a name="ls-stream"></a> `> cacache.ls.flujo(cache) -> Readable`
Enumera todas las entradas en el caché, emitiendo un objeto de
[`saca.info`](#get-info) por cada evento de `data` en el flujo.
##### Ejemplo
```javascript
cacache.ls.flujo(rutaCache).on('data', console.log)
// Salida
{
key: 'my-thing',
integrity: 'sha512-BaSe64HaSh',
path: '.testcache/content/deadbeef', // unido con `rutaCache`
time: 12345698490,
size: 13423,
metadata: {
name: 'blah',
version: '1.2.3',
description: 'this was once a package but now it is my-thing'
}
}
{
key: 'other-thing',
integrity: 'whirlpool-WoWSoMuchSupport',
path: '.testcache/content/bada55',
time: 11992309289,
size: 498023984029
}
{
...
}
```
#### <a name="get-data"></a> `> cacache.saca(cache, clave, [ops]) -> Promise({data, metadata, integrity})`
Devuelve un objeto con los datos, hacheo de integridad y metadatos identificados
por la `clave`. La propiedad `data` de este objeto será una instancia de
`Buffer` con los datos almacenados en el caché. to do with it! cacache just
won't care.
`integrity` es un `string` de [Subresource Integrity](#integrity). Dígase, un
`string` que puede ser usado para verificar a la `data`, que tiene como formato
`<algoritmo>-<hacheo-integridad-base64>`.
So no existe ninguna entrada identificada por `clave`, o se los datos
almacenados localmente fallan verificación, el `Promise` fallará.
Una sub-función, `saca.porHacheo`, tiene casi el mismo comportamiento, excepto
que busca entradas usando el hacheo de integridad, sin tocar el índice general.
Esta versión *sólo* devuelve `data`, sin ningún objeto conteniéndola.
##### Nota
Esta función lee la entrada completa a la memoria antes de devolverla. Si estás
almacenando datos Muy Grandes, es posible que [`saca.flujo`](#get-stream) sea
una mejor solución.
##### Ejemplo
```javascript
// Busca por clave
cache.saca(rutaCache, 'my-thing').then(console.log)
// Salida:
{
metadata: {
thingName: 'my'
},
integrity: 'sha512-BaSe64HaSh',
data: Buffer#<deadbeef>,
size: 9320
}
// Busca por hacheo
cache.saca.porHacheo(rutaCache, 'sha512-BaSe64HaSh').then(console.log)
// Salida:
Buffer#<deadbeef>
```
#### <a name="get-stream"></a> `> cacache.saca.flujo(cache, clave, [ops]) -> Readable`
Devuelve un [Readable
Stream](https://nodejs.org/api/stream.html#stream_readable_streams) de los datos
almacenados bajo `clave`.
So no existe ninguna entrada identificada por `clave`, o se los datos
almacenados localmente fallan verificación, el `Promise` fallará.
`metadata` y `integrity` serán emitidos como eventos antes de que el flujo
cierre.
Una sub-función, `saca.flujo.porHacheo`, tiene casi el mismo comportamiento,
excepto que busca entradas usando el hacheo de integridad, sin tocar el índice
general. Esta versión no emite eventos de `metadata` o `integrity`.
##### Ejemplo
```javascript
// Busca por clave
cache.saca.flujo(
rutaCache, 'my-thing'
).on('metadata', metadata => {
console.log('metadata:', metadata)
}).on('integrity', integrity => {
console.log('integrity:', integrity)
}).pipe(
fs.createWriteStream('./x.tgz')
)
// Salidas:
metadata: { ... }
integrity: 'sha512-SoMeDIGest+64=='
// Busca por hacheo
cache.saca.flujo.porHacheo(
rutaCache, 'sha512-SoMeDIGest+64=='
).pipe(
fs.createWriteStream('./x.tgz')
)
```
#### <a name="get-info"></a> `> cacache.saca.info(cache, clave) -> Promise`
Busca la `clave` en el índice del caché, devolviendo información sobre la
entrada si existe.
##### Campos
* `key` - Clave de la entrada. Igual al argumento `clave`.
* `integrity` - [hacheo de Subresource Integrity](#integrity) del contenido al que se refiere esta entrada.
* `path` - Dirección del fichero de datos almacenados, unida al argumento `cache`.
* `time` - Hora de creación de la entrada
* `metadata` - Metadatos asignados a esta entrada por el usuario
##### Ejemplo
```javascript
cacache.saca.info(rutaCache, 'my-thing').then(console.log)
// Salida
{
key: 'my-thing',
integrity: 'sha256-MUSTVERIFY+ALL/THINGS=='
path: '.testcache/content/deadbeef',
time: 12345698490,
size: 849234,
metadata: {
name: 'blah',
version: '1.2.3',
description: 'this was once a package but now it is my-thing'
}
}
```
#### <a name="get-hasContent"></a> `> cacache.saca.tieneDatos(cache, integrity) -> Promise`
Busca un [hacheo Subresource Integrity](#integrity) en el caché. Si existe el
contenido asociado con `integrity`, devuelve un objeto con dos campos: el hacheo
_específico_ que se usó para la búsqueda, `sri`, y el tamaño total del
contenido, `size`. Si no existe ningún contenido asociado con `integrity`,
devuelve `false`.
##### Ejemplo
```javascript
cacache.saca.tieneDatos(rutaCache, 'sha256-MUSTVERIFY+ALL/THINGS==').then(console.log)
// Salida
{
sri: {
source: 'sha256-MUSTVERIFY+ALL/THINGS==',
algorithm: 'sha256',
digest: 'MUSTVERIFY+ALL/THINGS==',
options: []
},
size: 9001
}
cacache.saca.tieneDatos(rutaCache, 'sha521-NOT+IN/CACHE==').then(console.log)
// Salida
false
```
#### <a name="put-data"></a> `> cacache.mete(cache, clave, datos, [ops]) -> Promise`
Inserta `datos` en el caché. El `Promise` devuelto se resuelve con un hacheo
(generado conforme a [`ops.algorithms`](#optsalgorithms)) después que la entrada
haya sido escrita en completo.
##### Ejemplo
```javascript
fetch(
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
).then(datos => {
return cacache.mete(rutaCache, 'registry.npmjs.org|cacache@1.0.0', datos)
}).then(integridad => {
console.log('el hacheo de integridad es', integridad)
})
```
#### <a name="put-stream"></a> `> cacache.mete.flujo(cache, clave, [ops]) -> Writable`
Devuelve un [Writable
Stream](https://nodejs.org/api/stream.html#stream_writable_streams) que inserta
al caché los datos escritos a él. Emite un evento `integrity` con el hacheo del
contenido escrito, cuando completa.
##### Ejemplo
```javascript
request.get(
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
).pipe(
cacache.mete.flujo(
rutaCache, 'registry.npmjs.org|cacache@1.0.0'
).on('integrity', d => console.log(`integrity digest is ${d}`))
)
```
#### <a name="put-options"></a> `> opciones para cacache.mete`
La funciones `cacache.mete` tienen un número de opciones en común.
##### `ops.metadata`
Metadatos del usuario que se almacenarán con la entrada.
##### `ops.size`
El tamaño declarado de los datos que se van a insertar. Si es proveído, cacache
verificará que los datos escritos sean de ese tamaño, o si no, fallará con un
error con código `EBADSIZE`.
##### `ops.integrity`
El hacheo de integridad de los datos siendo escritos.
Si es proveído, y los datos escritos no le corresponden, la operación fallará
con un error con código `EINTEGRITY`.
`ops.algorithms` no tiene ningún efecto si esta opción está presente.
##### `ops.algorithms`
Por Defecto: `['sha512']`
Algoritmos que se deben usar cuando se calcule el hacheo de [subresource
integrity](#integrity) para los datos insertados. Puede usar cualquier algoritmo
enumerado en `crypto.getHashes()`.
Por el momento, sólo se acepta un algoritmo (dígase, un array con exáctamente un
valor). No tiene ningún efecto si `ops.integrity` también ha sido proveido.
##### `ops.uid`/`ops.gid`
Si están presentes, cacache hará todo lo posible para asegurarse que todos los
ficheros creados en el proceso de sus operaciones en el caché usen esta
combinación en particular.
##### `ops.memoize`
Por Defecto: `null`
Si es verdad, cacache tratará de memoizar los datos de la entrada en memoria. La
próxima vez que el proceso corriente trate de accesar los datos o entrada,
cacache buscará en memoria antes de buscar en disco.
Si `ops.memoize` es un objeto regular o un objeto como `Map` (es decir, un
objeto con métodos `get()` y `set()`), este objeto en sí sera usado en vez del
caché de memoria global. Esto permite tener lógica específica a tu aplicación
encuanto al almacenaje en memoria de tus datos.
Si quieres asegurarte que los datos se lean del disco en vez de memoria, usa
`memoize: false` cuando uses funciones de `cacache.saca`.
#### <a name="rm-all"></a> `> cacache.rm.todo(cache) -> Promise`
Borra el caché completo, incluyendo ficheros temporeros, ficheros de datos, y el
índice del caché.
##### Ejemplo
```javascript
cacache.rm.todo(rutaCache).then(() => {
console.log('THE APOCALYPSE IS UPON US 😱')
})
```
#### <a name="rm-entry"></a> `> cacache.rm.entrada(cache, clave) -> Promise`
Alias: `cacache.rm`
Borra la entrada `clave` del índuce. El contenido asociado con esta entrada
seguirá siendo accesible por hacheo usando
[`saca.flujo.porHacheo`](#get-stream).
Para borrar el contenido en sí, usa [`rm.datos`](#rm-content). Si quieres hacer
esto de manera más segura (pues ficheros de contenido pueden ser usados por
multiples entradas), usa [`verifica`](#verify) para borrar huérfanos.
##### Ejemplo
```javascript
cacache.rm.entrada(rutaCache, 'my-thing').then(() => {
console.log('I did not like it anyway')
})
```
#### <a name="rm-content"></a> `> cacache.rm.datos(cache, integrity) -> Promise`
Borra el contenido identificado por `integrity`. Cualquier entrada que se
refiera a este contenido quedarán huérfanas y se invalidarán si se tratan de
accesar, al menos que contenido idéntico sea añadido bajo `integrity`.
##### Ejemplo
```javascript
cacache.rm.datos(rutaCache, 'sha512-SoMeDIGest/IN+BaSE64==').then(() => {
console.log('los datos para `mi-cosa` se borraron')
})
```
#### <a name="set-locale"></a> `> cacache.ponLenguaje(locale)`
Configura el lenguaje usado para mensajes y errores de cacache. La lista de
lenguajes disponibles está en el directorio `./locales` del proyecto.
_Te interesa añadir más lenguajes? [Somete un PR](CONTRIBUTING.md)!_
#### <a name="clear-memoized"></a> `> cacache.limpiaMemoizado()`
Completamente reinicializa el caché de memoria interno. Si estás usando tu
propio objecto con `ops.memoize`, debes hacer esto de manera específica a él.
#### <a name="tmp-mkdir"></a> `> tmp.hazdir(cache, ops) -> Promise<Path>`
Alias: `tmp.mkdir`
Devuelve un directorio único dentro del directorio `tmp` del caché.
Una vez tengas el directorio, es responsabilidad tuya asegurarte que todos los
ficheros escrito a él sean creados usando los permisos y `uid`/`gid` concordante
con el caché. Si no, puedes pedirle a cacache que lo haga llamando a
[`cacache.tmp.fix()`](#tmp-fix). Esta función arreglará todos los permisos en el
directorio tmp.
Si quieres que cacache limpie el directorio automáticamente cuando termines, usa
[`cacache.tmp.conTmp()`](#with-tpm).
##### Ejemplo
```javascript
cacache.tmp.mkdir(cache).then(dir => {
fs.writeFile(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
})
```
#### <a name="with-tmp"></a> `> tmp.conTmp(cache, ops, cb) -> Promise`
Crea un directorio temporero con [`tmp.mkdir()`](#tmp-mkdir) y ejecuta `cb` con
él como primer argumento. El directorio creado será removido automáticamente
cuando el valor devolvido por `cb()` se resuelva.
Las mismas advertencias aplican en cuanto a manejando permisos para los ficheros
dentro del directorio.
##### Ejemplo
```javascript
cacache.tmp.conTmp(cache, dir => {
return fs.writeFileAsync(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
}).then(() => {
// `dir` no longer exists
})
```
#### <a name="integrity"></a> Hacheos de Subresource Integrity
cacache usa strings que siguen la especificación de [Subresource Integrity
spec](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
Es decir, donde quiera cacache espera un argumento o opción `integrity`, ese
string debería usar el formato `<algoritmo>-<hacheo-base64>`.
Una variación importante sobre los hacheos que cacache acepta es que acepta el
nombre de cualquier algoritmo aceptado por el proceso de Node.js donde se usa.
Puedes usar `crypto.getHashes()` para ver cuales están disponibles.
##### Generando tus propios hacheos
Si tienes un `shasum`, en general va a estar en formato de string hexadecimal
(es decir, un `sha1` se vería como algo así:
`5f5513f8822fdbe5145af33b64d8d970dcf95c6e`).
Para ser compatible con cacache, necesitas convertir esto a su equivalente en
subresource integrity. Por ejemplo, el hacheo correspondiente al ejemplo
anterior sería: `sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4=`.
Puedes usar código así para generarlo por tu cuenta:
```javascript
const crypto = require('crypto')
const algoritmo = 'sha512'
const datos = 'foobarbaz'
const integrity = (
algorithm +
'-' +
crypto.createHash(algoritmo).update(datos).digest('base64')
)
```
También puedes usar [`ssri`](https://npm.im/ssri) para deferir el trabajo a otra
librería que garantiza que todo esté correcto, pues maneja probablemente todas
las operaciones que tendrías que hacer con SRIs, incluyendo convirtiendo entre
hexadecimal y el formato SRI.
#### <a name="verify"></a> `> cacache.verifica(cache, ops) -> Promise`
Examina y arregla tu caché:
* Limpia entradas inválidas, huérfanas y corrompidas
* Te deja filtrar cuales entradas retener, con tu propio filtro
* Reclama cualquier ficheros de contenido sin referencias en el índice
* Verifica integridad de todos los ficheros de contenido y remueve los malos
* Arregla permisos del caché
* Remieve el directorio `tmp` en el caché, y todo su contenido.
Cuando termine, devuelve un objeto con varias estadísticas sobre el proceso de
verificación, por ejemplo la cantidad de espacio de disco reclamado, el número
de entradas válidas, número de entradas removidas, etc.
##### Opciones
* `ops.uid` - uid para asignarle al caché y su contenido
* `ops.gid` - gid para asignarle al caché y su contenido
* `ops.filter` - recibe una entrada como argumento. Devuelve falso para removerla. Nota: es posible que esta función sea invocada con la misma entrada más de una vez.
##### Example
```sh
echo somegarbage >> $RUTACACHE/content/deadbeef
```
```javascript
cacache.verifica(rutaCache).then(stats => {
// deadbeef collected, because of invalid checksum.
console.log('cache is much nicer now! stats:', stats)
})
```
#### <a name="verify-last-run"></a> `> cacache.verifica.ultimaVez(cache) -> Promise`
Alias: `últimaVez`
Devuelve un `Date` que representa la última vez que `cacache.verifica` fue
ejecutada en `cache`.
##### Example
```javascript
cacache.verifica(rutaCache).then(() => {
cacache.verifica.ultimaVez(rutaCache).then(última => {
console.log('La última vez que se usó cacache.verifica() fue ' + última)
})
})
```
+641
View File
@@ -0,0 +1,641 @@
# cacache [![npm version](https://img.shields.io/npm/v/cacache.svg)](https://npm.im/cacache) [![license](https://img.shields.io/npm/l/cacache.svg)](https://npm.im/cacache) [![Travis](https://img.shields.io/travis/npm/cacache.svg)](https://travis-ci.org/npm/cacache) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/npm/cacache?svg=true)](https://ci.appveyor.com/project/npm/cacache) [![Coverage Status](https://coveralls.io/repos/github/npm/cacache/badge.svg?branch=latest)](https://coveralls.io/github/npm/cacache?branch=latest)
[`cacache`](https://github.com/npm/cacache) is a Node.js library for managing
local key and content address caches. It's really fast, really good at
concurrency, and it will never give you corrupted data, even if cache files
get corrupted or manipulated.
On systems that support user and group settings on files, cacache will
match the `uid` and `gid` values to the folder where the cache lives, even
when running as `root`.
It was written to be used as [npm](https://npm.im)'s local cache, but can
just as easily be used on its own.
_Translations: [español](README.es.md)_
## Install
`$ npm install --save cacache`
## Table of Contents
* [Example](#example)
* [Features](#features)
* [Contributing](#contributing)
* [API](#api)
* [Using localized APIs](#localized-api)
* Reading
* [`ls`](#ls)
* [`ls.stream`](#ls-stream)
* [`get`](#get-data)
* [`get.stream`](#get-stream)
* [`get.info`](#get-info)
* [`get.hasContent`](#get-hasContent)
* Writing
* [`put`](#put-data)
* [`put.stream`](#put-stream)
* [`put*` opts](#put-options)
* [`rm.all`](#rm-all)
* [`rm.entry`](#rm-entry)
* [`rm.content`](#rm-content)
* Utilities
* [`setLocale`](#set-locale)
* [`clearMemoized`](#clear-memoized)
* [`tmp.mkdir`](#tmp-mkdir)
* [`tmp.withTmp`](#with-tmp)
* Integrity
* [Subresource Integrity](#integrity)
* [`verify`](#verify)
* [`verify.lastRun`](#verify-last-run)
### Example
```javascript
const cacache = require('cacache/en')
const fs = require('fs')
const tarball = '/path/to/mytar.tgz'
const cachePath = '/tmp/my-toy-cache'
const key = 'my-unique-key-1234'
// Cache it! Use `cachePath` as the root of the content cache
cacache.put(cachePath, key, '10293801983029384').then(integrity => {
console.log(`Saved content to ${cachePath}.`)
})
const destination = '/tmp/mytar.tgz'
// Copy the contents out of the cache and into their destination!
// But this time, use stream instead!
cacache.get.stream(
cachePath, key
).pipe(
fs.createWriteStream(destination)
).on('finish', () => {
console.log('done extracting!')
})
// The same thing, but skip the key index.
cacache.get.byDigest(cachePath, integrityHash).then(data => {
fs.writeFile(destination, data, err => {
console.log('tarball data fetched based on its sha512sum and written out!')
})
})
```
### Features
* Extraction by key or by content address (shasum, etc)
* [Subresource Integrity](#integrity) web standard support
* Multi-hash support - safely host sha1, sha512, etc, in a single cache
* Automatic content deduplication
* Fault tolerance (immune to corruption, partial writes, process races, etc)
* Consistency guarantees on read and write (full data verification)
* Lockless, high-concurrency cache access
* Streaming support
* Promise support
* Pretty darn fast -- sub-millisecond reads and writes including verification
* Arbitrary metadata storage
* Garbage collection and additional offline verification
* Thorough test coverage
* There's probably a bloom filter in there somewhere. Those are cool, right? 🤔
### Contributing
The cacache team enthusiastically welcomes contributions and project participation! There's a bunch of things you can do if you want to contribute! The [Contributor Guide](CONTRIBUTING.md) has all the information you need for everything from reporting bugs to contributing entire new features. Please don't hesitate to jump in if you'd like to, or even ask us questions if something isn't clear.
All participants and maintainers in this project are expected to follow [Code of Conduct](CODE_OF_CONDUCT.md), and just generally be excellent to each other.
Please refer to the [Changelog](CHANGELOG.md) for project history details, too.
Happy hacking!
### API
#### <a name="localized-api"></a> Using localized APIs
cacache includes a complete API in English, with the same features as other
translations. To use the English API as documented in this README, use
`require('cacache/en')`. This is also currently the default if you do
`require('cacache')`, but may change in the future.
cacache also supports other languages! You can find the list of currently
supported ones by looking in `./locales` in the source directory. You can use
the API in that language with `require('cacache/<lang>')`.
Want to add support for a new language? Please go ahead! You should be able to
copy `./locales/en.js` and `./locales/en.json` and fill them in. Translating the
`README.md` is a bit more work, but also appreciated if you get around to it. 👍🏼
#### <a name="ls"></a> `> cacache.ls(cache) -> Promise<Object>`
Lists info for all entries currently in the cache as a single large object. Each
entry in the object will be keyed by the unique index key, with corresponding
[`get.info`](#get-info) objects as the values.
##### Example
```javascript
cacache.ls(cachePath).then(console.log)
// Output
{
'my-thing': {
key: 'my-thing',
integrity: 'sha512-BaSe64/EnCoDED+HAsh=='
path: '.testcache/content/deadbeef', // joined with `cachePath`
time: 12345698490,
size: 4023948,
metadata: {
name: 'blah',
version: '1.2.3',
description: 'this was once a package but now it is my-thing'
}
},
'other-thing': {
key: 'other-thing',
integrity: 'sha1-ANothER+hasH=',
path: '.testcache/content/bada55',
time: 11992309289,
size: 111112
}
}
```
#### <a name="ls-stream"></a> `> cacache.ls.stream(cache) -> Readable`
Lists info for all entries currently in the cache as a single large object.
This works just like [`ls`](#ls), except [`get.info`](#get-info) entries are
returned as `'data'` events on the returned stream.
##### Example
```javascript
cacache.ls.stream(cachePath).on('data', console.log)
// Output
{
key: 'my-thing',
integrity: 'sha512-BaSe64HaSh',
path: '.testcache/content/deadbeef', // joined with `cachePath`
time: 12345698490,
size: 13423,
metadata: {
name: 'blah',
version: '1.2.3',
description: 'this was once a package but now it is my-thing'
}
}
{
key: 'other-thing',
integrity: 'whirlpool-WoWSoMuchSupport',
path: '.testcache/content/bada55',
time: 11992309289,
size: 498023984029
}
{
...
}
```
#### <a name="get-data"></a> `> cacache.get(cache, key, [opts]) -> Promise({data, metadata, integrity})`
Returns an object with the cached data, digest, and metadata identified by
`key`. The `data` property of this object will be a `Buffer` instance that
presumably holds some data that means something to you. I'm sure you know what
to do with it! cacache just won't care.
`integrity` is a [Subresource
Integrity](#integrity)
string. That is, a string that can be used to verify `data`, which looks like
`<hash-algorithm>-<base64-integrity-hash>`.
If there is no content identified by `key`, or if the locally-stored data does
not pass the validity checksum, the promise will be rejected.
A sub-function, `get.byDigest` may be used for identical behavior, except lookup
will happen by integrity hash, bypassing the index entirely. This version of the
function *only* returns `data` itself, without any wrapper.
##### Note
This function loads the entire cache entry into memory before returning it. If
you're dealing with Very Large data, consider using [`get.stream`](#get-stream)
instead.
##### Example
```javascript
// Look up by key
cache.get(cachePath, 'my-thing').then(console.log)
// Output:
{
metadata: {
thingName: 'my'
},
integrity: 'sha512-BaSe64HaSh',
data: Buffer#<deadbeef>,
size: 9320
}
// Look up by digest
cache.get.byDigest(cachePath, 'sha512-BaSe64HaSh').then(console.log)
// Output:
Buffer#<deadbeef>
```
#### <a name="get-stream"></a> `> cacache.get.stream(cache, key, [opts]) -> Readable`
Returns a [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams) of the cached data identified by `key`.
If there is no content identified by `key`, or if the locally-stored data does
not pass the validity checksum, an error will be emitted.
`metadata` and `integrity` events will be emitted before the stream closes, if
you need to collect that extra data about the cached entry.
A sub-function, `get.stream.byDigest` may be used for identical behavior,
except lookup will happen by integrity hash, bypassing the index entirely. This
version does not emit the `metadata` and `integrity` events at all.
##### Example
```javascript
// Look up by key
cache.get.stream(
cachePath, 'my-thing'
).on('metadata', metadata => {
console.log('metadata:', metadata)
}).on('integrity', integrity => {
console.log('integrity:', integrity)
}).pipe(
fs.createWriteStream('./x.tgz')
)
// Outputs:
metadata: { ... }
integrity: 'sha512-SoMeDIGest+64=='
// Look up by digest
cache.get.stream.byDigest(
cachePath, 'sha512-SoMeDIGest+64=='
).pipe(
fs.createWriteStream('./x.tgz')
)
```
#### <a name="get-info"></a> `> cacache.get.info(cache, key) -> Promise`
Looks up `key` in the cache index, returning information about the entry if
one exists.
##### Fields
* `key` - Key the entry was looked up under. Matches the `key` argument.
* `integrity` - [Subresource Integrity hash](#integrity) for the content this entry refers to.
* `path` - Filesystem path where content is stored, joined with `cache` argument.
* `time` - Timestamp the entry was first added on.
* `metadata` - User-assigned metadata associated with the entry/content.
##### Example
```javascript
cacache.get.info(cachePath, 'my-thing').then(console.log)
// Output
{
key: 'my-thing',
integrity: 'sha256-MUSTVERIFY+ALL/THINGS=='
path: '.testcache/content/deadbeef',
time: 12345698490,
size: 849234,
metadata: {
name: 'blah',
version: '1.2.3',
description: 'this was once a package but now it is my-thing'
}
}
```
#### <a name="get-hasContent"></a> `> cacache.get.hasContent(cache, integrity) -> Promise`
Looks up a [Subresource Integrity hash](#integrity) in the cache. If content
exists for this `integrity`, it will return an object, with the specific single integrity hash
that was found in `sri` key, and the size of the found content as `size`. If no content exists for this integrity, it will return `false`.
##### Example
```javascript
cacache.get.hasContent(cachePath, 'sha256-MUSTVERIFY+ALL/THINGS==').then(console.log)
// Output
{
sri: {
source: 'sha256-MUSTVERIFY+ALL/THINGS==',
algorithm: 'sha256',
digest: 'MUSTVERIFY+ALL/THINGS==',
options: []
},
size: 9001
}
cacache.get.hasContent(cachePath, 'sha521-NOT+IN/CACHE==').then(console.log)
// Output
false
```
#### <a name="put-data"></a> `> cacache.put(cache, key, data, [opts]) -> Promise`
Inserts data passed to it into the cache. The returned Promise resolves with a
digest (generated according to [`opts.algorithms`](#optsalgorithms)) after the
cache entry has been successfully written.
##### Example
```javascript
fetch(
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
).then(data => {
return cacache.put(cachePath, 'registry.npmjs.org|cacache@1.0.0', data)
}).then(integrity => {
console.log('integrity hash is', integrity)
})
```
#### <a name="put-stream"></a> `> cacache.put.stream(cache, key, [opts]) -> Writable`
Returns a [Writable
Stream](https://nodejs.org/api/stream.html#stream_writable_streams) that inserts
data written to it into the cache. Emits an `integrity` event with the digest of
written contents when it succeeds.
##### Example
```javascript
request.get(
'https://registry.npmjs.org/cacache/-/cacache-1.0.0.tgz'
).pipe(
cacache.put.stream(
cachePath, 'registry.npmjs.org|cacache@1.0.0'
).on('integrity', d => console.log(`integrity digest is ${d}`))
)
```
#### <a name="put-options"></a> `> cacache.put options`
`cacache.put` functions have a number of options in common.
##### `opts.metadata`
Arbitrary metadata to be attached to the inserted key.
##### `opts.size`
If provided, the data stream will be verified to check that enough data was
passed through. If there's more or less data than expected, insertion will fail
with an `EBADSIZE` error.
##### `opts.integrity`
If present, the pre-calculated digest for the inserted content. If this option
if provided and does not match the post-insertion digest, insertion will fail
with an `EINTEGRITY` error.
`algorithms` has no effect if this option is present.
##### `opts.algorithms`
Default: ['sha512']
Hashing algorithms to use when calculating the [subresource integrity
digest](#integrity)
for inserted data. Can use any algorithm listed in `crypto.getHashes()` or
`'omakase'`/`'お任せします'` to pick a random hash algorithm on each insertion. You
may also use any anagram of `'modnar'` to use this feature.
Currently only supports one algorithm at a time (i.e., an array length of
exactly `1`). Has no effect if `opts.integrity` is present.
##### `opts.memoize`
Default: null
If provided, cacache will memoize the given cache insertion in memory, bypassing
any filesystem checks for that key or digest in future cache fetches. Nothing
will be written to the in-memory cache unless this option is explicitly truthy.
If `opts.memoize` is an object or a `Map`-like (that is, an object with `get`
and `set` methods), it will be written to instead of the global memoization
cache.
Reading from disk data can be forced by explicitly passing `memoize: false` to
the reader functions, but their default will be to read from memory.
#### <a name="rm-all"></a> `> cacache.rm.all(cache) -> Promise`
Clears the entire cache. Mainly by blowing away the cache directory itself.
##### Example
```javascript
cacache.rm.all(cachePath).then(() => {
console.log('THE APOCALYPSE IS UPON US 😱')
})
```
#### <a name="rm-entry"></a> `> cacache.rm.entry(cache, key) -> Promise`
Alias: `cacache.rm`
Removes the index entry for `key`. Content will still be accessible if
requested directly by content address ([`get.stream.byDigest`](#get-stream)).
To remove the content itself (which might still be used by other entries), use
[`rm.content`](#rm-content). Or, to safely vacuum any unused content, use
[`verify`](#verify).
##### Example
```javascript
cacache.rm.entry(cachePath, 'my-thing').then(() => {
console.log('I did not like it anyway')
})
```
#### <a name="rm-content"></a> `> cacache.rm.content(cache, integrity) -> Promise`
Removes the content identified by `integrity`. Any index entries referring to it
will not be usable again until the content is re-added to the cache with an
identical digest.
##### Example
```javascript
cacache.rm.content(cachePath, 'sha512-SoMeDIGest/IN+BaSE64==').then(() => {
console.log('data for my-thing is gone!')
})
```
#### <a name="set-locale"></a> `> cacache.setLocale(locale)`
Configure the language/locale used for messages and errors coming from cacache.
The list of available locales is in the `./locales` directory in the project
root.
_Interested in contributing more languages! [Submit a PR](CONTRIBUTING.md)!_
#### <a name="clear-memoized"></a> `> cacache.clearMemoized()`
Completely resets the in-memory entry cache.
#### <a name="tmp-mkdir"></a> `> tmp.mkdir(cache, opts) -> Promise<Path>`
Returns a unique temporary directory inside the cache's `tmp` dir. This
directory will use the same safe user assignment that all the other stuff use.
Once the directory is made, it's the user's responsibility that all files
within are given the appropriate `gid`/`uid` ownership settings to match
the rest of the cache. If not, you can ask cacache to do it for you by
calling [`tmp.fix()`](#tmp-fix), which will fix all tmp directory
permissions.
If you want automatic cleanup of this directory, use
[`tmp.withTmp()`](#with-tpm)
##### Example
```javascript
cacache.tmp.mkdir(cache).then(dir => {
fs.writeFile(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
})
```
#### <a name="tmp-fix"></a> `> tmp.fix(cache) -> Promise`
Sets the `uid` and `gid` properties on all files and folders within the tmp
folder to match the rest of the cache.
Use this after manually writing files into [`tmp.mkdir`](#tmp-mkdir) or
[`tmp.withTmp`](#with-tmp).
##### Example
```javascript
cacache.tmp.mkdir(cache).then(dir => {
writeFile(path.join(dir, 'file'), someData).then(() => {
// make sure we didn't just put a root-owned file in the cache
cacache.tmp.fix().then(() => {
// all uids and gids match now
})
})
})
```
#### <a name="with-tmp"></a> `> tmp.withTmp(cache, opts, cb) -> Promise`
Creates a temporary directory with [`tmp.mkdir()`](#tmp-mkdir) and calls `cb`
with it. The created temporary directory will be removed when the return value
of `cb()` resolves -- that is, if you return a Promise from `cb()`, the tmp
directory will be automatically deleted once that promise completes.
The same caveats apply when it comes to managing permissions for the tmp dir's
contents.
##### Example
```javascript
cacache.tmp.withTmp(cache, dir => {
return fs.writeFileAsync(path.join(dir, 'blablabla'), Buffer#<1234>, ...)
}).then(() => {
// `dir` no longer exists
})
```
#### <a name="integrity"></a> Subresource Integrity Digests
For content verification and addressing, cacache uses strings following the
[Subresource
Integrity spec](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity).
That is, any time cacache expects an `integrity` argument or option, it
should be in the format `<hashAlgorithm>-<base64-hash>`.
One deviation from the current spec is that cacache will support any hash
algorithms supported by the underlying Node.js process. You can use
`crypto.getHashes()` to see which ones you can use.
##### Generating Digests Yourself
If you have an existing content shasum, they are generally formatted as a
hexadecimal string (that is, a sha1 would look like:
`5f5513f8822fdbe5145af33b64d8d970dcf95c6e`). In order to be compatible with
cacache, you'll need to convert this to an equivalent subresource integrity
string. For this example, the corresponding hash would be:
`sha1-X1UT+IIv2+UUWvM7ZNjZcNz5XG4=`.
If you want to generate an integrity string yourself for existing data, you can
use something like this:
```javascript
const crypto = require('crypto')
const hashAlgorithm = 'sha512'
const data = 'foobarbaz'
const integrity = (
hashAlgorithm +
'-' +
crypto.createHash(hashAlgorithm).update(data).digest('base64')
)
```
You can also use [`ssri`](https://npm.im/ssri) to have a richer set of functionality
around SRI strings, including generation, parsing, and translating from existing
hex-formatted strings.
#### <a name="verify"></a> `> cacache.verify(cache, opts) -> Promise`
Checks out and fixes up your cache:
* Cleans up corrupted or invalid index entries.
* Custom entry filtering options.
* Garbage collects any content entries not referenced by the index.
* Checks integrity for all content entries and removes invalid content.
* Fixes cache ownership.
* Removes the `tmp` directory in the cache and all its contents.
When it's done, it'll return an object with various stats about the verification
process, including amount of storage reclaimed, number of valid entries, number
of entries removed, etc.
##### Options
* `opts.filter` - receives a formatted entry. Return false to remove it.
Note: might be called more than once on the same entry.
##### Example
```sh
echo somegarbage >> $CACHEPATH/content/deadbeef
```
```javascript
cacache.verify(cachePath).then(stats => {
// deadbeef collected, because of invalid checksum.
console.log('cache is much nicer now! stats:', stats)
})
```
#### <a name="verify-last-run"></a> `> cacache.verify.lastRun(cache) -> Promise`
Returns a `Date` representing the last time `cacache.verify` was run on `cache`.
##### Example
```javascript
cacache.verify(cachePath).then(() => {
cacache.verify.lastRun(cachePath).then(lastTime => {
console.log('cacache.verify was last called on' + lastTime)
})
})
```
+3
View File
@@ -0,0 +1,3 @@
'use strict'
module.exports = require('./locales/en.js')
+3
View File
@@ -0,0 +1,3 @@
'use strict'
module.exports = require('./locales/es.js')
+247
View File
@@ -0,0 +1,247 @@
'use strict'
const BB = require('bluebird')
const figgyPudding = require('figgy-pudding')
const fs = require('fs')
const index = require('./lib/entry-index')
const memo = require('./lib/memoization')
const pipe = require('mississippi').pipe
const pipeline = require('mississippi').pipeline
const read = require('./lib/content/read')
const through = require('mississippi').through
const GetOpts = figgyPudding({
integrity: {},
memoize: {},
size: {}
})
module.exports = function get (cache, key, opts) {
return getData(false, cache, key, opts)
}
module.exports.byDigest = function getByDigest (cache, digest, opts) {
return getData(true, cache, digest, opts)
}
function getData (byDigest, cache, key, opts) {
opts = GetOpts(opts)
const memoized = (
byDigest
? memo.get.byDigest(cache, key, opts)
: memo.get(cache, key, opts)
)
if (memoized && opts.memoize !== false) {
return BB.resolve(byDigest ? memoized : {
metadata: memoized.entry.metadata,
data: memoized.data,
integrity: memoized.entry.integrity,
size: memoized.entry.size
})
}
return (
byDigest ? BB.resolve(null) : index.find(cache, key, opts)
).then(entry => {
if (!entry && !byDigest) {
throw new index.NotFoundError(cache, key)
}
return read(cache, byDigest ? key : entry.integrity, {
integrity: opts.integrity,
size: opts.size
}).then(data => byDigest ? data : {
metadata: entry.metadata,
data: data,
size: entry.size,
integrity: entry.integrity
}).then(res => {
if (opts.memoize && byDigest) {
memo.put.byDigest(cache, key, res, opts)
} else if (opts.memoize) {
memo.put(cache, entry, res.data, opts)
}
return res
})
})
}
module.exports.sync = function get (cache, key, opts) {
return getDataSync(false, cache, key, opts)
}
module.exports.sync.byDigest = function getByDigest (cache, digest, opts) {
return getDataSync(true, cache, digest, opts)
}
function getDataSync (byDigest, cache, key, opts) {
opts = GetOpts(opts)
const memoized = (
byDigest
? memo.get.byDigest(cache, key, opts)
: memo.get(cache, key, opts)
)
if (memoized && opts.memoize !== false) {
return byDigest ? memoized : {
metadata: memoized.entry.metadata,
data: memoized.data,
integrity: memoized.entry.integrity,
size: memoized.entry.size
}
}
const entry = !byDigest && index.find.sync(cache, key, opts)
if (!entry && !byDigest) {
throw new index.NotFoundError(cache, key)
}
const data = read.sync(
cache,
byDigest ? key : entry.integrity,
{
integrity: opts.integrity,
size: opts.size
}
)
const res = byDigest
? data
: {
metadata: entry.metadata,
data: data,
size: entry.size,
integrity: entry.integrity
}
if (opts.memoize && byDigest) {
memo.put.byDigest(cache, key, res, opts)
} else if (opts.memoize) {
memo.put(cache, entry, res.data, opts)
}
return res
}
module.exports.stream = getStream
function getStream (cache, key, opts) {
opts = GetOpts(opts)
let stream = through()
const memoized = memo.get(cache, key, opts)
if (memoized && opts.memoize !== false) {
stream.on('newListener', function (ev, cb) {
ev === 'metadata' && cb(memoized.entry.metadata)
ev === 'integrity' && cb(memoized.entry.integrity)
ev === 'size' && cb(memoized.entry.size)
})
stream.write(memoized.data, () => stream.end())
return stream
}
index.find(cache, key).then(entry => {
if (!entry) {
return stream.emit(
'error', new index.NotFoundError(cache, key)
)
}
let memoStream
if (opts.memoize) {
let memoData = []
let memoLength = 0
memoStream = through((c, en, cb) => {
memoData && memoData.push(c)
memoLength += c.length
cb(null, c, en)
}, cb => {
memoData && memo.put(cache, entry, Buffer.concat(memoData, memoLength), opts)
cb()
})
} else {
memoStream = through()
}
stream.emit('metadata', entry.metadata)
stream.emit('integrity', entry.integrity)
stream.emit('size', entry.size)
stream.on('newListener', function (ev, cb) {
ev === 'metadata' && cb(entry.metadata)
ev === 'integrity' && cb(entry.integrity)
ev === 'size' && cb(entry.size)
})
pipe(
read.readStream(cache, entry.integrity, opts.concat({
size: opts.size == null ? entry.size : opts.size
})),
memoStream,
stream
)
}).catch(err => stream.emit('error', err))
return stream
}
module.exports.stream.byDigest = getStreamDigest
function getStreamDigest (cache, integrity, opts) {
opts = GetOpts(opts)
const memoized = memo.get.byDigest(cache, integrity, opts)
if (memoized && opts.memoize !== false) {
const stream = through()
stream.write(memoized, () => stream.end())
return stream
} else {
let stream = read.readStream(cache, integrity, opts)
if (opts.memoize) {
let memoData = []
let memoLength = 0
const memoStream = through((c, en, cb) => {
memoData && memoData.push(c)
memoLength += c.length
cb(null, c, en)
}, cb => {
memoData && memo.put.byDigest(
cache,
integrity,
Buffer.concat(memoData, memoLength),
opts
)
cb()
})
stream = pipeline(stream, memoStream)
}
return stream
}
}
module.exports.info = info
function info (cache, key, opts) {
opts = GetOpts(opts)
const memoized = memo.get(cache, key, opts)
if (memoized && opts.memoize !== false) {
return BB.resolve(memoized.entry)
} else {
return index.find(cache, key)
}
}
module.exports.hasContent = read.hasContent
module.exports.copy = function cp (cache, key, dest, opts) {
return copy(false, cache, key, dest, opts)
}
module.exports.copy.byDigest = function cpDigest (cache, digest, dest, opts) {
return copy(true, cache, digest, dest, opts)
}
function copy (byDigest, cache, key, dest, opts) {
opts = GetOpts(opts)
if (read.copy) {
return (
byDigest ? BB.resolve(null) : index.find(cache, key, opts)
).then(entry => {
if (!entry && !byDigest) {
throw new index.NotFoundError(cache, key)
}
return read.copy(
cache, byDigest ? key : entry.integrity, dest, opts
).then(() => byDigest ? key : {
metadata: entry.metadata,
size: entry.size,
integrity: entry.integrity
})
})
} else {
return getData(byDigest, cache, key, opts).then(res => {
return fs.writeFileAsync(dest, byDigest ? res : res.data)
.then(() => byDigest ? key : {
metadata: res.metadata,
size: res.size,
integrity: res.integrity
})
})
}
}
+3
View File
@@ -0,0 +1,3 @@
'use strict'
module.exports = require('./locales/en.js')
+26
View File
@@ -0,0 +1,26 @@
'use strict'
const contentVer = require('../../package.json')['cache-version'].content
const hashToSegments = require('../util/hash-to-segments')
const path = require('path')
const ssri = require('ssri')
// Current format of content file path:
//
// sha512-BaSE64Hex= ->
// ~/.my-cache/content-v2/sha512/ba/da/55deadbeefc0ffee
//
module.exports = contentPath
function contentPath (cache, integrity) {
const sri = ssri.parse(integrity, { single: true })
// contentPath is the *strongest* algo given
return path.join.apply(path, [
contentDir(cache),
sri.algorithm
].concat(hashToSegments(sri.hexDigest())))
}
module.exports._contentDir = contentDir
function contentDir (cache) {
return path.join(cache, `content-v${contentVer}`)
}
+195
View File
@@ -0,0 +1,195 @@
'use strict'
const BB = require('bluebird')
const contentPath = require('./path')
const figgyPudding = require('figgy-pudding')
const fs = require('graceful-fs')
const PassThrough = require('stream').PassThrough
const pipe = BB.promisify(require('mississippi').pipe)
const ssri = require('ssri')
const Y = require('../util/y.js')
const lstatAsync = BB.promisify(fs.lstat)
const readFileAsync = BB.promisify(fs.readFile)
const ReadOpts = figgyPudding({
size: {}
})
module.exports = read
function read (cache, integrity, opts) {
opts = ReadOpts(opts)
return withContentSri(cache, integrity, (cpath, sri) => {
return readFileAsync(cpath, null).then(data => {
if (typeof opts.size === 'number' && opts.size !== data.length) {
throw sizeError(opts.size, data.length)
} else if (ssri.checkData(data, sri)) {
return data
} else {
throw integrityError(sri, cpath)
}
})
})
}
module.exports.sync = readSync
function readSync (cache, integrity, opts) {
opts = ReadOpts(opts)
return withContentSriSync(cache, integrity, (cpath, sri) => {
const data = fs.readFileSync(cpath)
if (typeof opts.size === 'number' && opts.size !== data.length) {
throw sizeError(opts.size, data.length)
} else if (ssri.checkData(data, sri)) {
return data
} else {
throw integrityError(sri, cpath)
}
})
}
module.exports.stream = readStream
module.exports.readStream = readStream
function readStream (cache, integrity, opts) {
opts = ReadOpts(opts)
const stream = new PassThrough()
withContentSri(cache, integrity, (cpath, sri) => {
return lstatAsync(cpath).then(stat => ({ cpath, sri, stat }))
}).then(({ cpath, sri, stat }) => {
return pipe(
fs.createReadStream(cpath),
ssri.integrityStream({
integrity: sri,
size: opts.size
}),
stream
)
}).catch(err => {
stream.emit('error', err)
})
return stream
}
let copyFileAsync
if (fs.copyFile) {
module.exports.copy = copy
module.exports.copy.sync = copySync
copyFileAsync = BB.promisify(fs.copyFile)
}
function copy (cache, integrity, dest, opts) {
opts = ReadOpts(opts)
return withContentSri(cache, integrity, (cpath, sri) => {
return copyFileAsync(cpath, dest)
})
}
function copySync (cache, integrity, dest, opts) {
opts = ReadOpts(opts)
return withContentSriSync(cache, integrity, (cpath, sri) => {
return fs.copyFileSync(cpath, dest)
})
}
module.exports.hasContent = hasContent
function hasContent (cache, integrity) {
if (!integrity) { return BB.resolve(false) }
return withContentSri(cache, integrity, (cpath, sri) => {
return lstatAsync(cpath).then(stat => ({ size: stat.size, sri, stat }))
}).catch(err => {
if (err.code === 'ENOENT') { return false }
if (err.code === 'EPERM') {
if (process.platform !== 'win32') {
throw err
} else {
return false
}
}
})
}
module.exports.hasContent.sync = hasContentSync
function hasContentSync (cache, integrity) {
if (!integrity) { return false }
return withContentSriSync(cache, integrity, (cpath, sri) => {
try {
const stat = fs.lstatSync(cpath)
return { size: stat.size, sri, stat }
} catch (err) {
if (err.code === 'ENOENT') { return false }
if (err.code === 'EPERM') {
if (process.platform !== 'win32') {
throw err
} else {
return false
}
}
}
})
}
function withContentSri (cache, integrity, fn) {
return BB.try(() => {
const sri = ssri.parse(integrity)
// If `integrity` has multiple entries, pick the first digest
// with available local data.
const algo = sri.pickAlgorithm()
const digests = sri[algo]
if (digests.length <= 1) {
const cpath = contentPath(cache, digests[0])
return fn(cpath, digests[0])
} else {
return BB.any(sri[sri.pickAlgorithm()].map(meta => {
return withContentSri(cache, meta, fn)
}, { concurrency: 1 }))
.catch(err => {
if ([].some.call(err, e => e.code === 'ENOENT')) {
throw Object.assign(
new Error('No matching content found for ' + sri.toString()),
{ code: 'ENOENT' }
)
} else {
throw err[0]
}
})
}
})
}
function withContentSriSync (cache, integrity, fn) {
const sri = ssri.parse(integrity)
// If `integrity` has multiple entries, pick the first digest
// with available local data.
const algo = sri.pickAlgorithm()
const digests = sri[algo]
if (digests.length <= 1) {
const cpath = contentPath(cache, digests[0])
return fn(cpath, digests[0])
} else {
let lastErr = null
for (const meta of sri[sri.pickAlgorithm()]) {
try {
return withContentSriSync(cache, meta, fn)
} catch (err) {
lastErr = err
}
}
if (lastErr) { throw lastErr }
}
}
function sizeError (expected, found) {
var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
err.expected = expected
err.found = found
err.code = 'EBADSIZE'
return err
}
function integrityError (sri, path) {
var err = new Error(Y`Integrity verification failed for ${sri} (${path})`)
err.code = 'EINTEGRITY'
err.sri = sri
err.path = path
return err
}
+21
View File
@@ -0,0 +1,21 @@
'use strict'
const BB = require('bluebird')
const contentPath = require('./path')
const hasContent = require('./read').hasContent
const rimraf = BB.promisify(require('rimraf'))
module.exports = rm
function rm (cache, integrity) {
return hasContent(cache, integrity).then(content => {
if (content) {
const sri = content.sri
if (sri) {
return rimraf(contentPath(cache, sri)).then(() => true)
}
} else {
return false
}
})
}
+164
View File
@@ -0,0 +1,164 @@
'use strict'
const BB = require('bluebird')
const contentPath = require('./path')
const fixOwner = require('../util/fix-owner')
const fs = require('graceful-fs')
const moveFile = require('../util/move-file')
const PassThrough = require('stream').PassThrough
const path = require('path')
const pipe = BB.promisify(require('mississippi').pipe)
const rimraf = BB.promisify(require('rimraf'))
const ssri = require('ssri')
const to = require('mississippi').to
const uniqueFilename = require('unique-filename')
const Y = require('../util/y.js')
const writeFileAsync = BB.promisify(fs.writeFile)
module.exports = write
function write (cache, data, opts) {
opts = opts || {}
if (opts.algorithms && opts.algorithms.length > 1) {
throw new Error(
Y`opts.algorithms only supports a single algorithm for now`
)
}
if (typeof opts.size === 'number' && data.length !== opts.size) {
return BB.reject(sizeError(opts.size, data.length))
}
const sri = ssri.fromData(data, {
algorithms: opts.algorithms
})
if (opts.integrity && !ssri.checkData(data, opts.integrity, opts)) {
return BB.reject(checksumError(opts.integrity, sri))
}
return BB.using(makeTmp(cache, opts), tmp => (
writeFileAsync(
tmp.target, data, { flag: 'wx' }
).then(() => (
moveToDestination(tmp, cache, sri, opts)
))
)).then(() => ({ integrity: sri, size: data.length }))
}
module.exports.stream = writeStream
function writeStream (cache, opts) {
opts = opts || {}
const inputStream = new PassThrough()
let inputErr = false
function errCheck () {
if (inputErr) { throw inputErr }
}
let allDone
const ret = to((c, n, cb) => {
if (!allDone) {
allDone = handleContent(inputStream, cache, opts, errCheck)
}
inputStream.write(c, n, cb)
}, cb => {
inputStream.end(() => {
if (!allDone) {
const e = new Error(Y`Cache input stream was empty`)
e.code = 'ENODATA'
return ret.emit('error', e)
}
allDone.then(res => {
res.integrity && ret.emit('integrity', res.integrity)
res.size !== null && ret.emit('size', res.size)
cb()
}, e => {
ret.emit('error', e)
})
})
})
ret.once('error', e => {
inputErr = e
})
return ret
}
function handleContent (inputStream, cache, opts, errCheck) {
return BB.using(makeTmp(cache, opts), tmp => {
errCheck()
return pipeToTmp(
inputStream, cache, tmp.target, opts, errCheck
).then(res => {
return moveToDestination(
tmp, cache, res.integrity, opts, errCheck
).then(() => res)
})
})
}
function pipeToTmp (inputStream, cache, tmpTarget, opts, errCheck) {
return BB.resolve().then(() => {
let integrity
let size
const hashStream = ssri.integrityStream({
integrity: opts.integrity,
algorithms: opts.algorithms,
size: opts.size
}).on('integrity', s => {
integrity = s
}).on('size', s => {
size = s
})
const outStream = fs.createWriteStream(tmpTarget, {
flags: 'wx'
})
errCheck()
return pipe(inputStream, hashStream, outStream).then(() => {
return { integrity, size }
}).catch(err => {
return rimraf(tmpTarget).then(() => { throw err })
})
})
}
function makeTmp (cache, opts) {
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
return fixOwner.mkdirfix(
cache, path.dirname(tmpTarget)
).then(() => ({
target: tmpTarget,
moved: false
})).disposer(tmp => (!tmp.moved && rimraf(tmp.target)))
}
function moveToDestination (tmp, cache, sri, opts, errCheck) {
errCheck && errCheck()
const destination = contentPath(cache, sri)
const destDir = path.dirname(destination)
return fixOwner.mkdirfix(
cache, destDir
).then(() => {
errCheck && errCheck()
return moveFile(tmp.target, destination)
}).then(() => {
errCheck && errCheck()
tmp.moved = true
return fixOwner.chownr(cache, destination)
})
}
function sizeError (expected, found) {
var err = new Error(Y`Bad data size: expected inserted data to be ${expected} bytes, but got ${found} instead`)
err.expected = expected
err.found = found
err.code = 'EBADSIZE'
return err
}
function checksumError (expected, found) {
var err = new Error(Y`Integrity check failed:
Wanted: ${expected}
Found: ${found}`)
err.code = 'EINTEGRITY'
err.expected = expected
err.found = found
return err
}
+288
View File
@@ -0,0 +1,288 @@
'use strict'
const BB = require('bluebird')
const contentPath = require('./content/path')
const crypto = require('crypto')
const figgyPudding = require('figgy-pudding')
const fixOwner = require('./util/fix-owner')
const fs = require('graceful-fs')
const hashToSegments = require('./util/hash-to-segments')
const ms = require('mississippi')
const path = require('path')
const ssri = require('ssri')
const Y = require('./util/y.js')
const indexV = require('../package.json')['cache-version'].index
const appendFileAsync = BB.promisify(fs.appendFile)
const readFileAsync = BB.promisify(fs.readFile)
const readdirAsync = BB.promisify(fs.readdir)
const concat = ms.concat
const from = ms.from
module.exports.NotFoundError = class NotFoundError extends Error {
constructor (cache, key) {
super(Y`No cache entry for \`${key}\` found in \`${cache}\``)
this.code = 'ENOENT'
this.cache = cache
this.key = key
}
}
const IndexOpts = figgyPudding({
metadata: {},
size: {}
})
module.exports.insert = insert
function insert (cache, key, integrity, opts) {
opts = IndexOpts(opts)
const bucket = bucketPath(cache, key)
const entry = {
key,
integrity: integrity && ssri.stringify(integrity),
time: Date.now(),
size: opts.size,
metadata: opts.metadata
}
return fixOwner.mkdirfix(
cache, path.dirname(bucket)
).then(() => {
const stringified = JSON.stringify(entry)
// NOTE - Cleverness ahoy!
//
// This works because it's tremendously unlikely for an entry to corrupt
// another while still preserving the string length of the JSON in
// question. So, we just slap the length in there and verify it on read.
//
// Thanks to @isaacs for the whiteboarding session that ended up with this.
return appendFileAsync(
bucket, `\n${hashEntry(stringified)}\t${stringified}`
)
}).then(
() => fixOwner.chownr(cache, bucket)
).catch({ code: 'ENOENT' }, () => {
// There's a class of race conditions that happen when things get deleted
// during fixOwner, or between the two mkdirfix/chownr calls.
//
// It's perfectly fine to just not bother in those cases and lie
// that the index entry was written. Because it's a cache.
}).then(() => {
return formatEntry(cache, entry)
})
}
module.exports.insert.sync = insertSync
function insertSync (cache, key, integrity, opts) {
opts = IndexOpts(opts)
const bucket = bucketPath(cache, key)
const entry = {
key,
integrity: integrity && ssri.stringify(integrity),
time: Date.now(),
size: opts.size,
metadata: opts.metadata
}
fixOwner.mkdirfix.sync(cache, path.dirname(bucket))
const stringified = JSON.stringify(entry)
fs.appendFileSync(
bucket, `\n${hashEntry(stringified)}\t${stringified}`
)
try {
fixOwner.chownr.sync(cache, bucket)
} catch (err) {
if (err.code !== 'ENOENT') {
throw err
}
}
return formatEntry(cache, entry)
}
module.exports.find = find
function find (cache, key) {
const bucket = bucketPath(cache, key)
return bucketEntries(bucket).then(entries => {
return entries.reduce((latest, next) => {
if (next && next.key === key) {
return formatEntry(cache, next)
} else {
return latest
}
}, null)
}).catch(err => {
if (err.code === 'ENOENT') {
return null
} else {
throw err
}
})
}
module.exports.find.sync = findSync
function findSync (cache, key) {
const bucket = bucketPath(cache, key)
try {
return bucketEntriesSync(bucket).reduce((latest, next) => {
if (next && next.key === key) {
return formatEntry(cache, next)
} else {
return latest
}
}, null)
} catch (err) {
if (err.code === 'ENOENT') {
return null
} else {
throw err
}
}
}
module.exports.delete = del
function del (cache, key, opts) {
return insert(cache, key, null, opts)
}
module.exports.delete.sync = delSync
function delSync (cache, key, opts) {
return insertSync(cache, key, null, opts)
}
module.exports.lsStream = lsStream
function lsStream (cache) {
const indexDir = bucketDir(cache)
const stream = from.obj()
// "/cachename/*"
readdirOrEmpty(indexDir).map(bucket => {
const bucketPath = path.join(indexDir, bucket)
// "/cachename/<bucket 0xFF>/*"
return readdirOrEmpty(bucketPath).map(subbucket => {
const subbucketPath = path.join(bucketPath, subbucket)
// "/cachename/<bucket 0xFF>/<bucket 0xFF>/*"
return readdirOrEmpty(subbucketPath).map(entry => {
const getKeyToEntry = bucketEntries(
path.join(subbucketPath, entry)
).reduce((acc, entry) => {
acc.set(entry.key, entry)
return acc
}, new Map())
return getKeyToEntry.then(reduced => {
for (let entry of reduced.values()) {
const formatted = formatEntry(cache, entry)
formatted && stream.push(formatted)
}
}).catch({ code: 'ENOENT' }, nop)
})
})
}).then(() => {
stream.push(null)
}, err => {
stream.emit('error', err)
})
return stream
}
module.exports.ls = ls
function ls (cache) {
return BB.fromNode(cb => {
lsStream(cache).on('error', cb).pipe(concat(entries => {
cb(null, entries.reduce((acc, xs) => {
acc[xs.key] = xs
return acc
}, {}))
}))
})
}
function bucketEntries (bucket, filter) {
return readFileAsync(
bucket, 'utf8'
).then(data => _bucketEntries(data, filter))
}
function bucketEntriesSync (bucket, filter) {
const data = fs.readFileSync(bucket, 'utf8')
return _bucketEntries(data, filter)
}
function _bucketEntries (data, filter) {
let entries = []
data.split('\n').forEach(entry => {
if (!entry) { return }
const pieces = entry.split('\t')
if (!pieces[1] || hashEntry(pieces[1]) !== pieces[0]) {
// Hash is no good! Corruption or malice? Doesn't matter!
// EJECT EJECT
return
}
let obj
try {
obj = JSON.parse(pieces[1])
} catch (e) {
// Entry is corrupted!
return
}
if (obj) {
entries.push(obj)
}
})
return entries
}
module.exports._bucketDir = bucketDir
function bucketDir (cache) {
return path.join(cache, `index-v${indexV}`)
}
module.exports._bucketPath = bucketPath
function bucketPath (cache, key) {
const hashed = hashKey(key)
return path.join.apply(path, [bucketDir(cache)].concat(
hashToSegments(hashed)
))
}
module.exports._hashKey = hashKey
function hashKey (key) {
return hash(key, 'sha256')
}
module.exports._hashEntry = hashEntry
function hashEntry (str) {
return hash(str, 'sha1')
}
function hash (str, digest) {
return crypto
.createHash(digest)
.update(str)
.digest('hex')
}
function formatEntry (cache, entry) {
// Treat null digests as deletions. They'll shadow any previous entries.
if (!entry.integrity) { return null }
return {
key: entry.key,
integrity: entry.integrity,
path: contentPath(cache, entry.integrity),
size: entry.size,
time: entry.time,
metadata: entry.metadata
}
}
function readdirOrEmpty (dir) {
return readdirAsync(dir)
.catch({ code: 'ENOENT' }, () => [])
.catch({ code: 'ENOTDIR' }, () => [])
}
function nop () {
}
+69
View File
@@ -0,0 +1,69 @@
'use strict'
const LRU = require('lru-cache')
const MAX_SIZE = 50 * 1024 * 1024 // 50MB
const MAX_AGE = 3 * 60 * 1000
let MEMOIZED = new LRU({
max: MAX_SIZE,
maxAge: MAX_AGE,
length: (entry, key) => {
if (key.startsWith('key:')) {
return entry.data.length
} else if (key.startsWith('digest:')) {
return entry.length
}
}
})
module.exports.clearMemoized = clearMemoized
function clearMemoized () {
const old = {}
MEMOIZED.forEach((v, k) => {
old[k] = v
})
MEMOIZED.reset()
return old
}
module.exports.put = put
function put (cache, entry, data, opts) {
pickMem(opts).set(`key:${cache}:${entry.key}`, { entry, data })
putDigest(cache, entry.integrity, data, opts)
}
module.exports.put.byDigest = putDigest
function putDigest (cache, integrity, data, opts) {
pickMem(opts).set(`digest:${cache}:${integrity}`, data)
}
module.exports.get = get
function get (cache, key, opts) {
return pickMem(opts).get(`key:${cache}:${key}`)
}
module.exports.get.byDigest = getDigest
function getDigest (cache, integrity, opts) {
return pickMem(opts).get(`digest:${cache}:${integrity}`)
}
class ObjProxy {
constructor (obj) {
this.obj = obj
}
get (key) { return this.obj[key] }
set (key, val) { this.obj[key] = val }
}
function pickMem (opts) {
if (!opts || !opts.memoize) {
return MEMOIZED
} else if (opts.memoize.get && opts.memoize.set) {
return opts.memoize
} else if (typeof opts.memoize === 'object') {
return new ObjProxy(opts.memoize)
} else {
return MEMOIZED
}
}
+128
View File
@@ -0,0 +1,128 @@
'use strict'
const BB = require('bluebird')
const chownr = BB.promisify(require('chownr'))
const mkdirp = BB.promisify(require('mkdirp'))
const inflight = require('promise-inflight')
const inferOwner = require('infer-owner')
// Memoize getuid()/getgid() calls.
// patch process.setuid/setgid to invalidate cached value on change
const self = { uid: null, gid: null }
const getSelf = () => {
if (typeof self.uid !== 'number') {
self.uid = process.getuid()
const setuid = process.setuid
process.setuid = (uid) => {
self.uid = null
process.setuid = setuid
return process.setuid(uid)
}
}
if (typeof self.gid !== 'number') {
self.gid = process.getgid()
const setgid = process.setgid
process.setgid = (gid) => {
self.gid = null
process.setgid = setgid
return process.setgid(gid)
}
}
}
module.exports.chownr = fixOwner
function fixOwner (cache, filepath) {
if (!process.getuid) {
// This platform doesn't need ownership fixing
return BB.resolve()
}
getSelf()
if (self.uid !== 0) {
// almost certainly can't chown anyway
return BB.resolve()
}
return BB.resolve(inferOwner(cache)).then(owner => {
const { uid, gid } = owner
// No need to override if it's already what we used.
if (self.uid === uid && self.gid === gid) {
return
}
return inflight(
'fixOwner: fixing ownership on ' + filepath,
() => chownr(
filepath,
typeof uid === 'number' ? uid : self.uid,
typeof gid === 'number' ? gid : self.gid
).catch({ code: 'ENOENT' }, () => null)
)
})
}
module.exports.chownr.sync = fixOwnerSync
function fixOwnerSync (cache, filepath) {
if (!process.getuid) {
// This platform doesn't need ownership fixing
return
}
const { uid, gid } = inferOwner.sync(cache)
getSelf()
if (self.uid === uid && self.gid === gid) {
// No need to override if it's already what we used.
return
}
try {
chownr.sync(
filepath,
typeof uid === 'number' ? uid : self.uid,
typeof gid === 'number' ? gid : self.gid
)
} catch (err) {
// only catch ENOENT, any other error is a problem.
if (err.code === 'ENOENT') {
return null
}
throw err
}
}
module.exports.mkdirfix = mkdirfix
function mkdirfix (cache, p, cb) {
// we have to infer the owner _before_ making the directory, even though
// we aren't going to use the results, since the cache itself might not
// exist yet. If we mkdirp it, then our current uid/gid will be assumed
// to be correct if it creates the cache folder in the process.
return BB.resolve(inferOwner(cache)).then(() => {
return mkdirp(p).then(made => {
if (made) {
return fixOwner(cache, made).then(() => made)
}
}).catch({ code: 'EEXIST' }, () => {
// There's a race in mkdirp!
return fixOwner(cache, p).then(() => null)
})
})
}
module.exports.mkdirfix.sync = mkdirfixSync
function mkdirfixSync (cache, p) {
try {
inferOwner.sync(cache)
const made = mkdirp.sync(p)
if (made) {
fixOwnerSync(cache, made)
return made
}
} catch (err) {
if (err.code === 'EEXIST') {
fixOwnerSync(cache, p)
return null
} else {
throw err
}
}
}
+11
View File
@@ -0,0 +1,11 @@
'use strict'
module.exports = hashToSegments
function hashToSegments (hash) {
return [
hash.slice(0, 2),
hash.slice(2, 4),
hash.slice(4)
]
}
+51
View File
@@ -0,0 +1,51 @@
'use strict'
const fs = require('graceful-fs')
const BB = require('bluebird')
const chmod = BB.promisify(fs.chmod)
const unlink = BB.promisify(fs.unlink)
let move
let pinflight
module.exports = moveFile
function moveFile (src, dest) {
// This isn't quite an fs.rename -- the assumption is that
// if `dest` already exists, and we get certain errors while
// trying to move it, we should just not bother.
//
// In the case of cache corruption, users will receive an
// EINTEGRITY error elsewhere, and can remove the offending
// content their own way.
//
// Note that, as the name suggests, this strictly only supports file moves.
return BB.fromNode(cb => {
fs.link(src, dest, err => {
if (err) {
if (err.code === 'EEXIST' || err.code === 'EBUSY') {
// file already exists, so whatever
} else if (err.code === 'EPERM' && process.platform === 'win32') {
// file handle stayed open even past graceful-fs limits
} else {
return cb(err)
}
}
return cb()
})
}).then(() => {
// content should never change for any reason, so make it read-only
return BB.join(unlink(src), process.platform !== 'win32' && chmod(dest, '0444'))
}).catch(() => {
if (!pinflight) { pinflight = require('promise-inflight') }
return pinflight('cacache-move-file:' + dest, () => {
return BB.promisify(fs.stat)(dest).catch(err => {
if (err.code !== 'ENOENT') {
// Something else is wrong here. Bail bail bail
throw err
}
// file doesn't already exist! let's try a rename -> copy fallback
if (!move) { move = require('move-concurrently') }
return move(src, dest, { BB, fs })
})
})
})
}
+37
View File
@@ -0,0 +1,37 @@
'use strict'
const BB = require('bluebird')
const figgyPudding = require('figgy-pudding')
const fixOwner = require('./fix-owner')
const path = require('path')
const rimraf = BB.promisify(require('rimraf'))
const uniqueFilename = require('unique-filename')
const TmpOpts = figgyPudding({
tmpPrefix: {}
})
module.exports.mkdir = mktmpdir
function mktmpdir (cache, opts) {
opts = TmpOpts(opts)
const tmpTarget = uniqueFilename(path.join(cache, 'tmp'), opts.tmpPrefix)
return fixOwner.mkdirfix(cache, tmpTarget).then(() => {
return tmpTarget
})
}
module.exports.withTmp = withTmp
function withTmp (cache, opts, cb) {
if (!cb) {
cb = opts
opts = null
}
opts = TmpOpts(opts)
return BB.using(mktmpdir(cache, opts).disposer(rimraf), cb)
}
module.exports.fix = fixtmpdir
function fixtmpdir (cache) {
return fixOwner(cache, path.join(cache, 'tmp'))
}
+25
View File
@@ -0,0 +1,25 @@
'use strict'
const path = require('path')
const y18n = require('y18n')({
directory: path.join(__dirname, '../../locales'),
locale: 'en',
updateFiles: process.env.CACACHE_UPDATE_LOCALE_FILES === 'true'
})
module.exports = yTag
function yTag (parts) {
let str = ''
parts.forEach((part, i) => {
const arg = arguments[i + 1]
str += part
if (arg) {
str += '%s'
}
})
return y18n.__.apply(null, [str].concat([].slice.call(arguments, 1)))
}
module.exports.setLocale = locale => {
y18n.setLocale(locale)
}
+227
View File
@@ -0,0 +1,227 @@
'use strict'
const BB = require('bluebird')
const contentPath = require('./content/path')
const figgyPudding = require('figgy-pudding')
const finished = BB.promisify(require('mississippi').finished)
const fixOwner = require('./util/fix-owner')
const fs = require('graceful-fs')
const glob = BB.promisify(require('glob'))
const index = require('./entry-index')
const path = require('path')
const rimraf = BB.promisify(require('rimraf'))
const ssri = require('ssri')
BB.promisifyAll(fs)
const VerifyOpts = figgyPudding({
concurrency: {
default: 20
},
filter: {},
log: {
default: { silly () {} }
}
})
module.exports = verify
function verify (cache, opts) {
opts = VerifyOpts(opts)
opts.log.silly('verify', 'verifying cache at', cache)
return BB.reduce([
markStartTime,
fixPerms,
garbageCollect,
rebuildIndex,
cleanTmp,
writeVerifile,
markEndTime
], (stats, step, i) => {
const label = step.name || `step #${i}`
const start = new Date()
return BB.resolve(step(cache, opts)).then(s => {
s && Object.keys(s).forEach(k => {
stats[k] = s[k]
})
const end = new Date()
if (!stats.runTime) { stats.runTime = {} }
stats.runTime[label] = end - start
return stats
})
}, {}).tap(stats => {
stats.runTime.total = stats.endTime - stats.startTime
opts.log.silly('verify', 'verification finished for', cache, 'in', `${stats.runTime.total}ms`)
})
}
function markStartTime (cache, opts) {
return { startTime: new Date() }
}
function markEndTime (cache, opts) {
return { endTime: new Date() }
}
function fixPerms (cache, opts) {
opts.log.silly('verify', 'fixing cache permissions')
return fixOwner.mkdirfix(cache, cache).then(() => {
// TODO - fix file permissions too
return fixOwner.chownr(cache, cache)
}).then(() => null)
}
// Implements a naive mark-and-sweep tracing garbage collector.
//
// The algorithm is basically as follows:
// 1. Read (and filter) all index entries ("pointers")
// 2. Mark each integrity value as "live"
// 3. Read entire filesystem tree in `content-vX/` dir
// 4. If content is live, verify its checksum and delete it if it fails
// 5. If content is not marked as live, rimraf it.
//
function garbageCollect (cache, opts) {
opts.log.silly('verify', 'garbage collecting content')
const indexStream = index.lsStream(cache)
const liveContent = new Set()
indexStream.on('data', entry => {
if (opts.filter && !opts.filter(entry)) { return }
liveContent.add(entry.integrity.toString())
})
return finished(indexStream).then(() => {
const contentDir = contentPath._contentDir(cache)
return glob(path.join(contentDir, '**'), {
follow: false,
nodir: true,
nosort: true
}).then(files => {
return BB.resolve({
verifiedContent: 0,
reclaimedCount: 0,
reclaimedSize: 0,
badContentCount: 0,
keptSize: 0
}).tap((stats) => BB.map(files, (f) => {
const split = f.split(/[/\\]/)
const digest = split.slice(split.length - 3).join('')
const algo = split[split.length - 4]
const integrity = ssri.fromHex(digest, algo)
if (liveContent.has(integrity.toString())) {
return verifyContent(f, integrity).then(info => {
if (!info.valid) {
stats.reclaimedCount++
stats.badContentCount++
stats.reclaimedSize += info.size
} else {
stats.verifiedContent++
stats.keptSize += info.size
}
return stats
})
} else {
// No entries refer to this content. We can delete.
stats.reclaimedCount++
return fs.statAsync(f).then(s => {
return rimraf(f).then(() => {
stats.reclaimedSize += s.size
return stats
})
})
}
}, { concurrency: opts.concurrency }))
})
})
}
function verifyContent (filepath, sri) {
return fs.statAsync(filepath).then(stat => {
const contentInfo = {
size: stat.size,
valid: true
}
return ssri.checkStream(
fs.createReadStream(filepath),
sri
).catch(err => {
if (err.code !== 'EINTEGRITY') { throw err }
return rimraf(filepath).then(() => {
contentInfo.valid = false
})
}).then(() => contentInfo)
}).catch({ code: 'ENOENT' }, () => ({ size: 0, valid: false }))
}
function rebuildIndex (cache, opts) {
opts.log.silly('verify', 'rebuilding index')
return index.ls(cache).then(entries => {
const stats = {
missingContent: 0,
rejectedEntries: 0,
totalEntries: 0
}
const buckets = {}
for (let k in entries) {
if (entries.hasOwnProperty(k)) {
const hashed = index._hashKey(k)
const entry = entries[k]
const excluded = opts.filter && !opts.filter(entry)
excluded && stats.rejectedEntries++
if (buckets[hashed] && !excluded) {
buckets[hashed].push(entry)
} else if (buckets[hashed] && excluded) {
// skip
} else if (excluded) {
buckets[hashed] = []
buckets[hashed]._path = index._bucketPath(cache, k)
} else {
buckets[hashed] = [entry]
buckets[hashed]._path = index._bucketPath(cache, k)
}
}
}
return BB.map(Object.keys(buckets), key => {
return rebuildBucket(cache, buckets[key], stats, opts)
}, { concurrency: opts.concurrency }).then(() => stats)
})
}
function rebuildBucket (cache, bucket, stats, opts) {
return fs.truncateAsync(bucket._path).then(() => {
// This needs to be serialized because cacache explicitly
// lets very racy bucket conflicts clobber each other.
return BB.mapSeries(bucket, entry => {
const content = contentPath(cache, entry.integrity)
return fs.statAsync(content).then(() => {
return index.insert(cache, entry.key, entry.integrity, {
metadata: entry.metadata,
size: entry.size
}).then(() => { stats.totalEntries++ })
}).catch({ code: 'ENOENT' }, () => {
stats.rejectedEntries++
stats.missingContent++
})
})
})
}
function cleanTmp (cache, opts) {
opts.log.silly('verify', 'cleaning tmp directory')
return rimraf(path.join(cache, 'tmp'))
}
function writeVerifile (cache, opts) {
const verifile = path.join(cache, '_lastverified')
opts.log.silly('verify', 'writing verifile to ' + verifile)
try {
return fs.writeFileAsync(verifile, '' + (+(new Date())))
} finally {
fixOwner.chownr.sync(cache, verifile)
}
}
module.exports.lastRun = lastRun
function lastRun (cache) {
return fs.readFileAsync(
path.join(cache, '_lastverified'), 'utf8'
).then(data => new Date(+data))
}
+47
View File
@@ -0,0 +1,47 @@
'use strict'
const ls = require('../ls.js')
const get = require('../get.js')
const put = require('../put.js')
const rm = require('../rm.js')
const verify = require('../verify.js')
const setLocale = require('../lib/util/y.js').setLocale
const clearMemoized = require('../lib/memoization.js').clearMemoized
const tmp = require('../lib/util/tmp.js')
setLocale('en')
const x = module.exports
x.ls = cache => ls(cache)
x.ls.stream = cache => ls.stream(cache)
x.get = (cache, key, opts) => get(cache, key, opts)
x.get.byDigest = (cache, hash, opts) => get.byDigest(cache, hash, opts)
x.get.sync = (cache, key, opts) => get.sync(cache, key, opts)
x.get.sync.byDigest = (cache, key, opts) => get.sync.byDigest(cache, key, opts)
x.get.stream = (cache, key, opts) => get.stream(cache, key, opts)
x.get.stream.byDigest = (cache, hash, opts) => get.stream.byDigest(cache, hash, opts)
x.get.copy = (cache, key, dest, opts) => get.copy(cache, key, dest, opts)
x.get.copy.byDigest = (cache, hash, dest, opts) => get.copy.byDigest(cache, hash, dest, opts)
x.get.info = (cache, key) => get.info(cache, key)
x.get.hasContent = (cache, hash) => get.hasContent(cache, hash)
x.get.hasContent.sync = (cache, hash) => get.hasContent.sync(cache, hash)
x.put = (cache, key, data, opts) => put(cache, key, data, opts)
x.put.stream = (cache, key, opts) => put.stream(cache, key, opts)
x.rm = (cache, key) => rm.entry(cache, key)
x.rm.all = cache => rm.all(cache)
x.rm.entry = x.rm
x.rm.content = (cache, hash) => rm.content(cache, hash)
x.setLocale = lang => setLocale(lang)
x.clearMemoized = () => clearMemoized()
x.tmp = {}
x.tmp.mkdir = (cache, opts) => tmp.mkdir(cache, opts)
x.tmp.withTmp = (cache, opts, cb) => tmp.withTmp(cache, opts, cb)
x.verify = (cache, opts) => verify(cache, opts)
x.verify.lastRun = cache => verify.lastRun(cache)
+7
View File
@@ -0,0 +1,7 @@
{
"No cache entry for `%s` found in `%s`": "No cache entry for %s found in %s",
"Integrity verification failed for %s (%s)": "Integrity verification failed for %s (%s)",
"Bad data size: expected inserted data to be %s bytes, but got %s instead": "Bad data size: expected inserted data to be %s bytes, but got %s instead",
"Cache input stream was empty": "Cache input stream was empty",
"Integrity check failed:\n Wanted: %s\n Found: %s": "Integrity check failed:\n Wanted: %s\n Found: %s"
}
+49
View File
@@ -0,0 +1,49 @@
'use strict'
const ls = require('../ls.js')
const get = require('../get.js')
const put = require('../put.js')
const rm = require('../rm.js')
const verify = require('../verify.js')
const setLocale = require('../lib/util/y.js').setLocale
const clearMemoized = require('../lib/memoization.js').clearMemoized
const tmp = require('../lib/util/tmp.js')
setLocale('es')
const x = module.exports
x.ls = cache => ls(cache)
x.ls.flujo = cache => ls.stream(cache)
x.saca = (cache, clave, ops) => get(cache, clave, ops)
x.saca.porHacheo = (cache, hacheo, ops) => get.byDigest(cache, hacheo, ops)
x.saca.sinc = (cache, clave, ops) => get.sync(cache, clave, ops)
x.saca.sinc.porHacheo = (cache, hacheo, ops) => get.sync.byDigest(cache, hacheo, ops)
x.saca.flujo = (cache, clave, ops) => get.stream(cache, clave, ops)
x.saca.flujo.porHacheo = (cache, hacheo, ops) => get.stream.byDigest(cache, hacheo, ops)
x.sava.copia = (cache, clave, destino, opts) => get.copy(cache, clave, destino, opts)
x.sava.copia.porHacheo = (cache, hacheo, destino, opts) => get.copy.byDigest(cache, hacheo, destino, opts)
x.saca.info = (cache, clave) => get.info(cache, clave)
x.saca.tieneDatos = (cache, hacheo) => get.hasContent(cache, hacheo)
x.saca.tieneDatos.sinc = (cache, hacheo) => get.hasContent.sync(cache, hacheo)
x.mete = (cache, clave, datos, ops) => put(cache, clave, datos, ops)
x.mete.flujo = (cache, clave, ops) => put.stream(cache, clave, ops)
x.rm = (cache, clave) => rm.entry(cache, clave)
x.rm.todo = cache => rm.all(cache)
x.rm.entrada = x.rm
x.rm.datos = (cache, hacheo) => rm.content(cache, hacheo)
x.ponLenguaje = lang => setLocale(lang)
x.limpiaMemoizado = () => clearMemoized()
x.tmp = {}
x.tmp.mkdir = (cache, ops) => tmp.mkdir(cache, ops)
x.tmp.hazdir = x.tmp.mkdir
x.tmp.conTmp = (cache, ops, cb) => tmp.withTmp(cache, ops, cb)
x.verifica = (cache, ops) => verify(cache, ops)
x.verifica.ultimaVez = cache => verify.lastRun(cache)
x.verifica.últimaVez = x.verifica.ultimaVez
+6
View File
@@ -0,0 +1,6 @@
{
"No cache entry for `%s` found in `%s`": "No existe ninguna entrada para «%s» en «%s»",
"Integrity verification failed for %s (%s)": "Verificación de integridad falló para «%s» (%s)",
"Bad data size: expected inserted data to be %s bytes, but got %s instead": "Tamaño incorrecto de datos: los datos insertados debieron haber sido %s octetos, pero fueron %s",
"Cache input stream was empty": "El stream de entrada al caché estaba vacío"
}
+6
View File
@@ -0,0 +1,6 @@
'use strict'
var index = require('./lib/entry-index')
module.exports = index.ls
module.exports.stream = index.lsStream
+128
View File
@@ -0,0 +1,128 @@
{
"_args": [
[
"cacache@12.0.4",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "cacache@12.0.4",
"_id": "cacache@12.0.4",
"_inBundle": false,
"_integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==",
"_location": "/webpack/cacache",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "cacache@12.0.4",
"name": "cacache",
"escapedName": "cacache",
"rawSpec": "12.0.4",
"saveSpec": null,
"fetchSpec": "12.0.4"
},
"_requiredBy": [
"/webpack/terser-webpack-plugin"
],
"_resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz",
"_spec": "12.0.4",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Kat Marchán",
"email": "kzm@sykosomatic.org"
},
"bugs": {
"url": "https://github.com/npm/cacache/issues"
},
"cache-version": {
"content": "2",
"index": "5"
},
"config": {
"nyc": {
"exclude": [
"node_modules/**",
"test/**"
]
}
},
"contributors": [
{
"name": "Charlotte Spencer",
"email": "charlottelaspencer@gmail.com"
},
{
"name": "Rebecca Turner",
"email": "me@re-becca.org"
}
],
"dependencies": {
"bluebird": "^3.5.5",
"chownr": "^1.1.1",
"figgy-pudding": "^3.5.1",
"glob": "^7.1.4",
"graceful-fs": "^4.1.15",
"infer-owner": "^1.0.3",
"lru-cache": "^5.1.1",
"mississippi": "^3.0.0",
"mkdirp": "^0.5.1",
"move-concurrently": "^1.0.1",
"promise-inflight": "^1.0.1",
"rimraf": "^2.6.3",
"ssri": "^6.0.1",
"unique-filename": "^1.1.1",
"y18n": "^4.0.0"
},
"description": "Fast, fault-tolerant, cross-platform, disk-based, data-agnostic, content-addressable cache.",
"devDependencies": {
"benchmark": "^2.1.4",
"chalk": "^2.4.2",
"cross-env": "^5.1.4",
"require-inject": "^1.4.4",
"standard": "^12.0.1",
"standard-version": "^6.0.1",
"tacks": "^1.3.0",
"tap": "^12.7.0"
},
"files": [
"*.js",
"lib",
"locales"
],
"homepage": "https://github.com/npm/cacache#readme",
"keywords": [
"cache",
"caching",
"content-addressable",
"sri",
"sri hash",
"subresource integrity",
"cache",
"storage",
"store",
"file store",
"filesystem",
"disk cache",
"disk storage"
],
"license": "ISC",
"main": "index.js",
"name": "cacache",
"publishConfig": {
"tag": "legacy"
},
"repository": {
"type": "git",
"url": "git+https://github.com/npm/cacache.git"
},
"scripts": {
"benchmarks": "node test/benchmarks",
"postrelease": "npm publish && git push --follow-tags",
"prerelease": "npm t",
"pretest": "standard",
"release": "standard-version -s",
"test": "cross-env CACACHE_UPDATE_LOCALE_FILES=true tap --coverage --nyc-arg=--all -J test/*.js",
"test-docker": "docker run -it --rm --name pacotest -v \"$PWD\":/tmp -w /tmp node:latest npm test"
},
"version": "12.0.4"
}
+86
View File
@@ -0,0 +1,86 @@
'use strict'
const figgyPudding = require('figgy-pudding')
const index = require('./lib/entry-index')
const memo = require('./lib/memoization')
const write = require('./lib/content/write')
const to = require('mississippi').to
const PutOpts = figgyPudding({
algorithms: {
default: ['sha512']
},
integrity: {},
memoize: {},
metadata: {},
pickAlgorithm: {},
size: {},
tmpPrefix: {},
single: {},
sep: {},
error: {},
strict: {}
})
module.exports = putData
function putData (cache, key, data, opts) {
opts = PutOpts(opts)
return write(cache, data, opts).then(res => {
return index.insert(
cache, key, res.integrity, opts.concat({ size: res.size })
).then(entry => {
if (opts.memoize) {
memo.put(cache, entry, data, opts)
}
return res.integrity
})
})
}
module.exports.stream = putStream
function putStream (cache, key, opts) {
opts = PutOpts(opts)
let integrity
let size
const contentStream = write.stream(
cache, opts
).on('integrity', int => {
integrity = int
}).on('size', s => {
size = s
})
let memoData
let memoTotal = 0
const stream = to((chunk, enc, cb) => {
contentStream.write(chunk, enc, () => {
if (opts.memoize) {
if (!memoData) { memoData = [] }
memoData.push(chunk)
memoTotal += chunk.length
}
cb()
})
}, cb => {
contentStream.end(() => {
index.insert(cache, key, integrity, opts.concat({ size })).then(entry => {
if (opts.memoize) {
memo.put(cache, entry, Buffer.concat(memoData, memoTotal), opts)
}
stream.emit('integrity', integrity)
cb()
})
})
})
let erred = false
stream.once('error', err => {
if (erred) { return }
erred = true
contentStream.emit('error', err)
})
contentStream.once('error', err => {
if (erred) { return }
erred = true
stream.emit('error', err)
})
return stream
}
+28
View File
@@ -0,0 +1,28 @@
'use strict'
const BB = require('bluebird')
const index = require('./lib/entry-index')
const memo = require('./lib/memoization')
const path = require('path')
const rimraf = BB.promisify(require('rimraf'))
const rmContent = require('./lib/content/rm')
module.exports = entry
module.exports.entry = entry
function entry (cache, key) {
memo.clearMemoized()
return index.delete(cache, key)
}
module.exports.content = content
function content (cache, integrity) {
memo.clearMemoized()
return rmContent(cache, integrity)
}
module.exports.all = all
function all (cache) {
memo.clearMemoized()
return rimraf(path.join(cache, '*(content-*|index-*)'))
}
+3
View File
@@ -0,0 +1,3 @@
'use strict'
module.exports = require('./lib/verify')
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+250
View File
@@ -0,0 +1,250 @@
# fill-range [![NPM version](https://img.shields.io/npm/v/fill-range.svg?style=flat)](https://www.npmjs.com/package/fill-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![NPM total downloads](https://img.shields.io/npm/dt/fill-range.svg?style=flat)](https://npmjs.org/package/fill-range) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/fill-range.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/fill-range)
> Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`
## Table of Contents
- [Install](#install)
- [Usage](#usage)
- [Examples](#examples)
- [Options](#options)
* [options.step](#optionsstep)
* [options.strictRanges](#optionsstrictranges)
* [options.stringify](#optionsstringify)
* [options.toRegex](#optionstoregex)
* [options.transform](#optionstransform)
- [About](#about)
_(TOC generated by [verb](https://github.com/verbose/verb) using [markdown-toc](https://github.com/jonschlinkert/markdown-toc))_
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save fill-range
```
Install with [yarn](https://yarnpkg.com):
```sh
$ yarn add fill-range
```
## Usage
Expands numbers and letters, optionally using a `step` as the last argument. _(Numbers may be defined as JavaScript numbers or strings)_.
```js
var fill = require('fill-range');
fill(from, to[, step, options]);
// examples
console.log(fill('1', '10')); //=> '[ '1', '2', '3', '4', '5', '6', '7', '8', '9', '10' ]'
console.log(fill('1', '10', {toRegex: true})); //=> [1-9]|10
```
**Params**
* `from`: **{String|Number}** the number or letter to start with
* `to`: **{String|Number}** the number or letter to end with
* `step`: **{String|Number|Object|Function}** Optionally pass a [step](#optionsstep) to use.
* `options`: **{Object|Function}**: See all available [options](#options)
## Examples
By default, an array of values is returned.
**Alphabetical ranges**
```js
console.log(fill('a', 'e')); //=> ['a', 'b', 'c', 'd', 'e']
console.log(fill('A', 'E')); //=> [ 'A', 'B', 'C', 'D', 'E' ]
```
**Numerical ranges**
Numbers can be defined as actual numbers or strings.
```js
console.log(fill(1, 5)); //=> [ 1, 2, 3, 4, 5 ]
console.log(fill('1', '5')); //=> [ 1, 2, 3, 4, 5 ]
```
**Negative ranges**
Numbers can be defined as actual numbers or strings.
```js
console.log(fill('-5', '-1')); //=> [ '-5', '-4', '-3', '-2', '-1' ]
console.log(fill('-5', '5')); //=> [ '-5', '-4', '-3', '-2', '-1', '0', '1', '2', '3', '4', '5' ]
```
**Steps (increments)**
```js
// numerical ranges with increments
console.log(fill('0', '25', 4)); //=> [ '0', '4', '8', '12', '16', '20', '24' ]
console.log(fill('0', '25', 5)); //=> [ '0', '5', '10', '15', '20', '25' ]
console.log(fill('0', '25', 6)); //=> [ '0', '6', '12', '18', '24' ]
// alphabetical ranges with increments
console.log(fill('a', 'z', 4)); //=> [ 'a', 'e', 'i', 'm', 'q', 'u', 'y' ]
console.log(fill('a', 'z', 5)); //=> [ 'a', 'f', 'k', 'p', 'u', 'z' ]
console.log(fill('a', 'z', 6)); //=> [ 'a', 'g', 'm', 's', 'y' ]
```
## Options
### options.step
**Type**: `number` (formatted as a string or number)
**Default**: `undefined`
**Description**: The increment to use for the range. Can be used with letters or numbers.
**Example(s)**
```js
// numbers
console.log(fill('1', '10', 2)); //=> [ '1', '3', '5', '7', '9' ]
console.log(fill('1', '10', 3)); //=> [ '1', '4', '7', '10' ]
console.log(fill('1', '10', 4)); //=> [ '1', '5', '9' ]
// letters
console.log(fill('a', 'z', 5)); //=> [ 'a', 'f', 'k', 'p', 'u', 'z' ]
console.log(fill('a', 'z', 7)); //=> [ 'a', 'h', 'o', 'v' ]
console.log(fill('a', 'z', 9)); //=> [ 'a', 'j', 's' ]
```
### options.strictRanges
**Type**: `boolean`
**Default**: `false`
**Description**: By default, `null` is returned when an invalid range is passed. Enable this option to throw a `RangeError` on invalid ranges.
**Example(s)**
The following are all invalid:
```js
fill('1.1', '2'); // decimals not supported in ranges
fill('a', '2'); // incompatible range values
fill(1, 10, 'foo'); // invalid "step" argument
```
### options.stringify
**Type**: `boolean`
**Default**: `undefined`
**Description**: Cast all returned values to strings. By default, integers are returned as numbers.
**Example(s)**
```js
console.log(fill(1, 5)); //=> [ 1, 2, 3, 4, 5 ]
console.log(fill(1, 5, {stringify: true})); //=> [ '1', '2', '3', '4', '5' ]
```
### options.toRegex
**Type**: `boolean`
**Default**: `undefined`
**Description**: Create a regex-compatible source string, instead of expanding values to an array.
**Example(s)**
```js
// alphabetical range
console.log(fill('a', 'e', {toRegex: true})); //=> '[a-e]'
// alphabetical with step
console.log(fill('a', 'z', 3, {toRegex: true})); //=> 'a|d|g|j|m|p|s|v|y'
// numerical range
console.log(fill('1', '100', {toRegex: true})); //=> '[1-9]|[1-9][0-9]|100'
// numerical range with zero padding
console.log(fill('000001', '100000', {toRegex: true}));
//=> '0{5}[1-9]|0{4}[1-9][0-9]|0{3}[1-9][0-9]{2}|0{2}[1-9][0-9]{3}|0[1-9][0-9]{4}|100000'
```
### options.transform
**Type**: `function`
**Default**: `undefined`
**Description**: Customize each value in the returned array (or [string](#optionstoRegex)). _(you can also pass this function as the last argument to `fill()`)_.
**Example(s)**
```js
// increase padding by two
var arr = fill('01', '05', function(val, a, b, step, idx, arr, options) {
return repeat('0', (options.maxLength + 2) - val.length) + val;
});
console.log(arr);
//=> ['0001', '0002', '0003', '0004', '0005']
```
## About
### Related projects
* [braces](https://www.npmjs.com/package/braces): Fast, comprehensive, bash-like brace expansion implemented in JavaScript. Complete support for the Bash 4.3 braces… [more](https://github.com/jonschlinkert/braces) | [homepage](https://github.com/jonschlinkert/braces "Fast, comprehensive, bash-like brace expansion implemented in JavaScript. Complete support for the Bash 4.3 braces specification, without sacrificing speed.")
* [expand-range](https://www.npmjs.com/package/expand-range): Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See… [more](https://github.com/jonschlinkert/expand-range) | [homepage](https://github.com/jonschlinkert/expand-range "Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See the benchmarks. Used by micromatch.")
* [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/jonschlinkert/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.")
* [to-regex-range](https://www.npmjs.com/package/to-regex-range): Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than… [more](https://github.com/jonschlinkert/to-regex-range) | [homepage](https://github.com/jonschlinkert/to-regex-range "Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than 2.87 million test assertions.")
### Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 103 | [jonschlinkert](https://github.com/jonschlinkert) |
| 2 | [paulmillr](https://github.com/paulmillr) |
| 1 | [edorivai](https://github.com/edorivai) |
| 1 | [wtgtybhertgeghgtwtg](https://github.com/wtgtybhertgeghgtwtg) |
### Building docs
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
### Running tests
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
### Author
**Jon Schlinkert**
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
### License
Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.5.0, on April 23, 2017._
+208
View File
@@ -0,0 +1,208 @@
/*!
* fill-range <https://github.com/jonschlinkert/fill-range>
*
* Copyright (c) 2014-2015, 2017, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
var util = require('util');
var isNumber = require('is-number');
var extend = require('extend-shallow');
var repeat = require('repeat-string');
var toRegex = require('to-regex-range');
/**
* Return a range of numbers or letters.
*
* @param {String} `start` Start of the range
* @param {String} `stop` End of the range
* @param {String} `step` Increment or decrement to use.
* @param {Function} `fn` Custom function to modify each element in the range.
* @return {Array}
*/
function fillRange(start, stop, step, options) {
if (typeof start === 'undefined') {
return [];
}
if (typeof stop === 'undefined' || start === stop) {
// special case, for handling negative zero
var isString = typeof start === 'string';
if (isNumber(start) && !toNumber(start)) {
return [isString ? '0' : 0];
}
return [start];
}
if (typeof step !== 'number' && typeof step !== 'string') {
options = step;
step = undefined;
}
if (typeof options === 'function') {
options = { transform: options };
}
var opts = extend({step: step}, options);
if (opts.step && !isValidNumber(opts.step)) {
if (opts.strictRanges === true) {
throw new TypeError('expected options.step to be a number');
}
return [];
}
opts.isNumber = isValidNumber(start) && isValidNumber(stop);
if (!opts.isNumber && !isValid(start, stop)) {
if (opts.strictRanges === true) {
throw new RangeError('invalid range arguments: ' + util.inspect([start, stop]));
}
return [];
}
opts.isPadded = isPadded(start) || isPadded(stop);
opts.toString = opts.stringify
|| typeof opts.step === 'string'
|| typeof start === 'string'
|| typeof stop === 'string'
|| !opts.isNumber;
if (opts.isPadded) {
opts.maxLength = Math.max(String(start).length, String(stop).length);
}
// support legacy minimatch/fill-range options
if (typeof opts.optimize === 'boolean') opts.toRegex = opts.optimize;
if (typeof opts.makeRe === 'boolean') opts.toRegex = opts.makeRe;
return expand(start, stop, opts);
}
function expand(start, stop, options) {
var a = options.isNumber ? toNumber(start) : start.charCodeAt(0);
var b = options.isNumber ? toNumber(stop) : stop.charCodeAt(0);
var step = Math.abs(toNumber(options.step)) || 1;
if (options.toRegex && step === 1) {
return toRange(a, b, start, stop, options);
}
var zero = {greater: [], lesser: []};
var asc = a < b;
var arr = new Array(Math.round((asc ? b - a : a - b) / step));
var idx = 0;
while (asc ? a <= b : a >= b) {
var val = options.isNumber ? a : String.fromCharCode(a);
if (options.toRegex && (val >= 0 || !options.isNumber)) {
zero.greater.push(val);
} else {
zero.lesser.push(Math.abs(val));
}
if (options.isPadded) {
val = zeros(val, options);
}
if (options.toString) {
val = String(val);
}
if (typeof options.transform === 'function') {
arr[idx++] = options.transform(val, a, b, step, idx, arr, options);
} else {
arr[idx++] = val;
}
if (asc) {
a += step;
} else {
a -= step;
}
}
if (options.toRegex === true) {
return toSequence(arr, zero, options);
}
return arr;
}
function toRange(a, b, start, stop, options) {
if (options.isPadded) {
return toRegex(start, stop, options);
}
if (options.isNumber) {
return toRegex(Math.min(a, b), Math.max(a, b), options);
}
var start = String.fromCharCode(Math.min(a, b));
var stop = String.fromCharCode(Math.max(a, b));
return '[' + start + '-' + stop + ']';
}
function toSequence(arr, zeros, options) {
var greater = '', lesser = '';
if (zeros.greater.length) {
greater = zeros.greater.join('|');
}
if (zeros.lesser.length) {
lesser = '-(' + zeros.lesser.join('|') + ')';
}
var res = greater && lesser
? greater + '|' + lesser
: greater || lesser;
if (options.capture) {
return '(' + res + ')';
}
return res;
}
function zeros(val, options) {
if (options.isPadded) {
var str = String(val);
var len = str.length;
var dash = '';
if (str.charAt(0) === '-') {
dash = '-';
str = str.slice(1);
}
var diff = options.maxLength - len;
var pad = repeat('0', diff);
val = (dash + pad + str);
}
if (options.stringify) {
return String(val);
}
return val;
}
function toNumber(val) {
return Number(val) || 0;
}
function isPadded(str) {
return /^-?0\d/.test(str);
}
function isValid(min, max) {
return (isValidNumber(min) || isValidLetter(min))
&& (isValidNumber(max) || isValidLetter(max));
}
function isValidLetter(ch) {
return typeof ch === 'string' && ch.length === 1 && /^\w+$/.test(ch);
}
function isValidNumber(n) {
return isNumber(n) && !/\./.test(n);
}
/**
* Expose `fillRange`
* @type {Function}
*/
module.exports = fillRange;
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2015, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,61 @@
# extend-shallow [![NPM version](https://badge.fury.io/js/extend-shallow.svg)](http://badge.fury.io/js/extend-shallow) [![Build Status](https://travis-ci.org/jonschlinkert/extend-shallow.svg)](https://travis-ci.org/jonschlinkert/extend-shallow)
> Extend an object with the properties of additional objects. node.js/javascript util.
## Install
Install with [npm](https://www.npmjs.com/)
```sh
$ npm i extend-shallow --save
```
## Usage
```js
var extend = require('extend-shallow');
extend({a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
Pass an empty object to shallow clone:
```js
var obj = {};
extend(obj, {a: 'b'}, {c: 'd'})
//=> {a: 'b', c: 'd'}
```
## Related
* [extend-shallow](https://github.com/jonschlinkert/extend-shallow): Extend an object with the properties of additional objects. node.js/javascript util.
* [for-own](https://github.com/jonschlinkert/for-own): Iterate over the own enumerable properties of an object, and return an object with properties… [more](https://github.com/jonschlinkert/for-own)
* [for-in](https://github.com/jonschlinkert/for-in): Iterate over the own and inherited enumerable properties of an objecte, and return an object… [more](https://github.com/jonschlinkert/for-in)
* [is-plain-object](https://github.com/jonschlinkert/is-plain-object): Returns true if an object was created by the `Object` constructor.
* [isobject](https://github.com/jonschlinkert/isobject): Returns true if the value is an object and not an array or null.
* [kind-of](https://github.com/jonschlinkert/kind-of): Get the native type of a value.
## Running tests
Install dev dependencies:
```sh
$ npm i -d && npm test
```
## Author
**Jon Schlinkert**
+ [github/jonschlinkert](https://github.com/jonschlinkert)
+ [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
## License
Copyright © 2015 Jon Schlinkert
Released under the MIT license.
***
_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on June 29, 2015._
@@ -0,0 +1,33 @@
'use strict';
var isObject = require('is-extendable');
module.exports = function extend(o/*, objects*/) {
if (!isObject(o)) { o = {}; }
var len = arguments.length;
for (var i = 1; i < len; i++) {
var obj = arguments[i];
if (isObject(obj)) {
assign(o, obj);
}
}
return o;
};
function assign(a, b) {
for (var key in b) {
if (hasOwn(b, key)) {
a[key] = b[key];
}
}
}
/**
* Returns true if the given `key` is an own property of `obj`.
*/
function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
@@ -0,0 +1,90 @@
{
"_args": [
[
"extend-shallow@2.0.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "extend-shallow@2.0.1",
"_id": "extend-shallow@2.0.1",
"_inBundle": false,
"_integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"_location": "/webpack/fill-range/extend-shallow",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "extend-shallow@2.0.1",
"name": "extend-shallow",
"escapedName": "extend-shallow",
"rawSpec": "2.0.1",
"saveSpec": null,
"fetchSpec": "2.0.1"
},
"_requiredBy": [
"/webpack/fill-range"
],
"_resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"_spec": "2.0.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/jonschlinkert/extend-shallow/issues"
},
"dependencies": {
"is-extendable": "^0.1.0"
},
"description": "Extend an object with the properties of additional objects. node.js/javascript util.",
"devDependencies": {
"array-slice": "^0.2.3",
"benchmarked": "^0.1.4",
"chalk": "^1.0.0",
"for-own": "^0.1.3",
"glob": "^5.0.12",
"is-plain-object": "^2.0.1",
"kind-of": "^2.0.0",
"minimist": "^1.1.1",
"mocha": "^2.2.5",
"should": "^7.0.1"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/jonschlinkert/extend-shallow",
"keywords": [
"assign",
"extend",
"javascript",
"js",
"keys",
"merge",
"obj",
"object",
"prop",
"properties",
"property",
"props",
"shallow",
"util",
"utility",
"utils",
"value"
],
"license": "MIT",
"main": "index.js",
"name": "extend-shallow",
"repository": {
"type": "git",
"url": "git+https://github.com/jonschlinkert/extend-shallow.git"
},
"scripts": {
"test": "mocha"
},
"version": "2.0.1"
}
+133
View File
@@ -0,0 +1,133 @@
{
"_args": [
[
"fill-range@4.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "fill-range@4.0.0",
"_id": "fill-range@4.0.0",
"_inBundle": false,
"_integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"_location": "/webpack/fill-range",
"_phantomChildren": {
"is-extendable": "0.1.1"
},
"_requested": {
"type": "version",
"registry": true,
"raw": "fill-range@4.0.0",
"name": "fill-range",
"escapedName": "fill-range",
"rawSpec": "4.0.0",
"saveSpec": null,
"fetchSpec": "4.0.0"
},
"_requiredBy": [
"/webpack/braces"
],
"_resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"_spec": "4.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/jonschlinkert/fill-range/issues"
},
"contributors": [
{
"email": "wtgtybhertgeghgtwtg@gmail.com",
"url": "https://github.com/wtgtybhertgeghgtwtg"
},
{
"name": "Edo Rivai",
"email": "edo.rivai@gmail.com",
"url": "edo.rivai.nl"
},
{
"name": "Jon Schlinkert",
"email": "jon.schlinkert@sellside.com",
"url": "http://twitter.com/jonschlinkert"
},
{
"name": "Paul Miller",
"email": "paul+gh@paulmillr.com",
"url": "paulmillr.com"
}
],
"dependencies": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"description": "Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`",
"devDependencies": {
"ansi-cyan": "^0.1.1",
"benchmarked": "^1.0.0",
"gulp-format-md": "^0.1.12",
"minimist": "^1.2.0",
"mocha": "^3.2.0"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/jonschlinkert/fill-range",
"keywords": [
"alpha",
"alphabetical",
"array",
"bash",
"brace",
"expand",
"expansion",
"fill",
"glob",
"match",
"matches",
"matching",
"number",
"numerical",
"range",
"ranges",
"regex",
"sh"
],
"license": "MIT",
"main": "index.js",
"name": "fill-range",
"repository": {
"type": "git",
"url": "git+https://github.com/jonschlinkert/fill-range.git"
},
"scripts": {
"test": "mocha"
},
"verb": {
"related": {
"list": [
"braces",
"expand-range",
"micromatch",
"to-regex-range"
]
},
"toc": true,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
}
},
"version": "4.0.0"
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2016, Jon Schlinkert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+115
View File
@@ -0,0 +1,115 @@
# is-number [![NPM version](https://img.shields.io/npm/v/is-number.svg?style=flat)](https://www.npmjs.com/package/is-number) [![NPM downloads](https://img.shields.io/npm/dm/is-number.svg?style=flat)](https://npmjs.org/package/is-number) [![Build Status](https://img.shields.io/travis/jonschlinkert/is-number.svg?style=flat)](https://travis-ci.org/jonschlinkert/is-number)
> Returns true if the value is a number. comprehensive tests.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save is-number
```
## Usage
To understand some of the rationale behind the decisions made in this library (and to learn about some oddities of number evaluation in JavaScript), [see this gist](https://gist.github.com/jonschlinkert/e30c70c713da325d0e81).
```js
var isNumber = require('is-number');
```
### true
See the [tests](./test.js) for more examples.
```js
isNumber(5e3) //=> 'true'
isNumber(0xff) //=> 'true'
isNumber(-1.1) //=> 'true'
isNumber(0) //=> 'true'
isNumber(1) //=> 'true'
isNumber(1.1) //=> 'true'
isNumber(10) //=> 'true'
isNumber(10.10) //=> 'true'
isNumber(100) //=> 'true'
isNumber('-1.1') //=> 'true'
isNumber('0') //=> 'true'
isNumber('012') //=> 'true'
isNumber('0xff') //=> 'true'
isNumber('1') //=> 'true'
isNumber('1.1') //=> 'true'
isNumber('10') //=> 'true'
isNumber('10.10') //=> 'true'
isNumber('100') //=> 'true'
isNumber('5e3') //=> 'true'
isNumber(parseInt('012')) //=> 'true'
isNumber(parseFloat('012')) //=> 'true'
```
### False
See the [tests](./test.js) for more examples.
```js
isNumber('foo') //=> 'false'
isNumber([1]) //=> 'false'
isNumber([]) //=> 'false'
isNumber(function () {}) //=> 'false'
isNumber(Infinity) //=> 'false'
isNumber(NaN) //=> 'false'
isNumber(new Array('abc')) //=> 'false'
isNumber(new Array(2)) //=> 'false'
isNumber(new Buffer('abc')) //=> 'false'
isNumber(null) //=> 'false'
isNumber(undefined) //=> 'false'
isNumber({abc: 'abc'}) //=> 'false'
```
## About
### Related projects
* [even](https://www.npmjs.com/package/even): Get the even numbered items from an array. | [homepage](https://github.com/jonschlinkert/even "Get the even numbered items from an array.")
* [is-even](https://www.npmjs.com/package/is-even): Return true if the given number is even. | [homepage](https://github.com/jonschlinkert/is-even "Return true if the given number is even.")
* [is-odd](https://www.npmjs.com/package/is-odd): Returns true if the given number is odd. | [homepage](https://github.com/jonschlinkert/is-odd "Returns true if the given number is odd.")
* [is-primitive](https://www.npmjs.com/package/is-primitive): Returns `true` if the value is a primitive. | [homepage](https://github.com/jonschlinkert/is-primitive "Returns `true` if the value is a primitive. ")
* [kind-of](https://www.npmjs.com/package/kind-of): Get the native type of a value. | [homepage](https://github.com/jonschlinkert/kind-of "Get the native type of a value.")
* [odd](https://www.npmjs.com/package/odd): Get the odd numbered items from an array. | [homepage](https://github.com/jonschlinkert/odd "Get the odd numbered items from an array.")
### Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
### Building docs
_(This document was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme) (a [verb](https://github.com/verbose/verb) generator), please don't edit the readme directly. Any changes to the readme must be made in [.verb.md](.verb.md).)_
To generate the readme and API documentation with [verb](https://github.com/verbose/verb):
```sh
$ npm install -g verb verb-generate-readme && verb
```
### Running tests
Install dev dependencies:
```sh
$ npm install -d && npm test
```
### Author
**Jon Schlinkert**
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](http://twitter.com/jonschlinkert)
### License
Copyright © 2016, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT license](https://github.com/jonschlinkert/is-number/blob/master/LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.1.30, on September 10, 2016._
+22
View File
@@ -0,0 +1,22 @@
/*!
* is-number <https://github.com/jonschlinkert/is-number>
*
* Copyright (c) 2014-2015, Jon Schlinkert.
* Licensed under the MIT License.
*/
'use strict';
var typeOf = require('kind-of');
module.exports = function isNumber(num) {
var type = typeOf(num);
if (type === 'string') {
if (!num.trim()) return false;
} else if (type !== 'number') {
return false;
}
return (num - num + 1) >= 0;
};
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,261 @@
# kind-of [![NPM version](https://img.shields.io/npm/v/kind-of.svg?style=flat)](https://www.npmjs.com/package/kind-of) [![NPM monthly downloads](https://img.shields.io/npm/dm/kind-of.svg?style=flat)](https://npmjs.org/package/kind-of) [![NPM total downloads](https://img.shields.io/npm/dt/kind-of.svg?style=flat)](https://npmjs.org/package/kind-of) [![Linux Build Status](https://img.shields.io/travis/jonschlinkert/kind-of.svg?style=flat&label=Travis)](https://travis-ci.org/jonschlinkert/kind-of)
> Get the native type of a value.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save kind-of
```
## Install
Install with [bower](https://bower.io/)
```sh
$ bower install kind-of --save
```
## Usage
> es5, browser and es6 ready
```js
var kindOf = require('kind-of');
kindOf(undefined);
//=> 'undefined'
kindOf(null);
//=> 'null'
kindOf(true);
//=> 'boolean'
kindOf(false);
//=> 'boolean'
kindOf(new Boolean(true));
//=> 'boolean'
kindOf(new Buffer(''));
//=> 'buffer'
kindOf(42);
//=> 'number'
kindOf(new Number(42));
//=> 'number'
kindOf('str');
//=> 'string'
kindOf(new String('str'));
//=> 'string'
kindOf(arguments);
//=> 'arguments'
kindOf({});
//=> 'object'
kindOf(Object.create(null));
//=> 'object'
kindOf(new Test());
//=> 'object'
kindOf(new Date());
//=> 'date'
kindOf([]);
//=> 'array'
kindOf([1, 2, 3]);
//=> 'array'
kindOf(new Array());
//=> 'array'
kindOf(/foo/);
//=> 'regexp'
kindOf(new RegExp('foo'));
//=> 'regexp'
kindOf(function () {});
//=> 'function'
kindOf(function * () {});
//=> 'function'
kindOf(new Function());
//=> 'function'
kindOf(new Map());
//=> 'map'
kindOf(new WeakMap());
//=> 'weakmap'
kindOf(new Set());
//=> 'set'
kindOf(new WeakSet());
//=> 'weakset'
kindOf(Symbol('str'));
//=> 'symbol'
kindOf(new Int8Array());
//=> 'int8array'
kindOf(new Uint8Array());
//=> 'uint8array'
kindOf(new Uint8ClampedArray());
//=> 'uint8clampedarray'
kindOf(new Int16Array());
//=> 'int16array'
kindOf(new Uint16Array());
//=> 'uint16array'
kindOf(new Int32Array());
//=> 'int32array'
kindOf(new Uint32Array());
//=> 'uint32array'
kindOf(new Float32Array());
//=> 'float32array'
kindOf(new Float64Array());
//=> 'float64array'
```
## Benchmarks
Benchmarked against [typeof](http://github.com/CodingFu/typeof) and [type-of](https://github.com/ForbesLindesay/type-of).
Note that performaces is slower for es6 features `Map`, `WeakMap`, `Set` and `WeakSet`.
```bash
#1: array
current x 23,329,397 ops/sec ±0.82% (94 runs sampled)
lib-type-of x 4,170,273 ops/sec ±0.55% (94 runs sampled)
lib-typeof x 9,686,935 ops/sec ±0.59% (98 runs sampled)
#2: boolean
current x 27,197,115 ops/sec ±0.85% (94 runs sampled)
lib-type-of x 3,145,791 ops/sec ±0.73% (97 runs sampled)
lib-typeof x 9,199,562 ops/sec ±0.44% (99 runs sampled)
#3: date
current x 20,190,117 ops/sec ±0.86% (92 runs sampled)
lib-type-of x 5,166,970 ops/sec ±0.74% (94 runs sampled)
lib-typeof x 9,610,821 ops/sec ±0.50% (96 runs sampled)
#4: function
current x 23,855,460 ops/sec ±0.60% (97 runs sampled)
lib-type-of x 5,667,740 ops/sec ±0.54% (100 runs sampled)
lib-typeof x 10,010,644 ops/sec ±0.44% (100 runs sampled)
#5: null
current x 27,061,047 ops/sec ±0.97% (96 runs sampled)
lib-type-of x 13,965,573 ops/sec ±0.62% (97 runs sampled)
lib-typeof x 8,460,194 ops/sec ±0.61% (97 runs sampled)
#6: number
current x 25,075,682 ops/sec ±0.53% (99 runs sampled)
lib-type-of x 2,266,405 ops/sec ±0.41% (98 runs sampled)
lib-typeof x 9,821,481 ops/sec ±0.45% (99 runs sampled)
#7: object
current x 3,348,980 ops/sec ±0.49% (99 runs sampled)
lib-type-of x 3,245,138 ops/sec ±0.60% (94 runs sampled)
lib-typeof x 9,262,952 ops/sec ±0.59% (99 runs sampled)
#8: regex
current x 21,284,827 ops/sec ±0.72% (96 runs sampled)
lib-type-of x 4,689,241 ops/sec ±0.43% (100 runs sampled)
lib-typeof x 8,957,593 ops/sec ±0.62% (98 runs sampled)
#9: string
current x 25,379,234 ops/sec ±0.58% (96 runs sampled)
lib-type-of x 3,635,148 ops/sec ±0.76% (93 runs sampled)
lib-typeof x 9,494,134 ops/sec ±0.49% (98 runs sampled)
#10: undef
current x 27,459,221 ops/sec ±1.01% (93 runs sampled)
lib-type-of x 14,360,433 ops/sec ±0.52% (99 runs sampled)
lib-typeof x 23,202,868 ops/sec ±0.59% (94 runs sampled)
```
## Optimizations
In 7 out of 8 cases, this library is 2x-10x faster than other top libraries included in the benchmarks. There are a few things that lead to this performance advantage, none of them hard and fast rules, but all of them simple and repeatable in almost any code library:
1. Optimize around the fastest and most common use cases first. Of course, this will change from project-to-project, but I took some time to understand how and why `typeof` checks were being used in my own libraries and other libraries I use a lot.
2. Optimize around bottlenecks - In other words, the order in which conditionals are implemented is significant, because each check is only as fast as the failing checks that came before it. Here, the biggest bottleneck by far is checking for plain objects (an object that was created by the `Object` constructor). I opted to make this check happen by process of elimination rather than brute force up front (e.g. by using something like `val.constructor.name`), so that every other type check would not be penalized it.
3. Don't do uneccessary processing - why do `.slice(8, -1).toLowerCase();` just to get the word `regex`? It's much faster to do `if (type === '[object RegExp]') return 'regex'`
## About
### Related projects
* [is-glob](https://www.npmjs.com/package/is-glob): Returns `true` if the given string looks like a glob pattern or an extglob pattern… [more](https://github.com/jonschlinkert/is-glob) | [homepage](https://github.com/jonschlinkert/is-glob "Returns `true` if the given string looks like a glob pattern or an extglob pattern. This makes it easy to create code that only uses external modules like node-glob when necessary, resulting in much faster code execution and initialization time, and a bet")
* [is-number](https://www.npmjs.com/package/is-number): Returns true if the value is a number. comprehensive tests. | [homepage](https://github.com/jonschlinkert/is-number "Returns true if the value is a number. comprehensive tests.")
* [is-primitive](https://www.npmjs.com/package/is-primitive): Returns `true` if the value is a primitive. | [homepage](https://github.com/jonschlinkert/is-primitive "Returns `true` if the value is a primitive. ")
### Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
### Contributors
| **Commits** | **Contributor** |
| --- | --- |
| 59 | [jonschlinkert](https://github.com/jonschlinkert) |
| 2 | [miguelmota](https://github.com/miguelmota) |
| 1 | [dtothefp](https://github.com/dtothefp) |
| 1 | [ksheedlo](https://github.com/ksheedlo) |
| 1 | [pdehaan](https://github.com/pdehaan) |
| 1 | [laggingreflex](https://github.com/laggingreflex) |
### Building docs
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
### Running tests
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
### Author
**Jon Schlinkert**
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
### License
Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on May 16, 2017._
@@ -0,0 +1,116 @@
var isBuffer = require('is-buffer');
var toString = Object.prototype.toString;
/**
* Get the native `typeof` a value.
*
* @param {*} `val`
* @return {*} Native javascript type
*/
module.exports = function kindOf(val) {
// primitivies
if (typeof val === 'undefined') {
return 'undefined';
}
if (val === null) {
return 'null';
}
if (val === true || val === false || val instanceof Boolean) {
return 'boolean';
}
if (typeof val === 'string' || val instanceof String) {
return 'string';
}
if (typeof val === 'number' || val instanceof Number) {
return 'number';
}
// functions
if (typeof val === 'function' || val instanceof Function) {
return 'function';
}
// array
if (typeof Array.isArray !== 'undefined' && Array.isArray(val)) {
return 'array';
}
// check for instances of RegExp and Date before calling `toString`
if (val instanceof RegExp) {
return 'regexp';
}
if (val instanceof Date) {
return 'date';
}
// other objects
var type = toString.call(val);
if (type === '[object RegExp]') {
return 'regexp';
}
if (type === '[object Date]') {
return 'date';
}
if (type === '[object Arguments]') {
return 'arguments';
}
if (type === '[object Error]') {
return 'error';
}
// buffer
if (isBuffer(val)) {
return 'buffer';
}
// es6: Map, WeakMap, Set, WeakSet
if (type === '[object Set]') {
return 'set';
}
if (type === '[object WeakSet]') {
return 'weakset';
}
if (type === '[object Map]') {
return 'map';
}
if (type === '[object WeakMap]') {
return 'weakmap';
}
if (type === '[object Symbol]') {
return 'symbol';
}
// typed arrays
if (type === '[object Int8Array]') {
return 'int8array';
}
if (type === '[object Uint8Array]') {
return 'uint8array';
}
if (type === '[object Uint8ClampedArray]') {
return 'uint8clampedarray';
}
if (type === '[object Int16Array]') {
return 'int16array';
}
if (type === '[object Uint16Array]') {
return 'uint16array';
}
if (type === '[object Int32Array]') {
return 'int32array';
}
if (type === '[object Uint32Array]') {
return 'uint32array';
}
if (type === '[object Float32Array]') {
return 'float32array';
}
if (type === '[object Float64Array]') {
return 'float64array';
}
// must be a plain object
return 'object';
};
@@ -0,0 +1,142 @@
{
"_args": [
[
"kind-of@3.2.2",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "kind-of@3.2.2",
"_id": "kind-of@3.2.2",
"_inBundle": false,
"_integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"_location": "/webpack/is-number/kind-of",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "kind-of@3.2.2",
"name": "kind-of",
"escapedName": "kind-of",
"rawSpec": "3.2.2",
"saveSpec": null,
"fetchSpec": "3.2.2"
},
"_requiredBy": [
"/webpack/is-number"
],
"_resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"_spec": "3.2.2",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/jonschlinkert/kind-of/issues"
},
"contributors": [
{
"name": "David Fox-Powell",
"url": "https://dtothefp.github.io/me"
},
{
"name": "Jon Schlinkert",
"url": "http://twitter.com/jonschlinkert"
},
{
"name": "Ken Sheedlo",
"url": "kensheedlo.com"
},
{
"name": "laggingreflex",
"url": "https://github.com/laggingreflex"
},
{
"name": "Miguel Mota",
"url": "https://miguelmota.com"
},
{
"name": "Peter deHaan",
"url": "http://about.me/peterdehaan"
}
],
"dependencies": {
"is-buffer": "^1.1.5"
},
"description": "Get the native type of a value.",
"devDependencies": {
"ansi-bold": "^0.1.1",
"benchmarked": "^1.0.0",
"browserify": "^14.3.0",
"glob": "^7.1.1",
"gulp-format-md": "^0.1.12",
"mocha": "^3.3.0",
"type-of": "^2.0.1",
"typeof": "^1.0.0"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/jonschlinkert/kind-of",
"keywords": [
"arguments",
"array",
"boolean",
"check",
"date",
"function",
"is",
"is-type",
"is-type-of",
"kind",
"kind-of",
"number",
"object",
"of",
"regexp",
"string",
"test",
"type",
"type-of",
"typeof",
"types"
],
"license": "MIT",
"main": "index.js",
"name": "kind-of",
"repository": {
"type": "git",
"url": "git+https://github.com/jonschlinkert/kind-of.git"
},
"scripts": {
"prepublish": "browserify -o browser.js -e index.js -s index --bare",
"test": "mocha"
},
"verb": {
"related": {
"list": [
"is-glob",
"is-number",
"is-primitive"
]
},
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
},
"reflinks": [
"verb"
]
},
"version": "3.2.2"
}
+127
View File
@@ -0,0 +1,127 @@
{
"_args": [
[
"is-number@3.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "is-number@3.0.0",
"_id": "is-number@3.0.0",
"_inBundle": false,
"_integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"_location": "/webpack/is-number",
"_phantomChildren": {
"is-buffer": "1.1.6"
},
"_requested": {
"type": "version",
"registry": true,
"raw": "is-number@3.0.0",
"name": "is-number",
"escapedName": "is-number",
"rawSpec": "3.0.0",
"saveSpec": null,
"fetchSpec": "3.0.0"
},
"_requiredBy": [
"/webpack/fill-range",
"/webpack/to-regex-range"
],
"_resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"_spec": "3.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/jonschlinkert/is-number/issues"
},
"contributors": [
{
"name": "Charlike Mike Reagent",
"url": "http://www.tunnckocore.tk"
},
{
"name": "Jon Schlinkert",
"email": "jon.schlinkert@sellside.com",
"url": "http://twitter.com/jonschlinkert"
}
],
"dependencies": {
"kind-of": "^3.0.2"
},
"description": "Returns true if the value is a number. comprehensive tests.",
"devDependencies": {
"benchmarked": "^0.2.5",
"chalk": "^1.1.3",
"gulp-format-md": "^0.1.10",
"mocha": "^3.0.2"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/jonschlinkert/is-number",
"keywords": [
"check",
"coerce",
"coercion",
"integer",
"is",
"is-nan",
"is-num",
"is-number",
"istype",
"kind",
"math",
"nan",
"num",
"number",
"numeric",
"test",
"type",
"typeof",
"value"
],
"license": "MIT",
"main": "index.js",
"name": "is-number",
"repository": {
"type": "git",
"url": "git+https://github.com/jonschlinkert/is-number.git"
},
"scripts": {
"test": "mocha"
},
"verb": {
"related": {
"list": [
"even",
"is-even",
"is-odd",
"is-primitive",
"kind-of",
"odd"
]
},
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
},
"reflinks": [
"verb",
"verb-generate-readme"
]
},
"version": "3.0.0"
}
+37
View File
@@ -0,0 +1,37 @@
## History
### key
Changelog entries are classified using the following labels _(from [keep-a-changelog][]_):
- `added`: for new features
- `changed`: for changes in existing functionality
- `deprecated`: for once-stable features removed in upcoming releases
- `removed`: for deprecated features removed in this release
- `fixed`: for any bug fixes
- `bumped`: updated dependencies, only minor or higher will be listed.
### [3.0.0] - 2017-04-11
TODO. There should be no breaking changes. Please report any regressions. I will [reformat these release notes](https://github.com/micromatch/micromatch/pull/76) and add them to the changelog as soon as I have a chance.
### [1.0.1] - 2016-12-12
**Added**
- Support for windows path edge cases where backslashes are used in brackets or other unusual combinations.
### [1.0.0] - 2016-12-12
Stable release.
### [0.1.0] - 2016-10-08
First release.
[Unreleased]: https://github.com/jonschlinkert/micromatch/compare/0.1.0...HEAD
[0.2.0]: https://github.com/jonschlinkert/micromatch/compare/0.1.0...0.2.0
[keep-a-changelog]: https://github.com/olivierlacan/keep-a-changelog
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014-2018, Jon Schlinkert.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large Load Diff
+877
View File
@@ -0,0 +1,877 @@
'use strict';
/**
* Module dependencies
*/
var util = require('util');
var braces = require('braces');
var toRegex = require('to-regex');
var extend = require('extend-shallow');
/**
* Local dependencies
*/
var compilers = require('./lib/compilers');
var parsers = require('./lib/parsers');
var cache = require('./lib/cache');
var utils = require('./lib/utils');
var MAX_LENGTH = 1024 * 64;
/**
* The main function takes a list of strings and one or more
* glob patterns to use for matching.
*
* ```js
* var mm = require('micromatch');
* mm(list, patterns[, options]);
*
* console.log(mm(['a.js', 'a.txt'], ['*.js']));
* //=> [ 'a.js' ]
* ```
* @param {Array} `list` A list of strings to match
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Array} Returns an array of matches
* @summary false
* @api public
*/
function micromatch(list, patterns, options) {
patterns = utils.arrayify(patterns);
list = utils.arrayify(list);
var len = patterns.length;
if (list.length === 0 || len === 0) {
return [];
}
if (len === 1) {
return micromatch.match(list, patterns[0], options);
}
var omit = [];
var keep = [];
var idx = -1;
while (++idx < len) {
var pattern = patterns[idx];
if (typeof pattern === 'string' && pattern.charCodeAt(0) === 33 /* ! */) {
omit.push.apply(omit, micromatch.match(list, pattern.slice(1), options));
} else {
keep.push.apply(keep, micromatch.match(list, pattern, options));
}
}
var matches = utils.diff(keep, omit);
if (!options || options.nodupes !== false) {
return utils.unique(matches);
}
return matches;
}
/**
* Similar to the main function, but `pattern` must be a string.
*
* ```js
* var mm = require('micromatch');
* mm.match(list, pattern[, options]);
*
* console.log(mm.match(['a.a', 'a.aa', 'a.b', 'a.c'], '*.a'));
* //=> ['a.a', 'a.aa']
* ```
* @param {Array} `list` Array of strings to match
* @param {String} `pattern` Glob pattern to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Array} Returns an array of matches
* @api public
*/
micromatch.match = function(list, pattern, options) {
if (Array.isArray(pattern)) {
throw new TypeError('expected pattern to be a string');
}
var unixify = utils.unixify(options);
var isMatch = memoize('match', pattern, options, micromatch.matcher);
var matches = [];
list = utils.arrayify(list);
var len = list.length;
var idx = -1;
while (++idx < len) {
var ele = list[idx];
if (ele === pattern || isMatch(ele)) {
matches.push(utils.value(ele, unixify, options));
}
}
// if no options were passed, uniquify results and return
if (typeof options === 'undefined') {
return utils.unique(matches);
}
if (matches.length === 0) {
if (options.failglob === true) {
throw new Error('no matches found for "' + pattern + '"');
}
if (options.nonull === true || options.nullglob === true) {
return [options.unescape ? utils.unescape(pattern) : pattern];
}
}
// if `opts.ignore` was defined, diff ignored list
if (options.ignore) {
matches = micromatch.not(matches, options.ignore, options);
}
return options.nodupes !== false ? utils.unique(matches) : matches;
};
/**
* Returns true if the specified `string` matches the given glob `pattern`.
*
* ```js
* var mm = require('micromatch');
* mm.isMatch(string, pattern[, options]);
*
* console.log(mm.isMatch('a.a', '*.a'));
* //=> true
* console.log(mm.isMatch('a.b', '*.a'));
* //=> false
* ```
* @param {String} `string` String to match
* @param {String} `pattern` Glob pattern to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if the string matches the glob pattern.
* @api public
*/
micromatch.isMatch = function(str, pattern, options) {
if (typeof str !== 'string') {
throw new TypeError('expected a string: "' + util.inspect(str) + '"');
}
if (isEmptyString(str) || isEmptyString(pattern)) {
return false;
}
var equals = utils.equalsPattern(options);
if (equals(str)) {
return true;
}
var isMatch = memoize('isMatch', pattern, options, micromatch.matcher);
return isMatch(str);
};
/**
* Returns true if some of the strings in the given `list` match any of the
* given glob `patterns`.
*
* ```js
* var mm = require('micromatch');
* mm.some(list, patterns[, options]);
*
* console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
* // true
* console.log(mm.some(['foo.js'], ['*.js', '!foo.js']));
* // false
* ```
* @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.some = function(list, patterns, options) {
if (typeof list === 'string') {
list = [list];
}
for (var i = 0; i < list.length; i++) {
if (micromatch(list[i], patterns, options).length === 1) {
return true;
}
}
return false;
};
/**
* Returns true if every string in the given `list` matches
* any of the given glob `patterns`.
*
* ```js
* var mm = require('micromatch');
* mm.every(list, patterns[, options]);
*
* console.log(mm.every('foo.js', ['foo.js']));
* // true
* console.log(mm.every(['foo.js', 'bar.js'], ['*.js']));
* // true
* console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js']));
* // false
* console.log(mm.every(['foo.js'], ['*.js', '!foo.js']));
* // false
* ```
* @param {String|Array} `list` The string or array of strings to test.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.every = function(list, patterns, options) {
if (typeof list === 'string') {
list = [list];
}
for (var i = 0; i < list.length; i++) {
if (micromatch(list[i], patterns, options).length !== 1) {
return false;
}
}
return true;
};
/**
* Returns true if **any** of the given glob `patterns`
* match the specified `string`.
*
* ```js
* var mm = require('micromatch');
* mm.any(string, patterns[, options]);
*
* console.log(mm.any('a.a', ['b.*', '*.a']));
* //=> true
* console.log(mm.any('a.a', 'b.*'));
* //=> false
* ```
* @param {String|Array} `str` The string to test.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.any = function(str, patterns, options) {
if (typeof str !== 'string') {
throw new TypeError('expected a string: "' + util.inspect(str) + '"');
}
if (isEmptyString(str) || isEmptyString(patterns)) {
return false;
}
if (typeof patterns === 'string') {
patterns = [patterns];
}
for (var i = 0; i < patterns.length; i++) {
if (micromatch.isMatch(str, patterns[i], options)) {
return true;
}
}
return false;
};
/**
* Returns true if **all** of the given `patterns` match
* the specified string.
*
* ```js
* var mm = require('micromatch');
* mm.all(string, patterns[, options]);
*
* console.log(mm.all('foo.js', ['foo.js']));
* // true
*
* console.log(mm.all('foo.js', ['*.js', '!foo.js']));
* // false
*
* console.log(mm.all('foo.js', ['*.js', 'foo.js']));
* // true
*
* console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js']));
* // true
* ```
* @param {String|Array} `str` The string to test.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if any patterns match `str`
* @api public
*/
micromatch.all = function(str, patterns, options) {
if (typeof str !== 'string') {
throw new TypeError('expected a string: "' + util.inspect(str) + '"');
}
if (typeof patterns === 'string') {
patterns = [patterns];
}
for (var i = 0; i < patterns.length; i++) {
if (!micromatch.isMatch(str, patterns[i], options)) {
return false;
}
}
return true;
};
/**
* Returns a list of strings that _**do not match any**_ of the given `patterns`.
*
* ```js
* var mm = require('micromatch');
* mm.not(list, patterns[, options]);
*
* console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a'));
* //=> ['b.b', 'c.c']
* ```
* @param {Array} `list` Array of strings to match.
* @param {String|Array} `patterns` One or more glob pattern to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Array} Returns an array of strings that **do not match** the given patterns.
* @api public
*/
micromatch.not = function(list, patterns, options) {
var opts = extend({}, options);
var ignore = opts.ignore;
delete opts.ignore;
var unixify = utils.unixify(opts);
list = utils.arrayify(list).map(unixify);
var matches = utils.diff(list, micromatch(list, patterns, opts));
if (ignore) {
matches = utils.diff(matches, micromatch(list, ignore));
}
return opts.nodupes !== false ? utils.unique(matches) : matches;
};
/**
* Returns true if the given `string` contains the given pattern. Similar
* to [.isMatch](#isMatch) but the pattern can match any part of the string.
*
* ```js
* var mm = require('micromatch');
* mm.contains(string, pattern[, options]);
*
* console.log(mm.contains('aa/bb/cc', '*b'));
* //=> true
* console.log(mm.contains('aa/bb/cc', '*d'));
* //=> false
* ```
* @param {String} `str` The string to match.
* @param {String|Array} `patterns` Glob pattern to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns true if the patter matches any part of `str`.
* @api public
*/
micromatch.contains = function(str, patterns, options) {
if (typeof str !== 'string') {
throw new TypeError('expected a string: "' + util.inspect(str) + '"');
}
if (typeof patterns === 'string') {
if (isEmptyString(str) || isEmptyString(patterns)) {
return false;
}
var equals = utils.equalsPattern(patterns, options);
if (equals(str)) {
return true;
}
var contains = utils.containsPattern(patterns, options);
if (contains(str)) {
return true;
}
}
var opts = extend({}, options, {contains: true});
return micromatch.any(str, patterns, opts);
};
/**
* Returns true if the given pattern and options should enable
* the `matchBase` option.
* @return {Boolean}
* @api private
*/
micromatch.matchBase = function(pattern, options) {
if (pattern && pattern.indexOf('/') !== -1 || !options) return false;
return options.basename === true || options.matchBase === true;
};
/**
* Filter the keys of the given object with the given `glob` pattern
* and `options`. Does not attempt to match nested keys. If you need this feature,
* use [glob-object][] instead.
*
* ```js
* var mm = require('micromatch');
* mm.matchKeys(object, patterns[, options]);
*
* var obj = { aa: 'a', ab: 'b', ac: 'c' };
* console.log(mm.matchKeys(obj, '*b'));
* //=> { ab: 'b' }
* ```
* @param {Object} `object` The object with keys to filter.
* @param {String|Array} `patterns` One or more glob patterns to use for matching.
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Object} Returns an object with only keys that match the given patterns.
* @api public
*/
micromatch.matchKeys = function(obj, patterns, options) {
if (!utils.isObject(obj)) {
throw new TypeError('expected the first argument to be an object');
}
var keys = micromatch(Object.keys(obj), patterns, options);
return utils.pick(obj, keys);
};
/**
* Returns a memoized matcher function from the given glob `pattern` and `options`.
* The returned function takes a string to match as its only argument and returns
* true if the string is a match.
*
* ```js
* var mm = require('micromatch');
* mm.matcher(pattern[, options]);
*
* var isMatch = mm.matcher('*.!(*a)');
* console.log(isMatch('a.a'));
* //=> false
* console.log(isMatch('a.b'));
* //=> true
* ```
* @param {String} `pattern` Glob pattern
* @param {Object} `options` See available [options](#options) for changing how matches are performed.
* @return {Function} Returns a matcher function.
* @api public
*/
micromatch.matcher = function matcher(pattern, options) {
if (Array.isArray(pattern)) {
return compose(pattern, options, matcher);
}
// if pattern is a regex
if (pattern instanceof RegExp) {
return test(pattern);
}
// if pattern is invalid
if (!utils.isString(pattern)) {
throw new TypeError('expected pattern to be an array, string or regex');
}
// if pattern is a non-glob string
if (!utils.hasSpecialChars(pattern)) {
if (options && options.nocase === true) {
pattern = pattern.toLowerCase();
}
return utils.matchPath(pattern, options);
}
// if pattern is a glob string
var re = micromatch.makeRe(pattern, options);
// if `options.matchBase` or `options.basename` is defined
if (micromatch.matchBase(pattern, options)) {
return utils.matchBasename(re, options);
}
function test(regex) {
var equals = utils.equalsPattern(options);
var unixify = utils.unixify(options);
return function(str) {
if (equals(str)) {
return true;
}
if (regex.test(unixify(str))) {
return true;
}
return false;
};
}
var fn = test(re);
Object.defineProperty(fn, 'result', {
configurable: true,
enumerable: false,
value: re.result
});
return fn;
};
/**
* Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match.
*
* ```js
* var mm = require('micromatch');
* mm.capture(pattern, string[, options]);
*
* console.log(mm.capture('test/*.js', 'test/foo.js'));
* //=> ['foo']
* console.log(mm.capture('test/*.js', 'foo/bar.css'));
* //=> null
* ```
* @param {String} `pattern` Glob pattern to use for matching.
* @param {String} `string` String to match
* @param {Object} `options` See available [options](#options) for changing how matches are performed
* @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`.
* @api public
*/
micromatch.capture = function(pattern, str, options) {
var re = micromatch.makeRe(pattern, extend({capture: true}, options));
var unixify = utils.unixify(options);
function match() {
return function(string) {
var match = re.exec(unixify(string));
if (!match) {
return null;
}
return match.slice(1);
};
}
var capture = memoize('capture', pattern, options, match);
return capture(str);
};
/**
* Create a regular expression from the given glob `pattern`.
*
* ```js
* var mm = require('micromatch');
* mm.makeRe(pattern[, options]);
*
* console.log(mm.makeRe('*.js'));
* //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/
* ```
* @param {String} `pattern` A glob pattern to convert to regex.
* @param {Object} `options` See available [options](#options) for changing how matches are performed.
* @return {RegExp} Returns a regex created from the given pattern.
* @api public
*/
micromatch.makeRe = function(pattern, options) {
if (typeof pattern !== 'string') {
throw new TypeError('expected pattern to be a string');
}
if (pattern.length > MAX_LENGTH) {
throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters');
}
function makeRe() {
var result = micromatch.create(pattern, options);
var ast_array = [];
var output = result.map(function(obj) {
obj.ast.state = obj.state;
ast_array.push(obj.ast);
return obj.output;
});
var regex = toRegex(output.join('|'), options);
Object.defineProperty(regex, 'result', {
configurable: true,
enumerable: false,
value: ast_array
});
return regex;
}
return memoize('makeRe', pattern, options, makeRe);
};
/**
* Expand the given brace `pattern`.
*
* ```js
* var mm = require('micromatch');
* console.log(mm.braces('foo/{a,b}/bar'));
* //=> ['foo/(a|b)/bar']
*
* console.log(mm.braces('foo/{a,b}/bar', {expand: true}));
* //=> ['foo/(a|b)/bar']
* ```
* @param {String} `pattern` String with brace pattern to expand.
* @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options.
* @return {Array}
* @api public
*/
micromatch.braces = function(pattern, options) {
if (typeof pattern !== 'string' && !Array.isArray(pattern)) {
throw new TypeError('expected pattern to be an array or string');
}
function expand() {
if (options && options.nobrace === true || !/\{.*\}/.test(pattern)) {
return utils.arrayify(pattern);
}
return braces(pattern, options);
}
return memoize('braces', pattern, options, expand);
};
/**
* Proxy to the [micromatch.braces](#method), for parity with
* minimatch.
*/
micromatch.braceExpand = function(pattern, options) {
var opts = extend({}, options, {expand: true});
return micromatch.braces(pattern, opts);
};
/**
* Parses the given glob `pattern` and returns an array of abstract syntax
* trees (ASTs), with the compiled `output` and optional source `map` on
* each AST.
*
* ```js
* var mm = require('micromatch');
* mm.create(pattern[, options]);
*
* console.log(mm.create('abc/*.js'));
* // [{ options: { source: 'string', sourcemap: true },
* // state: {},
* // compilers:
* // { ... },
* // output: '(\\.[\\\\\\/])?abc\\/(?!\\.)(?=.)[^\\/]*?\\.js',
* // ast:
* // { type: 'root',
* // errors: [],
* // nodes:
* // [ ... ],
* // dot: false,
* // input: 'abc/*.js' },
* // parsingErrors: [],
* // map:
* // { version: 3,
* // sources: [ 'string' ],
* // names: [],
* // mappings: 'AAAA,GAAG,EAAC,kBAAC,EAAC,EAAE',
* // sourcesContent: [ 'abc/*.js' ] },
* // position: { line: 1, column: 28 },
* // content: {},
* // files: {},
* // idx: 6 }]
* ```
* @param {String} `pattern` Glob pattern to parse and compile.
* @param {Object} `options` Any [options](#options) to change how parsing and compiling is performed.
* @return {Object} Returns an object with the parsed AST, compiled string and optional source map.
* @api public
*/
micromatch.create = function(pattern, options) {
return memoize('create', pattern, options, function() {
function create(str, opts) {
return micromatch.compile(micromatch.parse(str, opts), opts);
}
pattern = micromatch.braces(pattern, options);
var len = pattern.length;
var idx = -1;
var res = [];
while (++idx < len) {
res.push(create(pattern[idx], options));
}
return res;
});
};
/**
* Parse the given `str` with the given `options`.
*
* ```js
* var mm = require('micromatch');
* mm.parse(pattern[, options]);
*
* var ast = mm.parse('a/{b,c}/d');
* console.log(ast);
* // { type: 'root',
* // errors: [],
* // input: 'a/{b,c}/d',
* // nodes:
* // [ { type: 'bos', val: '' },
* // { type: 'text', val: 'a/' },
* // { type: 'brace',
* // nodes:
* // [ { type: 'brace.open', val: '{' },
* // { type: 'text', val: 'b,c' },
* // { type: 'brace.close', val: '}' } ] },
* // { type: 'text', val: '/d' },
* // { type: 'eos', val: '' } ] }
* ```
* @param {String} `str`
* @param {Object} `options`
* @return {Object} Returns an AST
* @api public
*/
micromatch.parse = function(pattern, options) {
if (typeof pattern !== 'string') {
throw new TypeError('expected a string');
}
function parse() {
var snapdragon = utils.instantiate(null, options);
parsers(snapdragon, options);
var ast = snapdragon.parse(pattern, options);
utils.define(ast, 'snapdragon', snapdragon);
ast.input = pattern;
return ast;
}
return memoize('parse', pattern, options, parse);
};
/**
* Compile the given `ast` or string with the given `options`.
*
* ```js
* var mm = require('micromatch');
* mm.compile(ast[, options]);
*
* var ast = mm.parse('a/{b,c}/d');
* console.log(mm.compile(ast));
* // { options: { source: 'string' },
* // state: {},
* // compilers:
* // { eos: [Function],
* // noop: [Function],
* // bos: [Function],
* // brace: [Function],
* // 'brace.open': [Function],
* // text: [Function],
* // 'brace.close': [Function] },
* // output: [ 'a/(b|c)/d' ],
* // ast:
* // { ... },
* // parsingErrors: [] }
* ```
* @param {Object|String} `ast`
* @param {Object} `options`
* @return {Object} Returns an object that has an `output` property with the compiled string.
* @api public
*/
micromatch.compile = function(ast, options) {
if (typeof ast === 'string') {
ast = micromatch.parse(ast, options);
}
return memoize('compile', ast.input, options, function() {
var snapdragon = utils.instantiate(ast, options);
compilers(snapdragon, options);
return snapdragon.compile(ast, options);
});
};
/**
* Clear the regex cache.
*
* ```js
* mm.clearCache();
* ```
* @api public
*/
micromatch.clearCache = function() {
micromatch.cache.caches = {};
};
/**
* Returns true if the given value is effectively an empty string
*/
function isEmptyString(val) {
return String(val) === '' || String(val) === './';
}
/**
* Compose a matcher function with the given patterns.
* This allows matcher functions to be compiled once and
* called multiple times.
*/
function compose(patterns, options, matcher) {
var matchers;
return memoize('compose', String(patterns), options, function() {
return function(file) {
// delay composition until it's invoked the first time,
// after that it won't be called again
if (!matchers) {
matchers = [];
for (var i = 0; i < patterns.length; i++) {
matchers.push(matcher(patterns[i], options));
}
}
var len = matchers.length;
while (len--) {
if (matchers[len](file) === true) {
return true;
}
}
return false;
};
});
}
/**
* Memoize a generated regex or function. A unique key is generated
* from the `type` (usually method name), the `pattern`, and
* user-defined options.
*/
function memoize(type, pattern, options, fn) {
var key = utils.createKey(type + '=' + pattern, options);
if (options && options.cache === false) {
return fn(pattern, options);
}
if (cache.has(type, key)) {
return cache.get(type, key);
}
var val = fn(pattern, options);
cache.set(type, key, val);
return val;
}
/**
* Expose compiler, parser and cache on `micromatch`
*/
micromatch.compilers = compilers;
micromatch.parsers = parsers;
micromatch.caches = cache.caches;
/**
* Expose `micromatch`
* @type {Function}
*/
module.exports = micromatch;
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
module.exports = new (require('fragment-cache'))();
+77
View File
@@ -0,0 +1,77 @@
'use strict';
var nanomatch = require('nanomatch');
var extglob = require('extglob');
module.exports = function(snapdragon) {
var compilers = snapdragon.compiler.compilers;
var opts = snapdragon.options;
// register nanomatch compilers
snapdragon.use(nanomatch.compilers);
// get references to some specific nanomatch compilers before they
// are overridden by the extglob and/or custom compilers
var escape = compilers.escape;
var qmark = compilers.qmark;
var slash = compilers.slash;
var star = compilers.star;
var text = compilers.text;
var plus = compilers.plus;
var dot = compilers.dot;
// register extglob compilers or escape exglobs if disabled
if (opts.extglob === false || opts.noext === true) {
snapdragon.compiler.use(escapeExtglobs);
} else {
snapdragon.use(extglob.compilers);
}
snapdragon.use(function() {
this.options.star = this.options.star || function(/*node*/) {
return '[^\\\\/]*?';
};
});
// custom micromatch compilers
snapdragon.compiler
// reset referenced compiler
.set('dot', dot)
.set('escape', escape)
.set('plus', plus)
.set('slash', slash)
.set('qmark', qmark)
.set('star', star)
.set('text', text);
};
function escapeExtglobs(compiler) {
compiler.set('paren', function(node) {
var val = '';
visit(node, function(tok) {
if (tok.val) val += (/^\W/.test(tok.val) ? '\\' : '') + tok.val;
});
return this.emit(val, node);
});
/**
* Visit `node` with the given `fn`
*/
function visit(node, fn) {
return node.nodes ? mapVisit(node.nodes, fn) : fn(node);
}
/**
* Map visit over array of `nodes`.
*/
function mapVisit(nodes, fn) {
var len = nodes.length;
var idx = -1;
while (++idx < len) {
visit(nodes[idx], fn);
}
}
}
+83
View File
@@ -0,0 +1,83 @@
'use strict';
var extglob = require('extglob');
var nanomatch = require('nanomatch');
var regexNot = require('regex-not');
var toRegex = require('to-regex');
var not;
/**
* Characters to use in negation regex (we want to "not" match
* characters that are matched by other parsers)
*/
var TEXT = '([!@*?+]?\\(|\\)|\\[:?(?=.*?:?\\])|:?\\]|[*+?!^$.\\\\/])+';
var createNotRegex = function(opts) {
return not || (not = textRegex(TEXT));
};
/**
* Parsers
*/
module.exports = function(snapdragon) {
var parsers = snapdragon.parser.parsers;
// register nanomatch parsers
snapdragon.use(nanomatch.parsers);
// get references to some specific nanomatch parsers before they
// are overridden by the extglob and/or parsers
var escape = parsers.escape;
var slash = parsers.slash;
var qmark = parsers.qmark;
var plus = parsers.plus;
var star = parsers.star;
var dot = parsers.dot;
// register extglob parsers
snapdragon.use(extglob.parsers);
// custom micromatch parsers
snapdragon.parser
.use(function() {
// override "notRegex" created in nanomatch parser
this.notRegex = /^\!+(?!\()/;
})
// reset the referenced parsers
.capture('escape', escape)
.capture('slash', slash)
.capture('qmark', qmark)
.capture('star', star)
.capture('plus', plus)
.capture('dot', dot)
/**
* Override `text` parser
*/
.capture('text', function() {
if (this.isInside('bracket')) return;
var pos = this.position();
var m = this.match(createNotRegex(this.options));
if (!m || !m[0]) return;
// escape regex boundary characters and simple brackets
var val = m[0].replace(/([[\]^$])/g, '\\$1');
return pos({
type: 'text',
val: val
});
});
};
/**
* Create text regex
*/
function textRegex(pattern) {
var notStr = regexNot.create(pattern, {contains: true, strictClose: false});
var prefix = '(?:[\\^]|\\\\|';
return toRegex(prefix + notStr + ')', {strictClose: false});
}
+309
View File
@@ -0,0 +1,309 @@
'use strict';
var utils = module.exports;
var path = require('path');
/**
* Module dependencies
*/
var Snapdragon = require('snapdragon');
utils.define = require('define-property');
utils.diff = require('arr-diff');
utils.extend = require('extend-shallow');
utils.pick = require('object.pick');
utils.typeOf = require('kind-of');
utils.unique = require('array-unique');
/**
* Returns true if the platform is windows, or `path.sep` is `\\`.
* This is defined as a function to allow `path.sep` to be set in unit tests,
* or by the user, if there is a reason to do so.
* @return {Boolean}
*/
utils.isWindows = function() {
return path.sep === '\\' || process.platform === 'win32';
};
/**
* Get the `Snapdragon` instance to use
*/
utils.instantiate = function(ast, options) {
var snapdragon;
// if an instance was created by `.parse`, use that instance
if (utils.typeOf(ast) === 'object' && ast.snapdragon) {
snapdragon = ast.snapdragon;
// if the user supplies an instance on options, use that instance
} else if (utils.typeOf(options) === 'object' && options.snapdragon) {
snapdragon = options.snapdragon;
// create a new instance
} else {
snapdragon = new Snapdragon(options);
}
utils.define(snapdragon, 'parse', function(str, options) {
var parsed = Snapdragon.prototype.parse.apply(this, arguments);
parsed.input = str;
// escape unmatched brace/bracket/parens
var last = this.parser.stack.pop();
if (last && this.options.strictErrors !== true) {
var open = last.nodes[0];
var inner = last.nodes[1];
if (last.type === 'bracket') {
if (inner.val.charAt(0) === '[') {
inner.val = '\\' + inner.val;
}
} else {
open.val = '\\' + open.val;
var sibling = open.parent.nodes[1];
if (sibling.type === 'star') {
sibling.loose = true;
}
}
}
// add non-enumerable parser reference
utils.define(parsed, 'parser', this.parser);
return parsed;
});
return snapdragon;
};
/**
* Create the key to use for memoization. The key is generated
* by iterating over the options and concatenating key-value pairs
* to the pattern string.
*/
utils.createKey = function(pattern, options) {
if (utils.typeOf(options) !== 'object') {
return pattern;
}
var val = pattern;
var keys = Object.keys(options);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
val += ';' + key + '=' + String(options[key]);
}
return val;
};
/**
* Cast `val` to an array
* @return {Array}
*/
utils.arrayify = function(val) {
if (typeof val === 'string') return [val];
return val ? (Array.isArray(val) ? val : [val]) : [];
};
/**
* Return true if `val` is a non-empty string
*/
utils.isString = function(val) {
return typeof val === 'string';
};
/**
* Return true if `val` is a non-empty string
*/
utils.isObject = function(val) {
return utils.typeOf(val) === 'object';
};
/**
* Returns true if the given `str` has special characters
*/
utils.hasSpecialChars = function(str) {
return /(?:(?:(^|\/)[!.])|[*?+()|\[\]{}]|[+@]\()/.test(str);
};
/**
* Escape regex characters in the given string
*/
utils.escapeRegex = function(str) {
return str.replace(/[-[\]{}()^$|*+?.\\\/\s]/g, '\\$&');
};
/**
* Normalize slashes in the given filepath.
*
* @param {String} `filepath`
* @return {String}
*/
utils.toPosixPath = function(str) {
return str.replace(/\\+/g, '/');
};
/**
* Strip backslashes before special characters in a string.
*
* @param {String} `str`
* @return {String}
*/
utils.unescape = function(str) {
return utils.toPosixPath(str.replace(/\\(?=[*+?!.])/g, ''));
};
/**
* Strip the prefix from a filepath
* @param {String} `fp`
* @return {String}
*/
utils.stripPrefix = function(str) {
if (str.charAt(0) !== '.') {
return str;
}
var ch = str.charAt(1);
if (utils.isSlash(ch)) {
return str.slice(2);
}
return str;
};
/**
* Returns true if the given str is an escaped or
* unescaped path character
*/
utils.isSlash = function(str) {
return str === '/' || str === '\\/' || str === '\\' || str === '\\\\';
};
/**
* Returns a function that returns true if the given
* pattern matches or contains a `filepath`
*
* @param {String} `pattern`
* @return {Function}
*/
utils.matchPath = function(pattern, options) {
return (options && options.contains)
? utils.containsPattern(pattern, options)
: utils.equalsPattern(pattern, options);
};
/**
* Returns true if the given (original) filepath or unixified path are equal
* to the given pattern.
*/
utils._equals = function(filepath, unixPath, pattern) {
return pattern === filepath || pattern === unixPath;
};
/**
* Returns true if the given (original) filepath or unixified path contain
* the given pattern.
*/
utils._contains = function(filepath, unixPath, pattern) {
return filepath.indexOf(pattern) !== -1 || unixPath.indexOf(pattern) !== -1;
};
/**
* Returns a function that returns true if the given
* pattern is the same as a given `filepath`
*
* @param {String} `pattern`
* @return {Function}
*/
utils.equalsPattern = function(pattern, options) {
var unixify = utils.unixify(options);
options = options || {};
return function fn(filepath) {
var equal = utils._equals(filepath, unixify(filepath), pattern);
if (equal === true || options.nocase !== true) {
return equal;
}
var lower = filepath.toLowerCase();
return utils._equals(lower, unixify(lower), pattern);
};
};
/**
* Returns a function that returns true if the given
* pattern contains a `filepath`
*
* @param {String} `pattern`
* @return {Function}
*/
utils.containsPattern = function(pattern, options) {
var unixify = utils.unixify(options);
options = options || {};
return function(filepath) {
var contains = utils._contains(filepath, unixify(filepath), pattern);
if (contains === true || options.nocase !== true) {
return contains;
}
var lower = filepath.toLowerCase();
return utils._contains(lower, unixify(lower), pattern);
};
};
/**
* Returns a function that returns true if the given
* regex matches the `filename` of a file path.
*
* @param {RegExp} `re` Matching regex
* @return {Function}
*/
utils.matchBasename = function(re) {
return function(filepath) {
return re.test(path.basename(filepath));
};
};
/**
* Determines the filepath to return based on the provided options.
* @return {any}
*/
utils.value = function(str, unixify, options) {
if (options && options.unixify === false) {
return str;
}
return unixify(str);
};
/**
* Returns a function that normalizes slashes in a string to forward
* slashes, strips `./` from beginning of paths, and optionally unescapes
* special characters.
* @return {Function}
*/
utils.unixify = function(options) {
options = options || {};
return function(filepath) {
if (utils.isWindows() || options.unixify === true) {
filepath = utils.toPosixPath(filepath);
}
if (options.stripPrefix !== false) {
filepath = utils.stripPrefix(filepath);
}
if (options.unescape === true) {
filepath = utils.unescape(filepath);
}
return filepath;
};
};
+219
View File
@@ -0,0 +1,219 @@
{
"_args": [
[
"micromatch@3.1.10",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "micromatch@3.1.10",
"_id": "micromatch@3.1.10",
"_inBundle": false,
"_integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"_location": "/webpack/micromatch",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "micromatch@3.1.10",
"name": "micromatch",
"escapedName": "micromatch",
"rawSpec": "3.1.10",
"saveSpec": null,
"fetchSpec": "3.1.10"
},
"_requiredBy": [
"/webpack"
],
"_resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"_spec": "3.1.10",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/micromatch/micromatch/issues"
},
"contributors": [
{
"name": "Amila Welihinda",
"url": "amilajack.com"
},
{
"name": "Bogdan Chadkin",
"url": "https://github.com/TrySound"
},
{
"name": "Brian Woodward",
"url": "https://twitter.com/doowb"
},
{
"name": "Devon Govett",
"url": "http://badassjs.com"
},
{
"name": "Elan Shanker",
"url": "https://github.com/es128"
},
{
"name": "Fabrício Matté",
"url": "https://ultcombo.js.org"
},
{
"name": "Jon Schlinkert",
"url": "http://twitter.com/jonschlinkert"
},
{
"name": "Martin Kolárik",
"url": "https://kolarik.sk"
},
{
"name": "Olsten Larck",
"url": "https://i.am.charlike.online"
},
{
"name": "Paul Miller",
"url": "paulmillr.com"
},
{
"name": "Tom Byrer",
"url": "https://github.com/tomByrer"
},
{
"name": "Tyler Akins",
"url": "http://rumkin.com"
},
{
"url": "https://github.com/DianeLooney"
}
],
"dependencies": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
},
"description": "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.",
"devDependencies": {
"bash-match": "^1.0.2",
"for-own": "^1.0.0",
"gulp": "^3.9.1",
"gulp-format-md": "^1.0.0",
"gulp-istanbul": "^1.1.3",
"gulp-mocha": "^5.0.0",
"gulp-unused": "^0.2.1",
"is-windows": "^1.0.2",
"minimatch": "^3.0.4",
"minimist": "^1.2.0",
"mocha": "^3.5.3",
"multimatch": "^2.1.0"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js",
"lib"
],
"homepage": "https://github.com/micromatch/micromatch",
"keywords": [
"bash",
"expand",
"expansion",
"expression",
"file",
"files",
"filter",
"find",
"glob",
"globbing",
"globs",
"globstar",
"match",
"matcher",
"matches",
"matching",
"micromatch",
"minimatch",
"multimatch",
"path",
"pattern",
"patterns",
"regex",
"regexp",
"regular",
"shell",
"wildcard"
],
"license": "MIT",
"lintDeps": {
"dependencies": {
"options": {
"lock": {
"snapdragon": "^0.8.1"
}
}
},
"devDependencies": {
"files": {
"options": {
"ignore": [
"benchmark/**"
]
}
}
}
},
"main": "index.js",
"name": "micromatch",
"repository": {
"type": "git",
"url": "git+https://github.com/micromatch/micromatch.git"
},
"scripts": {
"test": "mocha"
},
"verb": {
"toc": "collapsible",
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"helpers": [
"./benchmark/helper.js"
],
"related": {
"list": [
"braces",
"expand-brackets",
"extglob",
"fill-range",
"nanomatch"
]
},
"lint": {
"reflinks": true
},
"reflinks": [
"expand-brackets",
"extglob",
"glob-object",
"minimatch",
"multimatch",
"snapdragon"
]
},
"version": "3.1.10"
}
+134
View File
@@ -0,0 +1,134 @@
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="1.0.0"></a>
# [1.0.0](https://github.com/webpack-contrib/schema-utils/compare/v0.4.7...v1.0.0) (2018-08-07)
### Features
* **src:** add support for custom error messages ([#33](https://github.com/webpack-contrib/schema-utils/issues/33)) ([1cbe4ef](https://github.com/webpack-contrib/schema-utils/commit/1cbe4ef))
<a name="0.4.7"></a>
## [0.4.7](https://github.com/webpack-contrib/schema-utils/compare/v0.4.6...v0.4.7) (2018-08-07)
### Bug Fixes
* **src:** `node >= v4.0.0` support ([#32](https://github.com/webpack-contrib/schema-utils/issues/32)) ([cb13dd4](https://github.com/webpack-contrib/schema-utils/commit/cb13dd4))
<a name="0.4.6"></a>
## [0.4.6](https://github.com/webpack-contrib/schema-utils/compare/v0.4.5...v0.4.6) (2018-08-06)
### Bug Fixes
* **package:** remove lockfile ([#28](https://github.com/webpack-contrib/schema-utils/issues/28)) ([69f1a81](https://github.com/webpack-contrib/schema-utils/commit/69f1a81))
* **package:** remove unnecessary `webpack` dependency ([#26](https://github.com/webpack-contrib/schema-utils/issues/26)) ([532eaa5](https://github.com/webpack-contrib/schema-utils/commit/532eaa5))
<a name="0.4.5"></a>
## [0.4.5](https://github.com/webpack-contrib/schema-utils/compare/v0.4.4...v0.4.5) (2018-02-13)
### Bug Fixes
* **CHANGELOG:** update broken links ([4483b9f](https://github.com/webpack-contrib/schema-utils/commit/4483b9f))
* **package:** update broken links ([f2494ba](https://github.com/webpack-contrib/schema-utils/commit/f2494ba))
<a name="0.4.4"></a>
## [0.4.4](https://github.com/webpack-contrib/schema-utils/compare/v0.4.3...v0.4.4) (2018-02-13)
### Bug Fixes
* **package:** update `dependencies` ([#22](https://github.com/webpack-contrib/schema-utils/issues/22)) ([3aecac6](https://github.com/webpack-contrib/schema-utils/commit/3aecac6))
<a name="0.4.3"></a>
## [0.4.3](https://github.com/webpack-contrib/schema-utils/compare/v0.4.2...v0.4.3) (2017-12-14)
### Bug Fixes
* **validateOptions:** throw `err` instead of `process.exit(1)` ([#17](https://github.com/webpack-contrib/schema-utils/issues/17)) ([c595eda](https://github.com/webpack-contrib/schema-utils/commit/c595eda))
* **ValidationError:** never return `this` in the ctor ([#16](https://github.com/webpack-contrib/schema-utils/issues/16)) ([c723791](https://github.com/webpack-contrib/schema-utils/commit/c723791))
<a name="0.4.2"></a>
## [0.4.2](https://github.com/webpack-contrib/schema-utils/compare/v0.4.1...v0.4.2) (2017-11-09)
### Bug Fixes
* **validateOptions:** catch `ValidationError` and handle it internally ([#15](https://github.com/webpack-contrib/schema-utils/issues/15)) ([9c5ef5e](https://github.com/webpack-contrib/schema-utils/commit/9c5ef5e))
<a name="0.4.1"></a>
## [0.4.1](https://github.com/webpack-contrib/schema-utils/compare/v0.4.0...v0.4.1) (2017-11-03)
### Bug Fixes
* **ValidationError:** use `Error.captureStackTrace` for `err.stack` handling ([#14](https://github.com/webpack-contrib/schema-utils/issues/14)) ([a6fb974](https://github.com/webpack-contrib/schema-utils/commit/a6fb974))
<a name="0.4.0"></a>
# [0.4.0](https://github.com/webpack-contrib/schema-utils/compare/v0.3.0...v0.4.0) (2017-10-28)
### Features
* add support for `typeof`, `instanceof` (`{Function\|RegExp}`) ([#10](https://github.com/webpack-contrib/schema-utils/issues/10)) ([9f01816](https://github.com/webpack-contrib/schema-utils/commit/9f01816))
<a name="0.3.0"></a>
# [0.3.0](https://github.com/webpack-contrib/schema-utils/compare/v0.2.1...v0.3.0) (2017-04-29)
### Features
* add ValidationError ([#8](https://github.com/webpack-contrib/schema-utils/issues/8)) ([d48f0fb](https://github.com/webpack-contrib/schema-utils/commit/d48f0fb))
<a name="0.2.1"></a>
## [0.2.1](https://github.com/webpack-contrib/schema-utils/compare/v0.2.0...v0.2.1) (2017-03-13)
### Bug Fixes
* Include .babelrc to `files` ([28f0363](https://github.com/webpack-contrib/schema-utils/commit/28f0363))
* Include source to `files` ([43b0f2f](https://github.com/webpack-contrib/schema-utils/commit/43b0f2f))
<a name="0.2.0"></a>
# [0.2.0](https://github.com/webpack-contrib/schema-utils/compare/v0.1.0...v0.2.0) (2017-03-12)
<a name="0.1.0"></a>
# 0.1.0 (2017-03-07)
### Features
* **validations:** add validateOptions module ([ae9b47b](https://github.com/webpack-contrib/schema-utils/commit/ae9b47b))
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+20
View File
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+149
View File
@@ -0,0 +1,149 @@
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![test][test]][test-url]
[![coverage][cover]][cover-url]
[![chat][chat]][chat-url]
<div align="center">
<a href="http://json-schema.org">
<img width="160" height="160"
src="https://raw.githubusercontent.com/webpack-contrib/schema-utils/master/docs/logo.png">
</a>
<a href="https://github.com/webpack/webpack">
<img width="200" height="200"
src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
<h1>Schema Utils</h1>
</div>
<h2 align="center">Install</h2>
```bash
npm i schema-utils
```
<h2 align="center">Usage</h2>
### `validateOptions`
**`schema.json`**
```js
{
"type": "object",
"properties": {
// Options...
},
"additionalProperties": false
}
```
#### Error Messages (Custom)
**`schema.json`**
```js
{
"type": "object",
"properties": {
"option": {
"type": [ "boolean" ]
}
},
// Overrides the default err.message for option
"errorMessage": {
"option": "should be {Boolean} (https:/github.com/org/repo#anchor)"
}
"additionalProperties": false
}
```
```js
import schema from 'path/to/schema.json'
import validateOptions from 'schema-utils'
validateOptions(schema, options, 'Loader/Plugin Name')
```
<h2 align="center">Examples</h2>
**schema.json**
```json
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"test": {
"anyOf": [
{ "type": "array" },
{ "type": "string" },
{ "instanceof": "RegExp" }
]
},
"transform": {
"instanceof": "Function"
},
"sourceMap": {
"type": "boolean"
}
},
"additionalProperties": false
}
```
### `Loader`
```js
import { getOptions } from 'loader-utils'
import validateOptions from 'schema-utils'
import schema from 'path/to/schema.json'
function loader (src, map) {
const options = getOptions(this) || {}
validateOptions(schema, options, 'Loader Name')
// Code...
}
```
### `Plugin`
```js
import validateOptions from 'schema-utils'
import schema from 'path/to/schema.json'
class Plugin {
constructor (options) {
validateOptions(schema, options, 'Plugin Name')
this.options = options
}
apply (compiler) {
// Code...
}
}
```
[npm]: https://img.shields.io/npm/v/schema-utils.svg
[npm-url]: https://npmjs.com/package/schema-utils
[node]: https://img.shields.io/node/v/schema-utils.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/webpack-contrib/schema-utils.svg
[deps-url]: https://david-dm.org/webpack-contrib/schema-utils
[test]: http://img.shields.io/travis/webpack-contrib/schema-utils.svg
[test-url]: https://travis-ci.org/webpack-contrib/schema-utils
[cover]: https://codecov.io/gh/webpack-contrib/schema-utils/branch/master/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack-contrib/schema-utils
[chat]: https://img.shields.io/badge/gitter-webpack%2Fwebpack-brightgreen.svg
[chat-url]: https://gitter.im/webpack/webpack
+78
View File
@@ -0,0 +1,78 @@
{
"_args": [
[
"schema-utils@1.0.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "schema-utils@1.0.0",
"_id": "schema-utils@1.0.0",
"_inBundle": false,
"_integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
"_location": "/webpack/schema-utils",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "schema-utils@1.0.0",
"name": "schema-utils",
"escapedName": "schema-utils",
"rawSpec": "1.0.0",
"saveSpec": null,
"fetchSpec": "1.0.0"
},
"_requiredBy": [
"/webpack",
"/webpack/terser-webpack-plugin"
],
"_resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
"_spec": "1.0.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "webpack Contrib",
"url": "https://github.com/webpack-contrib"
},
"bugs": {
"url": "https://github.com/webpack-contrib/schema-utils/issues"
},
"dependencies": {
"ajv": "^6.1.0",
"ajv-errors": "^1.0.0",
"ajv-keywords": "^3.1.0"
},
"description": "webpack Validation Utils",
"devDependencies": {
"@commitlint/cli": "^7.0.0",
"@commitlint/config-conventional": "^7.0.0",
"@webpack-contrib/eslint-config-webpack": "^2.0.0",
"del-cli": "^1.0.0",
"eslint": "^5.0.0",
"eslint-plugin-import": "^2.0.0",
"eslint-plugin-prettier": "^2.0.0",
"jest": "^21.0.0",
"prettier": "^1.0.0",
"standard-version": "^4.0.0"
},
"engines": {
"node": ">= 4"
},
"files": [
"src"
],
"homepage": "https://github.com/webpack-contrib/schema-utils",
"license": "MIT",
"main": "src/index.js",
"name": "schema-utils",
"repository": {
"type": "git",
"url": "git+https://github.com/webpack-contrib/schema-utils.git"
},
"scripts": {
"clean": "del-cli coverage",
"commits": "commitlint --from $(git rev-list --tags --max-count=1)",
"lint": "eslint --cache src test",
"release": "npm run commits && standard-version",
"test": "jest --env node --verbose --coverage"
},
"version": "1.0.0"
}
+30
View File
@@ -0,0 +1,30 @@
/* eslint-disable
strict,
no-param-reassign
*/
'use strict';
class ValidationError extends Error {
constructor(errors, name) {
super();
this.name = 'ValidationError';
this.message = `${name || ''} Invalid Options\n\n`;
this.errors = errors.map((err) => {
err.dataPath = err.dataPath.replace(/\//g, '.');
return err;
});
this.errors.forEach((err) => {
this.message += `options${err.dataPath} ${err.message}\n`;
});
Error.captureStackTrace(this, this.constructor);
}
}
module.exports = ValidationError;
+9
View File
@@ -0,0 +1,9 @@
/* eslint-disable
strict
*/
'use strict';
const validateOptions = require('./validateOptions');
module.exports = validateOptions;
+38
View File
@@ -0,0 +1,38 @@
/* eslint-disable
strict,
no-param-reassign
*/
'use strict';
const fs = require('fs');
const path = require('path');
const Ajv = require('ajv');
const errors = require('ajv-errors');
const keywords = require('ajv-keywords');
const ValidationError = require('./ValidationError');
const ajv = new Ajv({
allErrors: true,
jsonPointers: true,
});
errors(ajv);
keywords(ajv, ['instanceof', 'typeof']);
const validateOptions = (schema, options, name) => {
if (typeof schema === 'string') {
schema = fs.readFileSync(path.resolve(schema), 'utf8');
schema = JSON.parse(schema);
}
if (!ajv.validate(schema, options)) {
throw new ValidationError(ajv.errors, name);
}
return true;
};
module.exports = validateOptions;
@@ -0,0 +1,11 @@
{
"spellright.language": [
"en"
],
"spellright.documentTypes": [
"markdown",
"latex",
"plaintext",
"javascript"
]
}
+27
View File
@@ -0,0 +1,27 @@
Copyright 2014 Yahoo! Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Yahoo! Inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+143
View File
@@ -0,0 +1,143 @@
Serialize JavaScript
====================
Serialize JavaScript to a _superset_ of JSON that includes regular expressions, dates and functions.
[![npm Version][npm-badge]][npm]
[![Dependency Status][david-badge]][david]
[![Build Status][travis-badge]][travis]
## Overview
The code in this package began its life as an internal module to [express-state][]. To expand its usefulness, it now lives as `serialize-javascript` — an independent package on npm.
You're probably wondering: **What about `JSON.stringify()`!?** We've found that sometimes we need to serialize JavaScript **functions**, **regexps**, **dates**, **sets** or **maps**. A great example is a web app that uses client-side URL routing where the route definitions are regexps that need to be shared from the server to the client. But this module is also great for communicating between node processes.
The string returned from this package's single export function is literal JavaScript which can be saved to a `.js` file, or be embedded into an HTML document by making the content of a `<script>` element.
> **HTML characters and JavaScript line terminators are escaped automatically.**
Please note that serialization for ES6 Sets & Maps requires support for `Array.from` (not available in IE or Node < 0.12), or an `Array.from` polyfill.
## Installation
Install using npm:
```shell
$ npm install serialize-javascript
```
## Usage
```js
var serialize = require('serialize-javascript');
serialize({
str : 'string',
num : 0,
obj : {foo: 'foo'},
arr : [1, 2, 3],
bool : true,
nil : null,
undef: undefined,
inf : Infinity,
date : new Date("Thu, 28 Apr 2016 22:02:17 GMT"),
map : new Map([['hello', 'world']]),
set : new Set([123, 456]),
fn : function echo(arg) { return arg; },
re : /([^\s]+)/g
});
```
The above will produce the following string output:
```js
'{"str":"string","num":0,"obj":{"foo":"foo"},"arr":[1,2,3],"bool":true,"nil":null,"undef":undefined,"inf":Infinity,"date":new Date("2016-04-28T22:02:17.000Z"),"map":new Map([["hello","world"]]),"set":new Set([123,456]),"fn":function echo(arg) { return arg; },"re":new RegExp("([^\\\\s]+)", "g")}'
```
Note: to produced a beautified string, you can pass an optional second argument to `serialize()` to define the number of spaces to be used for the indentation.
### Automatic Escaping of HTML Characters
A primary feature of this package is to serialize code to a string of literal JavaScript which can be embedded in an HTML document by adding it as the contents of the `<script>` element. In order to make this safe, HTML characters and JavaScript line terminators are escaped automatically.
```js
serialize({
haxorXSS: '</script>'
});
```
The above will produce the following string, HTML-escaped output which is safe to put into an HTML document as it will not cause the inline script element to terminate:
```js
'{"haxorXSS":"\\u003C\\u002Fscript\\u003E"}'
```
> You can pass an optional `unsafe` argument to `serialize()` for straight serialization.
### Options
The `serialize()` function accepts an `options` object as its second argument. All options are being defaulted to `undefined`:
#### `options.space`
This option is the same as the `space` argument that can be passed to [`JSON.stringify`][JSON.stringify]. It can be used to add whitespace and indentation to the serialized output to make it more readable.
```js
serialize(obj, {space: 2});
```
#### `options.isJSON`
This option is a signal to `serialize()` that the object being serialized does not contain any function or regexps values. This enables a hot-path that allows serialization to be over 3x faster. If you're serializing a lot of data, and know its pure JSON, then you can enable this option for a speed-up.
**Note:** That when using this option, the output will still be escaped to protect against XSS.
```js
serialize(obj, {isJSON: true});
```
#### `options.unsafe`
This option is to signal `serialize()` that we want to do a straight conversion, without the XSS protection. This options needs to be explicitly set to `true`. HTML characters and JavaScript line terminators will not be escaped. You will have to roll your own.
```js
serialize(obj, {unsafe: true});
```
#### `options.ignoreFunction`
This option is to signal `serialize()` that we do not want serialize JavaScript function.
Just treat function like `JSON.stringify` do, but other features will work as expected.
```js
serialize(obj, {ignoreFunction: true});
```
## Deserializing
For some use cases you might also need to deserialize the string. This is explicitly not part of this module. However, you can easily write it yourself:
```js
function deserialize(serializedJavascript){
return eval('(' + serializedJavascript + ')');
}
```
**Note:** Don't forget the parentheses around the serialized javascript, as the opening bracket `{` will be considered to be the start of a body.
## License
This software is free to use under the Yahoo! Inc. BSD license.
See the [LICENSE file][LICENSE] for license text and copyright information.
[npm]: https://www.npmjs.org/package/serialize-javascript
[npm-badge]: https://img.shields.io/npm/v/serialize-javascript.svg?style=flat-square
[david]: https://david-dm.org/yahoo/serialize-javascript
[david-badge]: https://img.shields.io/david/yahoo/serialize-javascript.svg?style=flat-square
[travis]: https://travis-ci.org/yahoo/serialize-javascript
[travis-badge]: https://img.shields.io/travis/yahoo/serialize-javascript.svg?style=flat-square
[express-state]: https://github.com/yahoo/express-state
[JSON.stringify]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
[LICENSE]: https://github.com/yahoo/serialize-javascript/blob/master/LICENSE
+238
View File
@@ -0,0 +1,238 @@
/*
Copyright (c) 2014, Yahoo! Inc. All rights reserved.
Copyrights licensed under the New BSD License.
See the accompanying LICENSE file for terms.
*/
'use strict';
var randomBytes = require('randombytes');
// Generate an internal UID to make the regexp pattern harder to guess.
var UID_LENGTH = 16;
var UID = generateUID();
var PLACE_HOLDER_REGEXP = new RegExp('(\\\\)?"@__(F|R|D|M|S|U|I)-' + UID + '-(\\d+)__@"', 'g');
var IS_NATIVE_CODE_REGEXP = /\{\s*\[native code\]\s*\}/g;
var IS_PURE_FUNCTION = /function.*?\(/;
var IS_ARROW_FUNCTION = /.*?=>.*?/;
var UNSAFE_CHARS_REGEXP = /[<>\/\u2028\u2029]/g;
var RESERVED_SYMBOLS = ['*', 'async'];
// Mapping of unsafe HTML and invalid JavaScript line terminator chars to their
// Unicode char counterparts which are safe to use in JavaScript strings.
var ESCAPED_CHARS = {
'<' : '\\u003C',
'>' : '\\u003E',
'/' : '\\u002F',
'\u2028': '\\u2028',
'\u2029': '\\u2029'
};
function escapeUnsafeChars(unsafeChar) {
return ESCAPED_CHARS[unsafeChar];
}
function generateUID() {
var bytes = randomBytes(UID_LENGTH);
var result = '';
for(var i=0; i<UID_LENGTH; ++i) {
result += bytes[i].toString(16);
}
return result;
}
function deleteFunctions(obj){
var functionKeys = [];
for (var key in obj) {
if (typeof obj[key] === "function") {
functionKeys.push(key);
}
}
for (var i = 0; i < functionKeys.length; i++) {
delete obj[functionKeys[i]];
}
}
module.exports = function serialize(obj, options) {
options || (options = {});
// Backwards-compatibility for `space` as the second argument.
if (typeof options === 'number' || typeof options === 'string') {
options = {space: options};
}
var functions = [];
var regexps = [];
var dates = [];
var maps = [];
var sets = [];
var undefs = [];
var infinities= [];
// Returns placeholders for functions and regexps (identified by index)
// which are later replaced by their string representation.
function replacer(key, value) {
// For nested function
if(options.ignoreFunction){
deleteFunctions(value);
}
if (!value && value !== undefined) {
return value;
}
// If the value is an object w/ a toJSON method, toJSON is called before
// the replacer runs, so we use this[key] to get the non-toJSONed value.
var origValue = this[key];
var type = typeof origValue;
if (type === 'object') {
if(origValue instanceof RegExp) {
return '@__R-' + UID + '-' + (regexps.push(origValue) - 1) + '__@';
}
if(origValue instanceof Date) {
return '@__D-' + UID + '-' + (dates.push(origValue) - 1) + '__@';
}
if(origValue instanceof Map) {
return '@__M-' + UID + '-' + (maps.push(origValue) - 1) + '__@';
}
if(origValue instanceof Set) {
return '@__S-' + UID + '-' + (sets.push(origValue) - 1) + '__@';
}
}
if (type === 'function') {
return '@__F-' + UID + '-' + (functions.push(origValue) - 1) + '__@';
}
if (type === 'undefined') {
return '@__U-' + UID + '-' + (undefs.push(origValue) - 1) + '__@';
}
if (type === 'number' && !isNaN(origValue) && !isFinite(origValue)) {
return '@__I-' + UID + '-' + (infinities.push(origValue) - 1) + '__@';
}
return value;
}
function serializeFunc(fn) {
var serializedFn = fn.toString();
if (IS_NATIVE_CODE_REGEXP.test(serializedFn)) {
throw new TypeError('Serializing native function: ' + fn.name);
}
// pure functions, example: {key: function() {}}
if(IS_PURE_FUNCTION.test(serializedFn)) {
return serializedFn;
}
// arrow functions, example: arg1 => arg1+5
if(IS_ARROW_FUNCTION.test(serializedFn)) {
return serializedFn;
}
var argsStartsAt = serializedFn.indexOf('(');
var def = serializedFn.substr(0, argsStartsAt)
.trim()
.split(' ')
.filter(function(val) { return val.length > 0 });
var nonReservedSymbols = def.filter(function(val) {
return RESERVED_SYMBOLS.indexOf(val) === -1
});
// enhanced literal objects, example: {key() {}}
if(nonReservedSymbols.length > 0) {
return (def.indexOf('async') > -1 ? 'async ' : '') + 'function'
+ (def.join('').indexOf('*') > -1 ? '*' : '')
+ serializedFn.substr(argsStartsAt);
}
// arrow functions
return serializedFn;
}
// Check if the parameter is function
if (options.ignoreFunction && typeof obj === "function") {
obj = undefined;
}
// Protects against `JSON.stringify()` returning `undefined`, by serializing
// to the literal string: "undefined".
if (obj === undefined) {
return String(obj);
}
var str;
// Creates a JSON string representation of the value.
// NOTE: Node 0.12 goes into slow mode with extra JSON.stringify() args.
if (options.isJSON && !options.space) {
str = JSON.stringify(obj);
} else {
str = JSON.stringify(obj, options.isJSON ? null : replacer, options.space);
}
// Protects against `JSON.stringify()` returning `undefined`, by serializing
// to the literal string: "undefined".
if (typeof str !== 'string') {
return String(str);
}
// Replace unsafe HTML and invalid JavaScript line terminator chars with
// their safe Unicode char counterpart. This _must_ happen before the
// regexps and functions are serialized and added back to the string.
if (options.unsafe !== true) {
str = str.replace(UNSAFE_CHARS_REGEXP, escapeUnsafeChars);
}
if (functions.length === 0 && regexps.length === 0 && dates.length === 0 && maps.length === 0 && sets.length === 0 && undefs.length === 0 && infinities.length === 0) {
return str;
}
// Replaces all occurrences of function, regexp, date, map and set placeholders in the
// JSON string with their string representations. If the original value can
// not be found, then `undefined` is used.
return str.replace(PLACE_HOLDER_REGEXP, function (match, backSlash, type, valueIndex) {
// The placeholder may not be preceded by a backslash. This is to prevent
// replacing things like `"a\"@__R-<UID>-0__@"` and thus outputting
// invalid JS.
if (backSlash) {
return match;
}
if (type === 'D') {
return "new Date(\"" + dates[valueIndex].toISOString() + "\")";
}
if (type === 'R') {
return "new RegExp(" + serialize(regexps[valueIndex].source) + ", \"" + regexps[valueIndex].flags + "\")";
}
if (type === 'M') {
return "new Map(" + serialize(Array.from(maps[valueIndex].entries()), options) + ")";
}
if (type === 'S') {
return "new Set(" + serialize(Array.from(sets[valueIndex].values()), options) + ")";
}
if (type === 'U') {
return 'undefined'
}
if (type === 'I') {
return infinities[valueIndex];
}
var fn = functions[valueIndex];
return serializeFunc(fn);
});
}
+67
View File
@@ -0,0 +1,67 @@
{
"_args": [
[
"serialize-javascript@3.1.0",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "serialize-javascript@3.1.0",
"_id": "serialize-javascript@3.1.0",
"_inBundle": false,
"_integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
"_location": "/webpack/serialize-javascript",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "serialize-javascript@3.1.0",
"name": "serialize-javascript",
"escapedName": "serialize-javascript",
"rawSpec": "3.1.0",
"saveSpec": null,
"fetchSpec": "3.1.0"
},
"_requiredBy": [
"/webpack/terser-webpack-plugin"
],
"_resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
"_spec": "3.1.0",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Eric Ferraiuolo",
"email": "edf@ericf.me"
},
"bugs": {
"url": "https://github.com/yahoo/serialize-javascript/issues"
},
"dependencies": {
"randombytes": "^2.1.0"
},
"description": "Serialize JavaScript to a superset of JSON that includes regular expressions and functions.",
"devDependencies": {
"benchmark": "^2.1.4",
"chai": "^4.1.0",
"mocha": "^7.0.0",
"nyc": "^15.0.0"
},
"homepage": "https://github.com/yahoo/serialize-javascript",
"keywords": [
"serialize",
"serialization",
"javascript",
"js",
"json"
],
"license": "BSD-3-Clause",
"main": "index.js",
"name": "serialize-javascript",
"repository": {
"type": "git",
"url": "git+https://github.com/yahoo/serialize-javascript.git"
},
"scripts": {
"benchmark": "node -v && node test/benchmark/serialize.js",
"test": "nyc --reporter=lcov mocha test/unit"
},
"version": "3.1.0"
}
+286
View File
@@ -0,0 +1,286 @@
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
<a name="6.0.1"></a>
## [6.0.1](https://github.com/zkat/ssri/compare/v6.0.0...v6.0.1) (2018-08-27)
### Bug Fixes
* **opts:** use figgy-pudding to specify consumed opts ([cf86553](https://github.com/zkat/ssri/commit/cf86553))
<a name="6.0.0"></a>
# [6.0.0](https://github.com/zkat/ssri/compare/v5.3.0...v6.0.0) (2018-04-09)
### Bug Fixes
* **docs:** minor typo ([b71ef17](https://github.com/zkat/ssri/commit/b71ef17))
### meta
* drop support for node@4 ([d9bf359](https://github.com/zkat/ssri/commit/d9bf359))
### BREAKING CHANGES
* node@4 is no longer supported
<a name="5.3.0"></a>
# [5.3.0](https://github.com/zkat/ssri/compare/v5.2.4...v5.3.0) (2018-03-13)
### Features
* **checkData:** optionally throw when checkData fails ([bf26b84](https://github.com/zkat/ssri/commit/bf26b84))
<a name="5.2.4"></a>
## [5.2.4](https://github.com/zkat/ssri/compare/v5.2.3...v5.2.4) (2018-02-16)
<a name="5.2.3"></a>
## [5.2.3](https://github.com/zkat/ssri/compare/v5.2.2...v5.2.3) (2018-02-16)
### Bug Fixes
* **hashes:** filter hash priority list by available hashes ([2fa30b8](https://github.com/zkat/ssri/commit/2fa30b8))
* **integrityStream:** dedupe algorithms to generate ([d56c654](https://github.com/zkat/ssri/commit/d56c654))
<a name="5.2.2"></a>
## [5.2.2](https://github.com/zkat/ssri/compare/v5.2.1...v5.2.2) (2018-02-14)
### Bug Fixes
* **security:** tweak strict SRI regex ([#10](https://github.com/zkat/ssri/issues/10)) ([d0ebcdc](https://github.com/zkat/ssri/commit/d0ebcdc))
<a name="5.2.1"></a>
## [5.2.1](https://github.com/zkat/ssri/compare/v5.2.0...v5.2.1) (2018-02-06)
<a name="5.2.0"></a>
# [5.2.0](https://github.com/zkat/ssri/compare/v5.1.0...v5.2.0) (2018-02-06)
### Features
* **match:** add integrity.match() ([3c49cc4](https://github.com/zkat/ssri/commit/3c49cc4))
<a name="5.1.0"></a>
# [5.1.0](https://github.com/zkat/ssri/compare/v5.0.0...v5.1.0) (2018-01-18)
### Bug Fixes
* **checkStream:** integrityStream now takes opts.integrity algos into account ([d262910](https://github.com/zkat/ssri/commit/d262910))
### Features
* **sha3:** do some guesswork about upcoming sha3 ([7fdd9df](https://github.com/zkat/ssri/commit/7fdd9df))
<a name="5.0.0"></a>
# [5.0.0](https://github.com/zkat/ssri/compare/v4.1.6...v5.0.0) (2017-10-23)
### Features
* **license:** relicense to ISC (#9) ([c82983a](https://github.com/zkat/ssri/commit/c82983a))
### BREAKING CHANGES
* **license:** the license has been changed from CC0-1.0 to ISC.
<a name="4.1.6"></a>
## [4.1.6](https://github.com/zkat/ssri/compare/v4.1.5...v4.1.6) (2017-06-07)
### Bug Fixes
* **checkStream:** make sure to pass all opts through ([0b1bcbe](https://github.com/zkat/ssri/commit/0b1bcbe))
<a name="4.1.5"></a>
## [4.1.5](https://github.com/zkat/ssri/compare/v4.1.4...v4.1.5) (2017-06-05)
### Bug Fixes
* **integrityStream:** stop crashing if opts.algorithms and opts.integrity have an algo mismatch ([fb1293e](https://github.com/zkat/ssri/commit/fb1293e))
<a name="4.1.4"></a>
## [4.1.4](https://github.com/zkat/ssri/compare/v4.1.3...v4.1.4) (2017-05-31)
### Bug Fixes
* **node:** older versions of node[@4](https://github.com/4) do not support base64buffer string parsing ([513df4e](https://github.com/zkat/ssri/commit/513df4e))
<a name="4.1.3"></a>
## [4.1.3](https://github.com/zkat/ssri/compare/v4.1.2...v4.1.3) (2017-05-24)
### Bug Fixes
* **check:** handle various bad hash corner cases better ([c2c262b](https://github.com/zkat/ssri/commit/c2c262b))
<a name="4.1.2"></a>
## [4.1.2](https://github.com/zkat/ssri/compare/v4.1.1...v4.1.2) (2017-04-18)
### Bug Fixes
* **stream:** _flush can be called multiple times. use on("end") ([b1c4805](https://github.com/zkat/ssri/commit/b1c4805))
<a name="4.1.1"></a>
## [4.1.1](https://github.com/zkat/ssri/compare/v4.1.0...v4.1.1) (2017-04-12)
### Bug Fixes
* **pickAlgorithm:** error if pickAlgorithm() is used in an empty Integrity ([fab470e](https://github.com/zkat/ssri/commit/fab470e))
<a name="4.1.0"></a>
# [4.1.0](https://github.com/zkat/ssri/compare/v4.0.0...v4.1.0) (2017-04-07)
### Features
* adding ssri.create for a crypto style interface (#2) ([96f52ad](https://github.com/zkat/ssri/commit/96f52ad))
<a name="4.0.0"></a>
# [4.0.0](https://github.com/zkat/ssri/compare/v3.0.2...v4.0.0) (2017-04-03)
### Bug Fixes
* **integrity:** should have changed the error code before. oops ([8381afa](https://github.com/zkat/ssri/commit/8381afa))
### BREAKING CHANGES
* **integrity:** EBADCHECKSUM -> EINTEGRITY for verification errors
<a name="3.0.2"></a>
## [3.0.2](https://github.com/zkat/ssri/compare/v3.0.1...v3.0.2) (2017-04-03)
<a name="3.0.1"></a>
## [3.0.1](https://github.com/zkat/ssri/compare/v3.0.0...v3.0.1) (2017-04-03)
### Bug Fixes
* **package.json:** really should have these in the keywords because search ([a6ac6d0](https://github.com/zkat/ssri/commit/a6ac6d0))
<a name="3.0.0"></a>
# [3.0.0](https://github.com/zkat/ssri/compare/v2.0.0...v3.0.0) (2017-04-03)
### Bug Fixes
* **hashes:** IntegrityMetadata -> Hash ([d04aa1f](https://github.com/zkat/ssri/commit/d04aa1f))
### Features
* **check:** return IntegrityMetadata on check success ([2301e74](https://github.com/zkat/ssri/commit/2301e74))
* **fromHex:** ssri.fromHex to make it easier to generate them from hex valus ([049b89e](https://github.com/zkat/ssri/commit/049b89e))
* **hex:** utility function for getting hex version of digest ([a9f021c](https://github.com/zkat/ssri/commit/a9f021c))
* **hexDigest:** added hexDigest method to Integrity objects too ([85208ba](https://github.com/zkat/ssri/commit/85208ba))
* **integrity:** add .isIntegrity and .isIntegrityMetadata ([1b29e6f](https://github.com/zkat/ssri/commit/1b29e6f))
* **integrityStream:** new stream that can both generate and check streamed data ([fd23e1b](https://github.com/zkat/ssri/commit/fd23e1b))
* **parse:** allow parsing straight into a single IntegrityMetadata object ([c8ddf48](https://github.com/zkat/ssri/commit/c8ddf48))
* **pickAlgorithm:** Intergrity#pickAlgorithm() added ([b97a796](https://github.com/zkat/ssri/commit/b97a796))
* **size:** calculate and update stream sizes ([02ed1ad](https://github.com/zkat/ssri/commit/02ed1ad))
### BREAKING CHANGES
* **hashes:** `.isIntegrityMetadata` is now `.isHash`. Also, any references to `IntegrityMetadata` now refer to `Hash`.
* **integrityStream:** createCheckerStream has been removed and replaced with a general-purpose integrityStream.
To convert existing createCheckerStream code, move the `sri` argument into `opts.integrity` in integrityStream. All other options should be the same.
* **check:** `checkData`, `checkStream`, and `createCheckerStream` now yield a whole IntegrityMetadata instance representing the first successful hash match.
<a name="2.0.0"></a>
# [2.0.0](https://github.com/zkat/ssri/compare/v1.0.0...v2.0.0) (2017-03-24)
### Bug Fixes
* **strict-mode:** make regexes more rigid ([122a32c](https://github.com/zkat/ssri/commit/122a32c))
### Features
* **api:** added serialize alias for unparse ([999b421](https://github.com/zkat/ssri/commit/999b421))
* **concat:** add Integrity#concat() ([cae12c7](https://github.com/zkat/ssri/commit/cae12c7))
* **pickAlgo:** pick the strongest algorithm provided, by default ([58c18f7](https://github.com/zkat/ssri/commit/58c18f7))
* **strict-mode:** strict SRI support ([3f0b64c](https://github.com/zkat/ssri/commit/3f0b64c))
* **stringify:** replaced unparse/serialize with stringify ([4acad30](https://github.com/zkat/ssri/commit/4acad30))
* **verification:** add opts.pickAlgorithm ([f72e658](https://github.com/zkat/ssri/commit/f72e658))
### BREAKING CHANGES
* **pickAlgo:** ssri will prioritize specific hashes now
* **stringify:** serialize and unparse have been removed. Use ssri.stringify instead.
* **strict-mode:** functions that accepted an optional `sep` argument now expect `opts.sep`.
<a name="1.0.0"></a>
# 1.0.0 (2017-03-23)
### Features
* **api:** implemented initial api ([4fbb16b](https://github.com/zkat/ssri/commit/4fbb16b))
### BREAKING CHANGES
* **api:** Initial API established.
+16
View File
@@ -0,0 +1,16 @@
ISC License
Copyright (c) npm, Inc.
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE COPYRIGHT HOLDER DISCLAIMS
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
USE OR PERFORMANCE OF THIS SOFTWARE.
+488
View File
@@ -0,0 +1,488 @@
# ssri [![npm version](https://img.shields.io/npm/v/ssri.svg)](https://npm.im/ssri) [![license](https://img.shields.io/npm/l/ssri.svg)](https://npm.im/ssri) [![Travis](https://img.shields.io/travis/zkat/ssri.svg)](https://travis-ci.org/zkat/ssri) [![AppVeyor](https://ci.appveyor.com/api/projects/status/github/zkat/ssri?svg=true)](https://ci.appveyor.com/project/zkat/ssri) [![Coverage Status](https://coveralls.io/repos/github/zkat/ssri/badge.svg?branch=latest)](https://coveralls.io/github/zkat/ssri?branch=latest)
[`ssri`](https://github.com/zkat/ssri), short for Standard Subresource
Integrity, is a Node.js utility for parsing, manipulating, serializing,
generating, and verifying [Subresource
Integrity](https://w3c.github.io/webappsec/specs/subresourceintegrity/) hashes.
## Install
`$ npm install --save ssri`
## Table of Contents
* [Example](#example)
* [Features](#features)
* [Contributing](#contributing)
* [API](#api)
* Parsing & Serializing
* [`parse`](#parse)
* [`stringify`](#stringify)
* [`Integrity#concat`](#integrity-concat)
* [`Integrity#toString`](#integrity-to-string)
* [`Integrity#toJSON`](#integrity-to-json)
* [`Integrity#match`](#integrity-match)
* [`Integrity#pickAlgorithm`](#integrity-pick-algorithm)
* [`Integrity#hexDigest`](#integrity-hex-digest)
* Integrity Generation
* [`fromHex`](#from-hex)
* [`fromData`](#from-data)
* [`fromStream`](#from-stream)
* [`create`](#create)
* Integrity Verification
* [`checkData`](#check-data)
* [`checkStream`](#check-stream)
* [`integrityStream`](#integrity-stream)
### Example
```javascript
const ssri = require('ssri')
const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
// Parsing and serializing
const parsed = ssri.parse(integrity)
ssri.stringify(parsed) // === integrity (works on non-Integrity objects)
parsed.toString() // === integrity
// Async stream functions
ssri.checkStream(fs.createReadStream('./my-file'), integrity).then(...)
ssri.fromStream(fs.createReadStream('./my-file')).then(sri => {
sri.toString() === integrity
})
fs.createReadStream('./my-file').pipe(ssri.createCheckerStream(sri))
// Sync data functions
ssri.fromData(fs.readFileSync('./my-file')) // === parsed
ssri.checkData(fs.readFileSync('./my-file'), integrity) // => 'sha512'
```
### Features
* Parses and stringifies SRI strings.
* Generates SRI strings from raw data or Streams.
* Strict standard compliance.
* `?foo` metadata option support.
* Multiple entries for the same algorithm.
* Object-based integrity hash manipulation.
* Small footprint: no dependencies, concise implementation.
* Full test coverage.
* Customizable algorithm picker.
### Contributing
The ssri team enthusiastically welcomes contributions and project participation!
There's a bunch of things you can do if you want to contribute! The [Contributor
Guide](CONTRIBUTING.md) has all the information you need for everything from
reporting bugs to contributing entire new features. Please don't hesitate to
jump in if you'd like to, or even ask us questions if something isn't clear.
### API
#### <a name="parse"></a> `> ssri.parse(sri, [opts]) -> Integrity`
Parses `sri` into an `Integrity` data structure. `sri` can be an integrity
string, an `Hash`-like with `digest` and `algorithm` fields and an optional
`options` field, or an `Integrity`-like object. The resulting object will be an
`Integrity` instance that has this shape:
```javascript
{
'sha1': [{algorithm: 'sha1', digest: 'deadbeef', options: []}],
'sha512': [
{algorithm: 'sha512', digest: 'c0ffee', options: []},
{algorithm: 'sha512', digest: 'bad1dea', options: ['foo']}
],
}
```
If `opts.single` is truthy, a single `Hash` object will be returned. That is, a
single object that looks like `{algorithm, digest, options}`, as opposed to a
larger object with multiple of these.
If `opts.strict` is truthy, the resulting object will be filtered such that
it strictly follows the Subresource Integrity spec, throwing away any entries
with any invalid components. This also means a restricted set of algorithms
will be used -- the spec limits them to `sha256`, `sha384`, and `sha512`.
Strict mode is recommended if the integrity strings are intended for use in
browsers, or in other situations where strict adherence to the spec is needed.
##### Example
```javascript
ssri.parse('sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo') // -> Integrity object
```
#### <a name="stringify"></a> `> ssri.stringify(sri, [opts]) -> String`
This function is identical to [`Integrity#toString()`](#integrity-to-string),
except it can be used on _any_ object that [`parse`](#parse) can handle -- that
is, a string, an `Hash`-like, or an `Integrity`-like.
The `opts.sep` option defines the string to use when joining multiple entries
together. To be spec-compliant, this _must_ be whitespace. The default is a
single space (`' '`).
If `opts.strict` is true, the integrity string will be created using strict
parsing rules. See [`ssri.parse`](#parse).
##### Example
```javascript
// Useful for cleaning up input SRI strings:
ssri.stringify('\n\rsha512-foo\n\t\tsha384-bar')
// -> 'sha512-foo sha384-bar'
// Hash-like: only a single entry.
ssri.stringify({
algorithm: 'sha512',
digest:'9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==',
options: ['foo']
})
// ->
// 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
// Integrity-like: full multi-entry syntax. Similar to output of `ssri.parse`
ssri.stringify({
'sha512': [
{
algorithm: 'sha512',
digest:'9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==',
options: ['foo']
}
]
})
// ->
// 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
```
#### <a name="integrity-concat"></a> `> Integrity#concat(otherIntegrity, [opts]) -> Integrity`
Concatenates an `Integrity` object with another IntegrityLike, or an integrity
string.
This is functionally equivalent to concatenating the string format of both
integrity arguments, and calling [`ssri.parse`](#ssri-parse) on the new string.
If `opts.strict` is true, the new `Integrity` will be created using strict
parsing rules. See [`ssri.parse`](#parse).
##### Example
```javascript
// This will combine the integrity checks for two different versions of
// your index.js file so you can use a single integrity string and serve
// either of these to clients, from a single `<script>` tag.
const desktopIntegrity = ssri.fromData(fs.readFileSync('./index.desktop.js'))
const mobileIntegrity = ssri.fromData(fs.readFileSync('./index.mobile.js'))
// Note that browsers (and ssri) will succeed as long as ONE of the entries
// for the *prioritized* algorithm succeeds. That is, in order for this fallback
// to work, both desktop and mobile *must* use the same `algorithm` values.
desktopIntegrity.concat(mobileIntegrity)
```
#### <a name="integrity-to-string"></a> `> Integrity#toString([opts]) -> String`
Returns the string representation of an `Integrity` object. All hash entries
will be concatenated in the string by `opts.sep`, which defaults to `' '`.
If you want to serialize an object that didn't come from an `ssri` function,
use [`ssri.stringify()`](#stringify).
If `opts.strict` is true, the integrity string will be created using strict
parsing rules. See [`ssri.parse`](#parse).
##### Example
```javascript
const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo'
ssri.parse(integrity).toString() === integrity
```
#### <a name="integrity-to-json"></a> `> Integrity#toJSON() -> String`
Returns the string representation of an `Integrity` object. All hash entries
will be concatenated in the string by `' '`.
This is a convenience method so you can pass an `Integrity` object directly to `JSON.stringify`.
For more info check out [toJSON() behavior on mdn](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior).
##### Example
```javascript
const integrity = '"sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A==?foo"'
JSON.stringify(ssri.parse(integrity)) === integrity
```
#### <a name="integrity-match"></a> `> Integrity#match(sri, [opts]) -> Hash | false`
Returns the matching (truthy) hash if `Integrity` matches the argument passed as
`sri`, which can be anything that [`parse`](#parse) will accept. `opts` will be
passed through to `parse` and [`pickAlgorithm()`](#integrity-pick-algorithm).
##### Example
```javascript
const integrity = 'sha512-9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A=='
ssri.parse(integrity).match(integrity)
// Hash {
// digest: '9KhgCRIx/AmzC8xqYJTZRrnO8OW2Pxyl2DIMZSBOr0oDvtEFyht3xpp71j/r/pAe1DM+JI/A+line3jUBgzQ7A=='
// algorithm: 'sha512'
// }
ssri.parse(integrity).match('sha1-deadbeef')
// false
```
#### <a name="integrity-pick-algorithm"></a> `> Integrity#pickAlgorithm([opts]) -> String`
Returns the "best" algorithm from those available in the integrity object.
If `opts.pickAlgorithm` is provided, it will be passed two algorithms as
arguments. ssri will prioritize whichever of the two algorithms is returned by
this function. Note that the function may be called multiple times, and it
**must** return one of the two algorithms provided. By default, ssri will make
a best-effort to pick the strongest/most reliable of the given algorithms. It
may intentionally deprioritize algorithms with known vulnerabilities.
##### Example
```javascript
ssri.parse('sha1-WEakDigEST sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1').pickAlgorithm() // sha512
```
#### <a name="integrity-hex-digest"></a> `> Integrity#hexDigest() -> String`
`Integrity` is assumed to be either a single-hash `Integrity` instance, or a
`Hash` instance. Returns its `digest`, converted to a hex representation of the
base64 data.
##### Example
```javascript
ssri.parse('sha1-deadbeef').hexDigest() // '75e69d6de79f'
```
#### <a name="from-hex"></a> `> ssri.fromHex(hexDigest, algorithm, [opts]) -> Integrity`
Creates an `Integrity` object with a single entry, based on a hex-formatted
hash. This is a utility function to help convert existing shasums to the
Integrity format, and is roughly equivalent to something like:
```javascript
algorithm + '-' + Buffer.from(hexDigest, 'hex').toString('base64')
```
`opts.options` may optionally be passed in: it must be an array of option
strings that will be added to all generated integrity hashes generated by
`fromData`. This is a loosely-specified feature of SRIs, and currently has no
specified semantics besides being `?`-separated. Use at your own risk, and
probably avoid if your integrity strings are meant to be used with browsers.
If `opts.strict` is true, the integrity object will be created using strict
parsing rules. See [`ssri.parse`](#parse).
If `opts.single` is true, a single `Hash` object will be returned.
##### Example
```javascript
ssri.fromHex('75e69d6de79f', 'sha1').toString() // 'sha1-deadbeef'
```
#### <a name="from-data"></a> `> ssri.fromData(data, [opts]) -> Integrity`
Creates an `Integrity` object from either string or `Buffer` data, calculating
all the requested hashes and adding any specified options to the object.
`opts.algorithms` determines which algorithms to generate hashes for. All
results will be included in a single `Integrity` object. The default value for
`opts.algorithms` is `['sha512']`. All algorithm strings must be hashes listed
in `crypto.getHashes()` for the host Node.js platform.
`opts.options` may optionally be passed in: it must be an array of option
strings that will be added to all generated integrity hashes generated by
`fromData`. This is a loosely-specified feature of SRIs, and currently has no
specified semantics besides being `?`-separated. Use at your own risk, and
probably avoid if your integrity strings are meant to be used with browsers.
If `opts.strict` is true, the integrity object will be created using strict
parsing rules. See [`ssri.parse`](#parse).
##### Example
```javascript
const integrityObj = ssri.fromData('foobarbaz', {
algorithms: ['sha256', 'sha384', 'sha512']
})
integrity.toString('\n')
// ->
// sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0=
// sha384-irnCxQ0CfQhYGlVAUdwTPC9bF3+YWLxlaDGM4xbYminxpbXEq+D+2GCEBTxcjES9
// sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg==
```
#### <a name="from-stream"></a> `> ssri.fromStream(stream, [opts]) -> Promise<Integrity>`
Returns a Promise of an Integrity object calculated by reading data from
a given `stream`.
It accepts both `opts.algorithms` and `opts.options`, which are documented as
part of [`ssri.fromData`](#from-data).
Additionally, `opts.Promise` may be passed in to inject a Promise library of
choice. By default, ssri will use Node's built-in Promises.
If `opts.strict` is true, the integrity object will be created using strict
parsing rules. See [`ssri.parse`](#parse).
##### Example
```javascript
ssri.fromStream(fs.createReadStream('index.js'), {
algorithms: ['sha1', 'sha512']
}).then(integrity => {
return ssri.checkStream(fs.createReadStream('index.js'), integrity)
}) // succeeds
```
#### <a name="create"></a> `> ssri.create([opts]) -> <Hash>`
Returns a Hash object with `update(<Buffer or string>[,enc])` and `digest()` methods.
The Hash object provides the same methods as [crypto class Hash](https://nodejs.org/dist/latest-v6.x/docs/api/crypto.html#crypto_class_hash).
`digest()` accepts no arguments and returns an Integrity object calculated by reading data from
calls to update.
It accepts both `opts.algorithms` and `opts.options`, which are documented as
part of [`ssri.fromData`](#from-data).
If `opts.strict` is true, the integrity object will be created using strict
parsing rules. See [`ssri.parse`](#parse).
##### Example
```javascript
const integrity = ssri.create().update('foobarbaz').digest()
integrity.toString()
// ->
// sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1+9vBnypkYWg==
```
#### <a name="check-data"></a> `> ssri.checkData(data, sri, [opts]) -> Hash|false`
Verifies `data` integrity against an `sri` argument. `data` may be either a
`String` or a `Buffer`, and `sri` can be any subresource integrity
representation that [`ssri.parse`](#parse) can handle.
If verification succeeds, `checkData` will return the name of the algorithm that
was used for verification (a truthy value). Otherwise, it will return `false`.
If `opts.pickAlgorithm` is provided, it will be used by
[`Integrity#pickAlgorithm`](#integrity-pick-algorithm) when deciding which of
the available digests to match against.
If `opts.error` is true, and verification fails, `checkData` will throw either
an `EBADSIZE` or an `EINTEGRITY` error, instead of just returning false.
##### Example
```javascript
const data = fs.readFileSync('index.js')
ssri.checkData(data, ssri.fromData(data)) // -> 'sha512'
ssri.checkData(data, 'sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0')
ssri.checkData(data, 'sha1-BaDDigEST') // -> false
ssri.checkData(data, 'sha1-BaDDigEST', {error: true}) // -> Error! EINTEGRITY
```
#### <a name="check-stream"></a> `> ssri.checkStream(stream, sri, [opts]) -> Promise<Hash>`
Verifies the contents of `stream` against an `sri` argument. `stream` will be
consumed in its entirety by this process. `sri` can be any subresource integrity
representation that [`ssri.parse`](#parse) can handle.
`checkStream` will return a Promise that either resolves to the
`Hash` that succeeded verification, or, if the verification fails
or an error happens with `stream`, the Promise will be rejected.
If the Promise is rejected because verification failed, the returned error will
have `err.code` as `EINTEGRITY`.
If `opts.size` is given, it will be matched against the stream size. An error
with `err.code` `EBADSIZE` will be returned by a rejection if the expected size
and actual size fail to match.
If `opts.pickAlgorithm` is provided, it will be used by
[`Integrity#pickAlgorithm`](#integrity-pick-algorithm) when deciding which of
the available digests to match against.
##### Example
```javascript
const integrity = ssri.fromData(fs.readFileSync('index.js'))
ssri.checkStream(
fs.createReadStream('index.js'),
integrity
)
// ->
// Promise<{
// algorithm: 'sha512',
// digest: 'sha512-yzd8ELD1piyANiWnmdnpCL5F52f10UfUdEkHywVZeqTt0ymgrxR63Qz0GB7TKPoeeZQmWCaz7T1'
// }>
ssri.checkStream(
fs.createReadStream('index.js'),
'sha256-l981iLWj8kurw4UbNy8Lpxqdzd7UOxS50Glhv8FwfZ0'
) // -> Promise<Hash>
ssri.checkStream(
fs.createReadStream('index.js'),
'sha1-BaDDigEST'
) // -> Promise<Error<{code: 'EINTEGRITY'}>>
```
#### <a name="integrity-stream"></a> `> integrityStream([opts]) -> IntegrityStream`
Returns a `Transform` stream that data can be piped through in order to generate
and optionally check data integrity for piped data. When the stream completes
successfully, it emits `size` and `integrity` events, containing the total
number of bytes processed and a calculated `Integrity` instance based on stream
data, respectively.
If `opts.algorithms` is passed in, the listed algorithms will be calculated when
generating the final `Integrity` instance. The default is `['sha512']`.
If `opts.single` is passed in, a single `Hash` instance will be returned.
If `opts.integrity` is passed in, it should be an `integrity` value understood
by [`parse`](#parse) that the stream will check the data against. If
verification succeeds, the integrity stream will emit a `verified` event whose
value is a single `Hash` object that is the one that succeeded verification. If
verification fails, the stream will error with an `EINTEGRITY` error code.
If `opts.size` is given, it will be matched against the stream size. An error
with `err.code` `EBADSIZE` will be emitted by the stream if the expected size
and actual size fail to match.
If `opts.pickAlgorithm` is provided, it will be passed two algorithms as
arguments. ssri will prioritize whichever of the two algorithms is returned by
this function. Note that the function may be called multiple times, and it
**must** return one of the two algorithms provided. By default, ssri will make
a best-effort to pick the strongest/most reliable of the given algorithms. It
may intentionally deprioritize algorithms with known vulnerabilities.
##### Example
```javascript
const integrity = ssri.fromData(fs.readFileSync('index.js'))
fs.createReadStream('index.js')
.pipe(ssri.integrityStream({integrity}))
```
+395
View File
@@ -0,0 +1,395 @@
'use strict'
const crypto = require('crypto')
const figgyPudding = require('figgy-pudding')
const Transform = require('stream').Transform
const SPEC_ALGORITHMS = ['sha256', 'sha384', 'sha512']
const BASE64_REGEX = /^[a-z0-9+/]+(?:=?=?)$/i
const SRI_REGEX = /^([^-]+)-([^?]+)([?\S*]*)$/
const STRICT_SRI_REGEX = /^([^-]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)*$/
const VCHAR_REGEX = /^[\x21-\x7E]+$/
const SsriOpts = figgyPudding({
algorithms: {default: ['sha512']},
error: {default: false},
integrity: {},
options: {default: []},
pickAlgorithm: {default: () => getPrioritizedHash},
Promise: {default: () => Promise},
sep: {default: ' '},
single: {default: false},
size: {},
strict: {default: false}
})
class Hash {
get isHash () { return true }
constructor (hash, opts) {
opts = SsriOpts(opts)
const strict = !!opts.strict
this.source = hash.trim()
// 3.1. Integrity metadata (called "Hash" by ssri)
// https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description
const match = this.source.match(
strict
? STRICT_SRI_REGEX
: SRI_REGEX
)
if (!match) { return }
if (strict && !SPEC_ALGORITHMS.some(a => a === match[1])) { return }
this.algorithm = match[1]
this.digest = match[2]
const rawOpts = match[3]
this.options = rawOpts ? rawOpts.slice(1).split('?') : []
}
hexDigest () {
return this.digest && Buffer.from(this.digest, 'base64').toString('hex')
}
toJSON () {
return this.toString()
}
toString (opts) {
opts = SsriOpts(opts)
if (opts.strict) {
// Strict mode enforces the standard as close to the foot of the
// letter as it can.
if (!(
// The spec has very restricted productions for algorithms.
// https://www.w3.org/TR/CSP2/#source-list-syntax
SPEC_ALGORITHMS.some(x => x === this.algorithm) &&
// Usually, if someone insists on using a "different" base64, we
// leave it as-is, since there's multiple standards, and the
// specified is not a URL-safe variant.
// https://www.w3.org/TR/CSP2/#base64_value
this.digest.match(BASE64_REGEX) &&
// Option syntax is strictly visual chars.
// https://w3c.github.io/webappsec-subresource-integrity/#grammardef-option-expression
// https://tools.ietf.org/html/rfc5234#appendix-B.1
(this.options || []).every(opt => opt.match(VCHAR_REGEX))
)) {
return ''
}
}
const options = this.options && this.options.length
? `?${this.options.join('?')}`
: ''
return `${this.algorithm}-${this.digest}${options}`
}
}
class Integrity {
get isIntegrity () { return true }
toJSON () {
return this.toString()
}
toString (opts) {
opts = SsriOpts(opts)
let sep = opts.sep || ' '
if (opts.strict) {
// Entries must be separated by whitespace, according to spec.
sep = sep.replace(/\S+/g, ' ')
}
return Object.keys(this).map(k => {
return this[k].map(hash => {
return Hash.prototype.toString.call(hash, opts)
}).filter(x => x.length).join(sep)
}).filter(x => x.length).join(sep)
}
concat (integrity, opts) {
opts = SsriOpts(opts)
const other = typeof integrity === 'string'
? integrity
: stringify(integrity, opts)
return parse(`${this.toString(opts)} ${other}`, opts)
}
hexDigest () {
return parse(this, {single: true}).hexDigest()
}
match (integrity, opts) {
opts = SsriOpts(opts)
const other = parse(integrity, opts)
const algo = other.pickAlgorithm(opts)
return (
this[algo] &&
other[algo] &&
this[algo].find(hash =>
other[algo].find(otherhash =>
hash.digest === otherhash.digest
)
)
) || false
}
pickAlgorithm (opts) {
opts = SsriOpts(opts)
const pickAlgorithm = opts.pickAlgorithm
const keys = Object.keys(this)
if (!keys.length) {
throw new Error(`No algorithms available for ${
JSON.stringify(this.toString())
}`)
}
return keys.reduce((acc, algo) => {
return pickAlgorithm(acc, algo) || acc
})
}
}
module.exports.parse = parse
function parse (sri, opts) {
opts = SsriOpts(opts)
if (typeof sri === 'string') {
return _parse(sri, opts)
} else if (sri.algorithm && sri.digest) {
const fullSri = new Integrity()
fullSri[sri.algorithm] = [sri]
return _parse(stringify(fullSri, opts), opts)
} else {
return _parse(stringify(sri, opts), opts)
}
}
function _parse (integrity, opts) {
// 3.4.3. Parse metadata
// https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata
if (opts.single) {
return new Hash(integrity, opts)
}
return integrity.trim().split(/\s+/).reduce((acc, string) => {
const hash = new Hash(string, opts)
if (hash.algorithm && hash.digest) {
const algo = hash.algorithm
if (!acc[algo]) { acc[algo] = [] }
acc[algo].push(hash)
}
return acc
}, new Integrity())
}
module.exports.stringify = stringify
function stringify (obj, opts) {
opts = SsriOpts(opts)
if (obj.algorithm && obj.digest) {
return Hash.prototype.toString.call(obj, opts)
} else if (typeof obj === 'string') {
return stringify(parse(obj, opts), opts)
} else {
return Integrity.prototype.toString.call(obj, opts)
}
}
module.exports.fromHex = fromHex
function fromHex (hexDigest, algorithm, opts) {
opts = SsriOpts(opts)
const optString = opts.options && opts.options.length
? `?${opts.options.join('?')}`
: ''
return parse(
`${algorithm}-${
Buffer.from(hexDigest, 'hex').toString('base64')
}${optString}`, opts
)
}
module.exports.fromData = fromData
function fromData (data, opts) {
opts = SsriOpts(opts)
const algorithms = opts.algorithms
const optString = opts.options && opts.options.length
? `?${opts.options.join('?')}`
: ''
return algorithms.reduce((acc, algo) => {
const digest = crypto.createHash(algo).update(data).digest('base64')
const hash = new Hash(
`${algo}-${digest}${optString}`,
opts
)
if (hash.algorithm && hash.digest) {
const algo = hash.algorithm
if (!acc[algo]) { acc[algo] = [] }
acc[algo].push(hash)
}
return acc
}, new Integrity())
}
module.exports.fromStream = fromStream
function fromStream (stream, opts) {
opts = SsriOpts(opts)
const P = opts.Promise || Promise
const istream = integrityStream(opts)
return new P((resolve, reject) => {
stream.pipe(istream)
stream.on('error', reject)
istream.on('error', reject)
let sri
istream.on('integrity', s => { sri = s })
istream.on('end', () => resolve(sri))
istream.on('data', () => {})
})
}
module.exports.checkData = checkData
function checkData (data, sri, opts) {
opts = SsriOpts(opts)
sri = parse(sri, opts)
if (!Object.keys(sri).length) {
if (opts.error) {
throw Object.assign(
new Error('No valid integrity hashes to check against'), {
code: 'EINTEGRITY'
}
)
} else {
return false
}
}
const algorithm = sri.pickAlgorithm(opts)
const digest = crypto.createHash(algorithm).update(data).digest('base64')
const newSri = parse({algorithm, digest})
const match = newSri.match(sri, opts)
if (match || !opts.error) {
return match
} else if (typeof opts.size === 'number' && (data.length !== opts.size)) {
const err = new Error(`data size mismatch when checking ${sri}.\n Wanted: ${opts.size}\n Found: ${data.length}`)
err.code = 'EBADSIZE'
err.found = data.length
err.expected = opts.size
err.sri = sri
throw err
} else {
const err = new Error(`Integrity checksum failed when using ${algorithm}: Wanted ${sri}, but got ${newSri}. (${data.length} bytes)`)
err.code = 'EINTEGRITY'
err.found = newSri
err.expected = sri
err.algorithm = algorithm
err.sri = sri
throw err
}
}
module.exports.checkStream = checkStream
function checkStream (stream, sri, opts) {
opts = SsriOpts(opts)
const P = opts.Promise || Promise
const checker = integrityStream(opts.concat({
integrity: sri
}))
return new P((resolve, reject) => {
stream.pipe(checker)
stream.on('error', reject)
checker.on('error', reject)
let sri
checker.on('verified', s => { sri = s })
checker.on('end', () => resolve(sri))
checker.on('data', () => {})
})
}
module.exports.integrityStream = integrityStream
function integrityStream (opts) {
opts = SsriOpts(opts)
// For verification
const sri = opts.integrity && parse(opts.integrity, opts)
const goodSri = sri && Object.keys(sri).length
const algorithm = goodSri && sri.pickAlgorithm(opts)
const digests = goodSri && sri[algorithm]
// Calculating stream
const algorithms = Array.from(
new Set(opts.algorithms.concat(algorithm ? [algorithm] : []))
)
const hashes = algorithms.map(crypto.createHash)
let streamSize = 0
const stream = new Transform({
transform (chunk, enc, cb) {
streamSize += chunk.length
hashes.forEach(h => h.update(chunk, enc))
cb(null, chunk, enc)
}
}).on('end', () => {
const optString = (opts.options && opts.options.length)
? `?${opts.options.join('?')}`
: ''
const newSri = parse(hashes.map((h, i) => {
return `${algorithms[i]}-${h.digest('base64')}${optString}`
}).join(' '), opts)
// Integrity verification mode
const match = goodSri && newSri.match(sri, opts)
if (typeof opts.size === 'number' && streamSize !== opts.size) {
const err = new Error(`stream size mismatch when checking ${sri}.\n Wanted: ${opts.size}\n Found: ${streamSize}`)
err.code = 'EBADSIZE'
err.found = streamSize
err.expected = opts.size
err.sri = sri
stream.emit('error', err)
} else if (opts.integrity && !match) {
const err = new Error(`${sri} integrity checksum failed when using ${algorithm}: wanted ${digests} but got ${newSri}. (${streamSize} bytes)`)
err.code = 'EINTEGRITY'
err.found = newSri
err.expected = digests
err.algorithm = algorithm
err.sri = sri
stream.emit('error', err)
} else {
stream.emit('size', streamSize)
stream.emit('integrity', newSri)
match && stream.emit('verified', match)
}
})
return stream
}
module.exports.create = createIntegrity
function createIntegrity (opts) {
opts = SsriOpts(opts)
const algorithms = opts.algorithms
const optString = opts.options.length
? `?${opts.options.join('?')}`
: ''
const hashes = algorithms.map(crypto.createHash)
return {
update: function (chunk, enc) {
hashes.forEach(h => h.update(chunk, enc))
return this
},
digest: function (enc) {
const integrity = algorithms.reduce((acc, algo) => {
const digest = hashes.shift().digest('base64')
const hash = new Hash(
`${algo}-${digest}${optString}`,
opts
)
if (hash.algorithm && hash.digest) {
const algo = hash.algorithm
if (!acc[algo]) { acc[algo] = [] }
acc[algo].push(hash)
}
return acc
}, new Integrity())
return integrity
}
}
}
const NODE_HASHES = new Set(crypto.getHashes())
// This is a Best Effort™ at a reasonable priority for hash algos
const DEFAULT_PRIORITY = [
'md5', 'whirlpool', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512',
// TODO - it's unclear _which_ of these Node will actually use as its name
// for the algorithm, so we guesswork it based on the OpenSSL names.
'sha3',
'sha3-256', 'sha3-384', 'sha3-512',
'sha3_256', 'sha3_384', 'sha3_512'
].filter(algo => NODE_HASHES.has(algo))
function getPrioritizedHash (algo1, algo2) {
return DEFAULT_PRIORITY.indexOf(algo1.toLowerCase()) >= DEFAULT_PRIORITY.indexOf(algo2.toLowerCase())
? algo1
: algo2
}
+92
View File
@@ -0,0 +1,92 @@
{
"_args": [
[
"ssri@6.0.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "ssri@6.0.1",
"_id": "ssri@6.0.1",
"_inBundle": false,
"_integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==",
"_location": "/webpack/ssri",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "ssri@6.0.1",
"name": "ssri",
"escapedName": "ssri",
"rawSpec": "6.0.1",
"saveSpec": null,
"fetchSpec": "6.0.1"
},
"_requiredBy": [
"/webpack/cacache"
],
"_resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz",
"_spec": "6.0.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Kat Marchán",
"email": "kzm@sykosomatic.org"
},
"bugs": {
"url": "https://github.com/zkat/ssri/issues"
},
"config": {
"nyc": {
"exclude": [
"node_modules/**",
"test/**"
]
}
},
"dependencies": {
"figgy-pudding": "^3.5.1"
},
"description": "Standard Subresource Integrity library -- parses, serializes, generates, and verifies integrity metadata according to the SRI spec.",
"devDependencies": {
"nyc": "^11.4.1",
"standard": "^10.0.3",
"standard-version": "^4.3.0",
"tap": "^11.1.0",
"weallbehave": "^1.2.0",
"weallcontribute": "^1.0.8"
},
"files": [
"*.js"
],
"homepage": "https://github.com/zkat/ssri#readme",
"keywords": [
"w3c",
"web",
"security",
"integrity",
"checksum",
"hashing",
"subresource integrity",
"sri",
"sri hash",
"sri string",
"sri generator",
"html"
],
"license": "ISC",
"main": "index.js",
"name": "ssri",
"repository": {
"type": "git",
"url": "git+https://github.com/zkat/ssri.git"
},
"scripts": {
"postrelease": "npm publish && git push --follow-tags",
"prerelease": "npm t",
"pretest": "standard",
"release": "standard-version -s",
"test": "tap -J --coverage test/*.js",
"update-coc": "weallbehave -o . && git add CODE_OF_CONDUCT.md && git commit -m 'docs(coc): updated CODE_OF_CONDUCT.md'",
"update-contrib": "weallcontribute -o . && git add CONTRIBUTING.md && git commit -m 'docs(contributing): updated CONTRIBUTING.md'"
},
"version": "6.0.1"
}
+149
View File
@@ -0,0 +1,149 @@
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [1.4.4](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.4.3...v1.4.4) (2020-06-03)
### Bug Fixes
* security problem ([#257](https://github.com/webpack-contrib/terser-webpack-plugin/issues/257)) ([915e25b](https://github.com/webpack-contrib/terser-webpack-plugin/commit/915e25be50c08740ecb9878a7d004091ef17131d))
### [1.4.3](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.4.2...v1.4.3) (2019-12-11)
### SECURITY
* update `serialize-javascript` to `2.1.2` version.
### [1.4.2](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.4.1...v1.4.2) (2019-12-06)
### SECURITY
* update `serialize-javascript` to `2.1.1` version.
### [1.4.1](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.4.0...v1.4.1) (2019-07-31)
### Bug Fixes
* removed unnecessary dependency ([#111](https://github.com/webpack-contrib/terser-webpack-plugin/issues/111)) ([bc19b72](https://github.com/webpack-contrib/terser-webpack-plugin/commit/bc19b72))
## [1.4.0](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.3.0...v1.4.0) (2019-07-31)
### Features
* generate higher quality SourceMaps ([#109](https://github.com/webpack-contrib/terser-webpack-plugin/issues/109)) ([9d777f0](https://github.com/webpack-contrib/terser-webpack-plugin/commit/9d777f0))
## [1.3.0](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.2.4...v1.3.0) (2019-05-24)
### Features
* update terser to 4 version ([#97](https://github.com/webpack-contrib/terser-webpack-plugin/issues/97)) ([15d1595](https://github.com/webpack-contrib/terser-webpack-plugin/commit/15d1595))
### [1.2.4](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.2.3...v1.2.4) (2019-05-15)
### Bug Fixes
* disable parallel on WSL due bugs ([#90](https://github.com/webpack-contrib/terser-webpack-plugin/issues/90)) ([d9533dd](https://github.com/webpack-contrib/terser-webpack-plugin/commit/d9533dd))
* fallback for cache directory ([#86](https://github.com/webpack-contrib/terser-webpack-plugin/issues/86)) ([3cdd2ed](https://github.com/webpack-contrib/terser-webpack-plugin/commit/3cdd2ed))
<a name="1.2.3"></a>
## [1.2.3](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.2.2...v1.2.3) (2019-02-25)
### Bug Fixes
* invalidate cache after changing node version ([675edfd](https://github.com/webpack-contrib/terser-webpack-plugin/commit/675edfd))
<a name="1.2.2"></a>
## [1.2.2](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.2.1...v1.2.2) (2019-02-04)
### Bug Fixes
* cannot read property 'minify' of undefined ([#69](https://github.com/webpack-contrib/terser-webpack-plugin/issues/69)) ([0593d7c](https://github.com/webpack-contrib/terser-webpack-plugin/commit/0593d7c))
<a name="1.2.1"></a>
## [1.2.1](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.2.0...v1.2.1) (2018-12-27)
### Bug Fixes
* don't crash when no extracted comments ([#49](https://github.com/webpack-contrib/terser-webpack-plugin/issues/49)) ([efad586](https://github.com/webpack-contrib/terser-webpack-plugin/commit/efad586))
<a name="1.2.0"></a>
# [1.2.0](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.1.0...v1.2.0) (2018-12-22)
### Bug Fixes
* `chunks` is a `Set` in webpack@5 ([#19](https://github.com/webpack-contrib/terser-webpack-plugin/issues/19)) ([df8c425](https://github.com/webpack-contrib/terser-webpack-plugin/commit/df8c425))
* catch `work-farm` errors ([#35](https://github.com/webpack-contrib/terser-webpack-plugin/issues/35)) ([2bdcd38](https://github.com/webpack-contrib/terser-webpack-plugin/commit/2bdcd38))
* dedupe extracted comments ([#40](https://github.com/webpack-contrib/terser-webpack-plugin/issues/40)) ([7f4a159](https://github.com/webpack-contrib/terser-webpack-plugin/commit/7f4a159))
* more consistent cache ([#43](https://github.com/webpack-contrib/terser-webpack-plugin/issues/43)) ([36f5f3c](https://github.com/webpack-contrib/terser-webpack-plugin/commit/36f5f3c))
* regenerate `contenthash` when assets was uglified ([#44](https://github.com/webpack-contrib/terser-webpack-plugin/issues/44)) ([7e6f8b1](https://github.com/webpack-contrib/terser-webpack-plugin/commit/7e6f8b1))
### Features
* `chunkFilter` option for filtering chunks ([#38](https://github.com/webpack-contrib/terser-webpack-plugin/issues/38)) ([7ffe57c](https://github.com/webpack-contrib/terser-webpack-plugin/commit/7ffe57c))
* uglify `mjs` by default ([#39](https://github.com/webpack-contrib/terser-webpack-plugin/issues/39)) ([1644620](https://github.com/webpack-contrib/terser-webpack-plugin/commit/1644620))
<a name="1.1.0"></a>
# [1.1.0](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.0.1...v1.1.0) (2018-09-14)
### Bug Fixes
* extract comment conditions is case insensitivity ([19e1e43](https://github.com/webpack-contrib/terser-webpack-plugin/commit/19e1e43))
### Features
* full coverage schema options validation ([#8](https://github.com/webpack-contrib/terser-webpack-plugin/issues/8)) ([68e531e](https://github.com/webpack-contrib/terser-webpack-plugin/commit/68e531e))
<a name="1.0.2"></a>
## [1.0.2](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.0.1...v1.0.2) (2018-08-16)
### Internal
* migrate from `@webpack-contrib/schema-utils` to `schema-utils` ([a5432da](https://github.com/webpack-contrib/terser-webpack-plugin/commit/a5432da))
<a name="1.0.1"></a>
## [1.0.1](https://github.com/webpack-contrib/terser-webpack-plugin/compare/v1.0.0...v1.0.1) (2018-08-15)
### Bug Fixes
* `cpus` length in a chroot environment (`os.cpus()`) ([#4](https://github.com/webpack-contrib/terser-webpack-plugin/issues/4)) ([9375646](https://github.com/webpack-contrib/terser-webpack-plugin/commit/9375646))
<a name="1.0.0"></a>
# 1.0.0 (2018-08-02)
Initial publish release
# Change Log
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+20
View File
@@ -0,0 +1,20 @@
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+731
View File
@@ -0,0 +1,731 @@
<div align="center">
<a href="https://github.com/webpack/webpack">
<img width="200" height="200" src="https://webpack.js.org/assets/icon-square-big.svg">
</a>
</div>
[![npm][npm]][npm-url]
[![node][node]][node-url]
[![deps][deps]][deps-url]
[![tests][tests]][tests-url]
[![cover][cover]][cover-url]
[![chat][chat]][chat-url]
[![size][size]][size-url]
# terser-webpack-plugin
This plugin uses [terser](https://github.com/terser-js/terser) to minify your JavaScript.
> ️ For `webpack@3` use [terser-webpack-plugin-legacy](https://www.npmjs.com/package/terser-webpack-plugin-legacy) package
## Getting Started
To begin, you'll need to install `terser-webpack-plugin`:
```console
$ npm install terser-webpack-plugin --save-dev
```
Then add the plugin to your `webpack` config. For example:
**webpack.config.js**
```js
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new TerserPlugin()],
},
};
```
And run `webpack` via your preferred method.
## Options
### `test`
Type: `String|RegExp|Array<String|RegExp>`
Default: `/\.m?js(\?.*)?$/i`
Test to match files against.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
test: /\.js(\?.*)?$/i,
}),
],
},
};
```
### `include`
Type: `String|RegExp|Array<String|RegExp>`
Default: `undefined`
Files to include.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
include: /\/includes/,
}),
],
},
};
```
### `exclude`
Type: `String|RegExp|Array<String|RegExp>`
Default: `undefined`
Files to exclude.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
exclude: /\/excludes/,
}),
],
},
};
```
### `chunkFilter`
Type: `Function<(chunk) -> boolean>`
Default: `() => true`
Allowing to filter which chunks should be uglified (by default all chunks are uglified).
Return `true` to uglify the chunk, `false` otherwise.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
chunkFilter: (chunk) => {
// Exclude uglification for the `vendor` chunk
if (chunk.name === 'vendor') {
return false;
}
return true;
},
}),
],
},
};
```
### `cache`
Type: `Boolean|String`
Default: `false`
Enable file caching.
Default path to cache directory: `node_modules/.cache/terser-webpack-plugin`.
> ️ If you use your own `minify` function please read the `minify` section for cache invalidation correctly.
#### `Boolean`
Enable/disable file caching.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
}),
],
},
};
```
#### `String`
Enable file caching and set path to cache directory.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
cache: 'path/to/cache',
}),
],
},
};
```
### `cacheKeys`
Type: `Function<(defaultCacheKeys, file) -> Object>`
Default: `defaultCacheKeys => defaultCacheKeys`
Allows you to override default cache keys.
Default cache keys:
```js
({
terser: require('terser/package.json').version, // terser version
'terser-webpack-plugin': require('../package.json').version, // plugin version
'terser-webpack-plugin-options': this.options, // plugin options
path: compiler.outputPath ? `${compiler.outputPath}/${file}` : file, // asset path
hash: crypto
.createHash('md4')
.update(input)
.digest('hex'), // source file hash
});
```
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
cacheKeys: (defaultCacheKeys, file) => {
defaultCacheKeys.myCacheKey = 'myCacheKeyValue';
return defaultCacheKeys;
},
}),
],
},
};
```
### `parallel`
Type: `Boolean|Number`
Default: `false`
Use multi-process parallel running to improve the build speed.
Default number of concurrent runs: `os.cpus().length - 1`.
> ️ Parallelization can speedup your build significantly and is therefore **highly recommended**.
#### `Boolean`
Enable/disable multi-process parallel running.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
},
};
```
#### `Number`
Enable multi-process parallel running and set number of concurrent runs.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
parallel: 4,
}),
],
},
};
```
### `sourceMap`
Type: `Boolean`
Default: `false`
Use source maps to map error message locations to modules (this slows down the compilation).
If you use your own `minify` function please read the `minify` section for handling source maps correctly.
> ⚠️ **`cheap-source-map` options don't work with this plugin**.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
sourceMap: true,
}),
],
},
};
```
### `minify`
Type: `Function`
Default: `undefined`
Allows you to override default minify function.
By default plugin uses [terser](https://github.com/terser-js/terser) package.
Useful for using and testing unpublished versions or forks.
> ⚠️ **Always use `require` inside `minify` function when `parallel` option enabled**.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
minify: (file, sourceMap) => {
const extractedComments = [];
// Custom logic for extract comments
const { error, map, code, warnings } = require('uglify-module') // Or require('./path/to/uglify-module')
.minify(file, {
/* Your options for minification */
});
return { error, map, code, warnings, extractedComments };
},
}),
],
},
};
```
### `terserOptions`
Type: `Object`
Default: [default](https://github.com/terser-js/terser#minify-options)
Terser minify [options](https://github.com/terser-js/terser#minify-options).
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
ecma: undefined,
warnings: false,
parse: {},
compress: {},
mangle: true, // Note `mangle.properties` is `false` by default.
module: false,
output: null,
toplevel: false,
nameCache: null,
ie8: false,
keep_classnames: undefined,
keep_fnames: false,
safari10: false,
},
}),
],
},
};
```
### `extractComments`
Type: `Boolean|String|RegExp|Function<(node, comment) -> Boolean|Object>|Object`
Default: `false`
Whether comments shall be extracted to a separate file, (see [details](https://github.com/webpack/webpack/commit/71933e979e51c533b432658d5e37917f9e71595a)).
By default extract only comments using `/^\**!|@preserve|@license|@cc_on/i` regexp condition and remove remaining comments.
If the original file is named `foo.js`, then the comments will be stored to `foo.js.LICENSE`.
The `terserOptions.output.comments` option specifies whether the comment will be preserved, i.e. it is possible to preserve some comments (e.g. annotations) while extracting others or even preserving comments that have been extracted.
#### `Boolean`
Enable/disable extracting comments.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: true,
}),
],
},
};
```
#### `String`
Extract `all` or `some` (use `/^\**!|@preserve|@license|@cc_on/i` RegExp) comments.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: 'all',
}),
],
},
};
```
#### `RegExp`
All comments that match the given expression will be extracted to the separate file.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: /@extract/i,
}),
],
},
};
```
#### `Function<(node, comment) -> Boolean>`
All comments that match the given expression will be extracted to the separate file.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: (astNode, comment) => {
if (/@extract/i.test(comment.value)) {
return true;
}
return false;
},
}),
],
},
};
```
#### `Object`
Allow to customize condition for extract comments, specify extracted file name and banner.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: {
condition: /^\**!|@preserve|@license|@cc_on/i,
filename: (file) => {
return `${file}.LICENSE`;
},
banner: (licenseFile) => {
return `License information can be found in ${licenseFile}`;
},
},
}),
],
},
};
```
##### `condition`
Type: `Boolean|String|RegExp|Function<(node, comment) -> Boolean|Object>`
Condition what comments you need extract.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: {
condition: 'some',
filename: (file) => {
return `${file}.LICENSE`;
},
banner: (licenseFile) => {
return `License information can be found in ${licenseFile}`;
},
},
}),
],
},
};
```
##### `filename`
Type: `String|Function<(string) -> String>`
Default: `${file}.LICENSE`
The file where the extracted comments will be stored.
Default is to append the suffix `.LICENSE` to the original filename.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: {
condition: /^\**!|@preserve|@license|@cc_on/i,
filename: 'extracted-comments.js',
banner: (licenseFile) => {
return `License information can be found in ${licenseFile}`;
},
},
}),
],
},
};
```
##### `banner`
Type: `Boolean|String|Function<(string) -> String>`
Default: `/*! For license information please see ${commentsFile} */`
The banner text that points to the extracted file and will be added on top of the original file.
Can be `false` (no banner), a `String`, or a `Function<(string) -> String>` that will be called with the filename where extracted comments have been stored.
Will be wrapped into comment.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
extractComments: {
condition: true,
filename: (file) => {
return `${file}.LICENSE`;
},
banner: (commentsFile) => {
return `My custom banner about license information ${commentsFile}`;
},
},
}),
],
},
};
```
### `warningsFilter`
Type: `Function<(warning, source) -> Boolean>`
Default: `() => true`
Allow to filter [terser](https://github.com/terser-js/terser) warnings.
Return `true` to keep the warning, `false` otherwise.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
warningsFilter: (warning, source) => {
if (/Dropping unreachable code/i.test(warning)) {
return true;
}
if (/filename\.js/i.test(source)) {
return true;
}
return false;
},
}),
],
},
};
```
## Examples
### Cache And Parallel
Enable cache and multi-process parallel running.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
}),
],
},
};
```
### Preserve Comments
Extract all legal comments (i.e. `/^\**!|@preserve|@license|@cc_on/i`) and preserve `/@license/i` comments.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: /@license/i,
},
},
extractComments: true,
}),
],
},
};
```
### Remove Comments
If you avoid building with comments, set **terserOptions.output.comments** to **false** as in this config:
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
terserOptions: {
output: {
comments: false,
},
},
}),
],
},
};
```
### Custom Minify Function
Override default minify function - use `uglify-js` for minification.
**webpack.config.js**
```js
module.exports = {
optimization: {
minimizer: [
new TerserPlugin({
// Uncomment lines below for cache invalidation correctly
// cache: true,
// cacheKeys: (defaultCacheKeys) => {
// delete defaultCacheKeys.terser;
//
// return Object.assign(
// {},
// defaultCacheKeys,
// { 'uglify-js': require('uglify-js/package.json').version },
// );
// },
minify: (file, sourceMap) => {
// https://github.com/mishoo/UglifyJS2#minify-options
const uglifyJsOptions = {
/* your `uglify-js` package options */
};
if (sourceMap) {
uglifyJsOptions.sourceMap = {
content: sourceMap,
};
}
return require('uglify-js').minify(file, uglifyJsOptions);
},
}),
],
},
};
```
## Contributing
Please take a moment to read our contributing guidelines if you haven't yet done so.
[CONTRIBUTING](./.github/CONTRIBUTING.md)
## License
[MIT](./LICENSE)
[npm]: https://img.shields.io/npm/v/terser-webpack-plugin.svg
[npm-url]: https://npmjs.com/package/terser-webpack-plugin
[node]: https://img.shields.io/node/v/terser-webpack-plugin.svg
[node-url]: https://nodejs.org
[deps]: https://david-dm.org/webpack-contrib/terser-webpack-plugin.svg
[deps-url]: https://david-dm.org/webpack-contrib/terser-webpack-plugin
[tests]: https://dev.azure.com/webpack-contrib/terser-webpack-plugin/_apis/build/status/webpack-contrib.terser-webpack-plugin?branchName=master
[tests-url]: https://dev.azure.com/webpack-contrib/terser-webpack-plugin/_build/latest?definitionId=7&branchName=master
[cover]: https://codecov.io/gh/webpack-contrib/terser-webpack-plugin/branch/master/graph/badge.svg
[cover-url]: https://codecov.io/gh/webpack-contrib/terser-webpack-plugin
[chat]: https://img.shields.io/badge/gitter-webpack%2Fwebpack-brightgreen.svg
[chat-url]: https://gitter.im/webpack/webpack
[size]: https://packagephobia.now.sh/badge?p=terser-webpack-plugin
[size-url]: https://packagephobia.now.sh/result?p=terser-webpack-plugin
@@ -0,0 +1,126 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _os = _interopRequireDefault(require("os"));
var _cacache = _interopRequireDefault(require("cacache"));
var _findCacheDir = _interopRequireDefault(require("find-cache-dir"));
var _workerFarm = _interopRequireDefault(require("worker-farm"));
var _serializeJavascript = _interopRequireDefault(require("serialize-javascript"));
var _isWsl = _interopRequireDefault(require("is-wsl"));
var _minify = _interopRequireDefault(require("./minify"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const worker = require.resolve('./worker');
class TaskRunner {
constructor(options = {}) {
const {
cache,
parallel
} = options;
this.cacheDir = cache === true ? (0, _findCacheDir.default)({
name: 'terser-webpack-plugin'
}) || _os.default.tmpdir() : cache; // In some cases cpus() returns undefined
// https://github.com/nodejs/node/issues/19022
const cpus = _os.default.cpus() || {
length: 1
}; // WSL sometimes freezes, error seems to be on the WSL side
// https://github.com/webpack-contrib/terser-webpack-plugin/issues/21
this.maxConcurrentWorkers = _isWsl.default ? 1 : parallel === true ? cpus.length - 1 : Math.min(Number(parallel) || 0, cpus.length - 1);
}
run(tasks, callback) {
/* istanbul ignore if */
if (!tasks.length) {
callback(null, []);
return;
}
if (this.maxConcurrentWorkers > 1) {
const workerOptions = process.platform === 'win32' ? {
maxConcurrentWorkers: this.maxConcurrentWorkers,
maxConcurrentCallsPerWorker: 1
} : {
maxConcurrentWorkers: this.maxConcurrentWorkers
};
this.workers = (0, _workerFarm.default)(workerOptions, worker);
this.boundWorkers = (options, cb) => {
try {
this.workers((0, _serializeJavascript.default)(options), cb);
} catch (error) {
// worker-farm can fail with ENOMEM or something else
cb(error);
}
};
} else {
this.boundWorkers = (options, cb) => {
try {
cb(null, (0, _minify.default)(options));
} catch (error) {
cb(error);
}
};
}
let toRun = tasks.length;
const results = [];
const step = (index, data) => {
toRun -= 1;
results[index] = data;
if (!toRun) {
callback(null, results);
}
};
tasks.forEach((task, index) => {
const enqueue = () => {
this.boundWorkers(task, (error, data) => {
const result = error ? {
error
} : data;
const done = () => step(index, result);
if (this.cacheDir && !result.error) {
_cacache.default.put(this.cacheDir, (0, _serializeJavascript.default)(task.cacheKeys), JSON.stringify(data)).then(done, done);
} else {
done();
}
});
};
if (this.cacheDir) {
_cacache.default.get(this.cacheDir, (0, _serializeJavascript.default)(task.cacheKeys)).then(({
data
}) => step(index, JSON.parse(data)), enqueue);
} else {
enqueue();
}
});
}
exit() {
if (this.workers) {
_workerFarm.default.end(this.workers);
}
}
}
exports.default = TaskRunner;
+5
View File
@@ -0,0 +1,5 @@
"use strict";
const plugin = require('./index');
module.exports = plugin.default;
+356
View File
@@ -0,0 +1,356 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _crypto = _interopRequireDefault(require("crypto"));
var _path = _interopRequireDefault(require("path"));
var _sourceMap = require("source-map");
var _webpackSources = require("webpack-sources");
var _RequestShortener = _interopRequireDefault(require("webpack/lib/RequestShortener"));
var _ModuleFilenameHelpers = _interopRequireDefault(require("webpack/lib/ModuleFilenameHelpers"));
var _schemaUtils = _interopRequireDefault(require("schema-utils"));
var _serializeJavascript = _interopRequireDefault(require("serialize-javascript"));
var _package = _interopRequireDefault(require("terser/package.json"));
var _options = _interopRequireDefault(require("./options.json"));
var _TaskRunner = _interopRequireDefault(require("./TaskRunner"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const warningRegex = /\[.+:([0-9]+),([0-9]+)\]/;
class TerserPlugin {
constructor(options = {}) {
(0, _schemaUtils.default)(_options.default, options, 'Terser Plugin');
const {
minify,
terserOptions = {},
test = /\.m?js(\?.*)?$/i,
chunkFilter = () => true,
warningsFilter = () => true,
extractComments = false,
sourceMap = false,
cache = false,
cacheKeys = defaultCacheKeys => defaultCacheKeys,
parallel = false,
include,
exclude
} = options;
this.options = {
test,
chunkFilter,
warningsFilter,
extractComments,
sourceMap,
cache,
cacheKeys,
parallel,
include,
exclude,
minify,
terserOptions: _objectSpread({
output: {
comments: extractComments ? false : /^\**!|@preserve|@license|@cc_on/i
}
}, terserOptions)
};
}
static isSourceMap(input) {
// All required options for `new SourceMapConsumer(...options)`
// https://github.com/mozilla/source-map#new-sourcemapconsumerrawsourcemap
return Boolean(input && input.version && input.sources && Array.isArray(input.sources) && typeof input.mappings === 'string');
}
static buildSourceMap(inputSourceMap) {
if (!inputSourceMap || !TerserPlugin.isSourceMap(inputSourceMap)) {
return null;
}
return new _sourceMap.SourceMapConsumer(inputSourceMap);
}
static buildError(err, file, sourceMap, requestShortener) {
// Handling error which should have line, col, filename and message
if (err.line) {
const original = sourceMap && sourceMap.originalPositionFor({
line: err.line,
column: err.col
});
if (original && original.source && requestShortener) {
return new Error(`${file} from Terser\n${err.message} [${requestShortener.shorten(original.source)}:${original.line},${original.column}][${file}:${err.line},${err.col}]`);
}
return new Error(`${file} from Terser\n${err.message} [${file}:${err.line},${err.col}]`);
} else if (err.stack) {
return new Error(`${file} from Terser\n${err.stack}`);
}
return new Error(`${file} from Terser\n${err.message}`);
}
static buildWarning(warning, file, sourceMap, requestShortener, warningsFilter) {
let warningMessage = warning;
let locationMessage = '';
let source = null;
if (sourceMap) {
const match = warningRegex.exec(warning);
if (match) {
const line = +match[1];
const column = +match[2];
const original = sourceMap.originalPositionFor({
line,
column
});
if (original && original.source && original.source !== file && requestShortener) {
({
source
} = original);
warningMessage = `${warningMessage.replace(warningRegex, '')}`;
locationMessage = `[${requestShortener.shorten(original.source)}:${original.line},${original.column}]`;
}
}
}
if (warningsFilter && !warningsFilter(warning, source)) {
return null;
}
return `Terser Plugin: ${warningMessage}${locationMessage}`;
}
apply(compiler) {
const buildModuleFn = moduleArg => {
// to get detailed location info about errors
moduleArg.useSourceMap = true;
};
const optimizeFn = (compilation, chunks, callback) => {
const taskRunner = new _TaskRunner.default({
cache: this.options.cache,
parallel: this.options.parallel
});
const processedAssets = new WeakSet();
const tasks = [];
const {
chunkFilter
} = this.options;
Array.from(chunks).filter(chunk => chunkFilter && chunkFilter(chunk)).reduce((acc, chunk) => acc.concat(chunk.files || []), []).concat(compilation.additionalChunkAssets || []).filter(_ModuleFilenameHelpers.default.matchObject.bind(null, this.options)).forEach(file => {
let inputSourceMap;
const asset = compilation.assets[file];
if (processedAssets.has(asset)) {
return;
}
try {
let input;
if (this.options.sourceMap && asset.sourceAndMap) {
const {
source,
map
} = asset.sourceAndMap();
input = source;
if (TerserPlugin.isSourceMap(map)) {
inputSourceMap = map;
} else {
inputSourceMap = map;
compilation.warnings.push(new Error(`${file} contains invalid source map`));
}
} else {
input = asset.source();
inputSourceMap = null;
} // Handling comment extraction
let commentsFile = false;
if (this.options.extractComments) {
commentsFile = this.options.extractComments.filename || `${file}.LICENSE`;
if (typeof commentsFile === 'function') {
commentsFile = commentsFile(file);
}
}
const task = {
file,
input,
inputSourceMap,
commentsFile,
extractComments: this.options.extractComments,
terserOptions: this.options.terserOptions,
minify: this.options.minify
};
if (this.options.cache) {
const defaultCacheKeys = {
terser: _package.default.version,
node_version: process.version,
// eslint-disable-next-line global-require
'terser-webpack-plugin': require('../package.json').version,
'terser-webpack-plugin-options': this.options,
hash: _crypto.default.createHash('md4').update(input).digest('hex')
};
task.cacheKeys = this.options.cacheKeys(defaultCacheKeys, file);
}
tasks.push(task);
} catch (error) {
compilation.errors.push(TerserPlugin.buildError(error, file, TerserPlugin.buildSourceMap(inputSourceMap), new _RequestShortener.default(compiler.context)));
}
});
taskRunner.run(tasks, (tasksError, results) => {
if (tasksError) {
compilation.errors.push(tasksError);
return;
}
results.forEach((data, index) => {
const {
file,
input,
inputSourceMap,
commentsFile
} = tasks[index];
const {
error,
map,
code,
warnings
} = data;
let {
extractedComments
} = data;
let sourceMap = null;
if (error || warnings && warnings.length > 0) {
sourceMap = TerserPlugin.buildSourceMap(inputSourceMap);
} // Handling results
// Error case: add errors, and go to next file
if (error) {
compilation.errors.push(TerserPlugin.buildError(error, file, sourceMap, new _RequestShortener.default(compiler.context)));
return;
}
let outputSource;
if (map) {
outputSource = new _webpackSources.SourceMapSource(code, file, JSON.parse(map), input, inputSourceMap, true);
} else {
outputSource = new _webpackSources.RawSource(code);
} // Write extracted comments to commentsFile
if (commentsFile && extractedComments && extractedComments.length > 0) {
if (commentsFile in compilation.assets) {
const commentsFileSource = compilation.assets[commentsFile].source();
extractedComments = extractedComments.filter(comment => !commentsFileSource.includes(comment));
}
if (extractedComments.length > 0) {
// Add a banner to the original file
if (this.options.extractComments.banner !== false) {
let banner = this.options.extractComments.banner || `For license information please see ${_path.default.posix.basename(commentsFile)}`;
if (typeof banner === 'function') {
banner = banner(commentsFile);
}
if (banner) {
outputSource = new _webpackSources.ConcatSource(`/*! ${banner} */\n`, outputSource);
}
}
const commentsSource = new _webpackSources.RawSource(`${extractedComments.join('\n\n')}\n`);
if (commentsFile in compilation.assets) {
// commentsFile already exists, append new comments...
if (compilation.assets[commentsFile] instanceof _webpackSources.ConcatSource) {
compilation.assets[commentsFile].add('\n');
compilation.assets[commentsFile].add(commentsSource);
} else {
compilation.assets[commentsFile] = new _webpackSources.ConcatSource(compilation.assets[commentsFile], '\n', commentsSource);
}
} else {
compilation.assets[commentsFile] = commentsSource;
}
}
} // Updating assets
processedAssets.add(compilation.assets[file] = outputSource); // Handling warnings
if (warnings && warnings.length > 0) {
warnings.forEach(warning => {
const builtWarning = TerserPlugin.buildWarning(warning, file, sourceMap, new _RequestShortener.default(compiler.context), this.options.warningsFilter);
if (builtWarning) {
compilation.warnings.push(builtWarning);
}
});
}
});
taskRunner.exit();
callback();
});
};
const plugin = {
name: this.constructor.name
};
compiler.hooks.compilation.tap(plugin, compilation => {
if (this.options.sourceMap) {
compilation.hooks.buildModule.tap(plugin, buildModuleFn);
}
const {
mainTemplate,
chunkTemplate
} = compilation; // Regenerate `contenthash` for minified assets
for (const template of [mainTemplate, chunkTemplate]) {
template.hooks.hashForChunk.tap(plugin, hash => {
const data = (0, _serializeJavascript.default)({
terser: _package.default.version,
terserOptions: this.options.terserOptions
});
hash.update('TerserPlugin');
hash.update(data);
});
}
compilation.hooks.optimizeChunkAssets.tapAsync(plugin, optimizeFn.bind(this, compilation));
});
}
}
var _default = TerserPlugin;
exports.default = _default;
+186
View File
@@ -0,0 +1,186 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _terser = require("terser");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
const buildTerserOptions = ({
ecma,
warnings,
parse = {},
compress = {},
mangle,
module,
output,
toplevel,
nameCache,
ie8,
/* eslint-disable camelcase */
keep_classnames,
keep_fnames,
/* eslint-enable camelcase */
safari10
} = {}) => ({
ecma,
warnings,
parse: _objectSpread({}, parse),
compress: typeof compress === 'boolean' ? compress : _objectSpread({}, compress),
// eslint-disable-next-line no-nested-ternary
mangle: mangle == null ? true : typeof mangle === 'boolean' ? mangle : _objectSpread({}, mangle),
output: _objectSpread({
shebang: true,
comments: false,
beautify: false,
semicolons: true
}, output),
module,
// Ignoring sourceMap from options
sourceMap: null,
toplevel,
nameCache,
ie8,
keep_classnames,
keep_fnames,
safari10
});
const buildComments = (options, terserOptions, extractedComments) => {
const condition = {};
const commentsOpts = terserOptions.output.comments; // Use /^\**!|@preserve|@license|@cc_on/i RegExp
if (typeof options.extractComments === 'boolean') {
condition.preserve = commentsOpts;
condition.extract = /^\**!|@preserve|@license|@cc_on/i;
} else if (typeof options.extractComments === 'string' || options.extractComments instanceof RegExp) {
// extractComments specifies the extract condition and commentsOpts specifies the preserve condition
condition.preserve = commentsOpts;
condition.extract = options.extractComments;
} else if (typeof options.extractComments === 'function') {
condition.preserve = commentsOpts;
condition.extract = options.extractComments;
} else if (Object.prototype.hasOwnProperty.call(options.extractComments, 'condition')) {
// Extract condition is given in extractComments.condition
condition.preserve = commentsOpts;
condition.extract = options.extractComments.condition;
} else {
// No extract condition is given. Extract comments that match commentsOpts instead of preserving them
condition.preserve = false;
condition.extract = commentsOpts;
} // Ensure that both conditions are functions
['preserve', 'extract'].forEach(key => {
let regexStr;
let regex;
switch (typeof condition[key]) {
case 'boolean':
condition[key] = condition[key] ? () => true : () => false;
break;
case 'function':
break;
case 'string':
if (condition[key] === 'all') {
condition[key] = () => true;
break;
}
if (condition[key] === 'some') {
condition[key] = (astNode, comment) => {
return comment.type === 'comment2' && /^\**!|@preserve|@license|@cc_on/i.test(comment.value);
};
break;
}
regexStr = condition[key];
condition[key] = (astNode, comment) => {
return new RegExp(regexStr).test(comment.value);
};
break;
default:
regex = condition[key];
condition[key] = (astNode, comment) => regex.test(comment.value);
}
}); // Redefine the comments function to extract and preserve
// comments according to the two conditions
return (astNode, comment) => {
if (condition.extract(astNode, comment)) {
const commentText = comment.type === 'comment2' ? `/*${comment.value}*/` : `//${comment.value}`; // Don't include duplicate comments
if (!extractedComments.includes(commentText)) {
extractedComments.push(commentText);
}
}
return condition.preserve(astNode, comment);
};
};
const minify = options => {
const {
file,
input,
inputSourceMap,
extractComments,
minify: minifyFn
} = options;
if (minifyFn) {
return minifyFn({
[file]: input
}, inputSourceMap);
} // Copy terser options
const terserOptions = buildTerserOptions(options.terserOptions); // Let terser generate a SourceMap
if (inputSourceMap) {
terserOptions.sourceMap = true;
}
const extractedComments = [];
if (extractComments) {
terserOptions.output.comments = buildComments(options, terserOptions, extractedComments);
}
const {
error,
map,
code,
warnings
} = (0, _terser.minify)({
[file]: input
}, terserOptions);
return {
error,
map,
code,
warnings,
extractedComments
};
};
var _default = minify;
exports.default = _default;
@@ -0,0 +1,169 @@
{
"additionalProperties": false,
"definitions": {
"file-conditions": {
"anyOf": [
{
"instanceof": "RegExp"
},
{
"type": "string"
}
]
}
},
"properties": {
"test": {
"anyOf": [
{
"$ref": "#/definitions/file-conditions"
},
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/file-conditions"
}
]
},
"type": "array"
}
]
},
"include": {
"anyOf": [
{
"$ref": "#/definitions/file-conditions"
},
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/file-conditions"
}
]
},
"type": "array"
}
]
},
"exclude": {
"anyOf": [
{
"$ref": "#/definitions/file-conditions"
},
{
"items": {
"anyOf": [
{
"$ref": "#/definitions/file-conditions"
}
]
},
"type": "array"
}
]
},
"chunkFilter": {
"instanceof": "Function"
},
"cache": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
}
]
},
"cacheKeys": {
"instanceof": "Function"
},
"parallel": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "integer"
}
]
},
"sourceMap": {
"type": "boolean"
},
"minify": {
"instanceof": "Function"
},
"terserOptions": {
"additionalProperties": true,
"type": "object"
},
"extractComments": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
},
{
"instanceof": "RegExp"
},
{
"instanceof": "Function"
},
{
"additionalProperties": false,
"properties": {
"condition": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
},
{
"instanceof": "RegExp"
},
{
"instanceof": "Function"
}
]
},
"filename": {
"anyOf": [
{
"type": "string"
},
{
"instanceof": "Function"
}
]
},
"banner": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "string"
},
{
"instanceof": "Function"
}
]
}
},
"type": "object"
}
]
},
"warningsFilter": {
"instanceof": "Function"
}
},
"type": "object"
}
+17
View File
@@ -0,0 +1,17 @@
"use strict";
var _minify = _interopRequireDefault(require("./minify"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
module.exports = (options, callback) => {
try {
// 'use strict' => this === undefined (Clean Scope)
// Safer for possible security issues, albeit not critical at all here
// eslint-disable-next-line no-new-func, no-param-reassign
options = new Function('exports', 'require', 'module', '__filename', '__dirname', `'use strict'\nreturn ${options}`)(exports, require, module, __filename, __dirname);
callback(null, (0, _minify.default)(options));
} catch (errors) {
callback(errors);
}
};
+129
View File
@@ -0,0 +1,129 @@
{
"_args": [
[
"terser-webpack-plugin@1.4.4",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "terser-webpack-plugin@1.4.4",
"_id": "terser-webpack-plugin@1.4.4",
"_inBundle": false,
"_integrity": "sha512-U4mACBHIegmfoEe5fdongHESNJWqsGU+W0S/9+BmYGVQDw1+c2Ow05TpMhxjPK1sRb7cuYq1BPl1e5YHJMTCqA==",
"_location": "/webpack/terser-webpack-plugin",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "terser-webpack-plugin@1.4.4",
"name": "terser-webpack-plugin",
"escapedName": "terser-webpack-plugin",
"rawSpec": "1.4.4",
"saveSpec": null,
"fetchSpec": "1.4.4"
},
"_requiredBy": [
"/webpack"
],
"_resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.4.tgz",
"_spec": "1.4.4",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "webpack Contrib Team"
},
"bugs": {
"url": "https://github.com/webpack-contrib/terser-webpack-plugin/issues"
},
"dependencies": {
"cacache": "^12.0.2",
"find-cache-dir": "^2.1.0",
"is-wsl": "^1.1.0",
"schema-utils": "^1.0.0",
"serialize-javascript": "^3.1.0",
"source-map": "^0.6.1",
"terser": "^4.1.2",
"webpack-sources": "^1.4.0",
"worker-farm": "^1.7.0"
},
"description": "Terser plugin for webpack",
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@commitlint/cli": "^8.1.0",
"@commitlint/config-conventional": "^8.1.0",
"@webpack-contrib/defaults": "^5.0.2",
"@webpack-contrib/eslint-config-webpack": "^3.0.0",
"babel-jest": "^24.8.0",
"commitlint-azure-pipelines-cli": "^1.0.2",
"cross-env": "^5.2.0",
"del": "^4.1.1",
"del-cli": "^1.1.0",
"eslint": "^6.1.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-import": "^2.18.2",
"husky": "^3.0.2",
"jest": "^24.8.0",
"jest-junit": "^7.0.0",
"lint-staged": "^9.2.1",
"memory-fs": "^0.4.1",
"npm-run-all": "^4.1.5",
"prettier": "^1.18.2",
"standard-version": "^7.0.0",
"uglify-js": "^3.6.0",
"webpack": "^4.38.0"
},
"engines": {
"node": ">= 6.9.0"
},
"files": [
"dist"
],
"homepage": "https://github.com/webpack-contrib/terser-webpack-plugin",
"keywords": [
"uglify",
"uglify-js",
"uglify-es",
"terser",
"webpack",
"webpack-plugin",
"minification",
"compress",
"compressor",
"min",
"minification",
"minifier",
"minify",
"optimize",
"optimizer"
],
"license": "MIT",
"main": "dist/cjs.js",
"name": "terser-webpack-plugin",
"peerDependencies": {
"webpack": "^4.0.0"
},
"repository": {
"type": "git",
"url": "git+https://github.com/webpack-contrib/terser-webpack-plugin.git"
},
"scripts": {
"build": "cross-env NODE_ENV=production babel src -d dist --ignore \"src/**/*.test.js\" --copy-files",
"clean": "del-cli dist",
"commitlint": "commitlint --from=master",
"defaults": "webpack-defaults",
"lint": "npm-run-all -l -p \"lint:**\"",
"lint:js": "eslint --cache src test",
"lint:prettier": "prettier \"{**/*,*}.{js,json,md,yml,css}\" --list-different",
"prebuild": "npm run clean",
"prepare": "npm run build",
"pretest": "npm run lint",
"release": "standard-version",
"security": "npm audit",
"start": "npm run build -- -w",
"test": "cross-env NODE_ENV=test npm run test:coverage",
"test:coverage": "cross-env NODE_ENV=test jest --collectCoverageFrom=\"src/**/*.js\" --coverage",
"test:only": "cross-env NODE_ENV=test jest",
"test:watch": "cross-env NODE_ENV=test jest --watch"
},
"version": "1.4.4"
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2017, Jon Schlinkert
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+281
View File
@@ -0,0 +1,281 @@
# to-regex-range [![NPM version](https://img.shields.io/npm/v/to-regex-range.svg?style=flat)](https://www.npmjs.com/package/to-regex-range) [![NPM monthly downloads](https://img.shields.io/npm/dm/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![NPM total downloads](https://img.shields.io/npm/dt/to-regex-range.svg?style=flat)](https://npmjs.org/package/to-regex-range) [![Linux Build Status](https://img.shields.io/travis/micromatch/to-regex-range.svg?style=flat&label=Travis)](https://travis-ci.org/micromatch/to-regex-range)
> Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than 2.78 million test assertions.
## Install
Install with [npm](https://www.npmjs.com/):
```sh
$ npm install --save to-regex-range
```
Install with [yarn](https://yarnpkg.com):
```sh
$ yarn add to-regex-range
```
<details>
<summary><strong>What does this do?</strong></summary>
<br>
This libary generates the `source` string to be passed to `new RegExp()` for matching a range of numbers.
**Example**
```js
var toRegexRange = require('to-regex-range');
var regex = new RegExp(toRegexRange('15', '95'));
```
A string is returned so that you can do whatever you need with it before passing it to `new RegExp()` (like adding `^` or `$` boundaries, defining flags, or combining it another string).
<br>
</details>
<details>
<summary><strong>Why use this library?</strong></summary>
<br>
### Convenience
Creating regular expressions for matching numbers gets deceptively complicated pretty fast.
For example, let's say you need a validation regex for matching part of a user-id, postal code, social security number, tax id, etc:
* regex for matching `1` => `/1/` (easy enough)
* regex for matching `1` through `5` => `/[1-5]/` (not bad...)
* regex for matching `1` or `5` => `/(1|5)/` (still easy...)
* regex for matching `1` through `50` => `/([1-9]|[1-4][0-9]|50)/` (uh-oh...)
* regex for matching `1` through `55` => `/([1-9]|[1-4][0-9]|5[0-5])/` (no prob, I can do this...)
* regex for matching `1` through `555` => `/([1-9]|[1-9][0-9]|[1-4][0-9]{2}|5[0-4][0-9]|55[0-5])/` (maybe not...)
* regex for matching `0001` through `5555` => `/(0{3}[1-9]|0{2}[1-9][0-9]|0[1-9][0-9]{2}|[1-4][0-9]{3}|5[0-4][0-9]{2}|55[0-4][0-9]|555[0-5])/` (okay, I get the point!)
The numbers are contrived, but they're also really basic. In the real world you might need to generate a regex on-the-fly for validation.
**Learn more**
If you're interested in learning more about [character classes](http://www.regular-expressions.info/charclass.html) and other regex features, I personally have always found [regular-expressions.info](http://www.regular-expressions.info/charclass.html) to be pretty useful.
### Heavily tested
As of April 27, 2017, this library runs [2,783,483 test assertions](./test/test.js) against generated regex-ranges to provide brute-force verification that results are indeed correct.
Tests run in ~870ms on my MacBook Pro, 2.5 GHz Intel Core i7.
### Highly optimized
Generated regular expressions are highly optimized:
* duplicate sequences and character classes are reduced using quantifiers
* smart enough to use `?` conditionals when number(s) or range(s) can be positive or negative
* uses fragment caching to avoid processing the same exact string more than once
<br>
</details>
## Usage
Add this library to your javascript application with the following line of code
```js
var toRegexRange = require('to-regex-range');
```
The main export is a function that takes two integers: the `min` value and `max` value (formatted as strings or numbers).
```js
var source = toRegexRange('15', '95');
//=> 1[5-9]|[2-8][0-9]|9[0-5]
var re = new RegExp('^' + source + '$');
console.log(re.test('14')); //=> false
console.log(re.test('50')); //=> true
console.log(re.test('94')); //=> true
console.log(re.test('96')); //=> false
```
## Options
### options.capture
**Type**: `boolean`
**Deafault**: `undefined`
Wrap the returned value in parentheses when there is more than one regex condition. Useful when you're dynamically generating ranges.
```js
console.log(toRegexRange('-10', '10'));
//=> -[1-9]|-?10|[0-9]
console.log(toRegexRange('-10', '10', {capture: true}));
//=> (-[1-9]|-?10|[0-9])
```
### options.shorthand
**Type**: `boolean`
**Deafault**: `undefined`
Use the regex shorthand for `[0-9]`:
```js
console.log(toRegexRange('0', '999999'));
//=> [0-9]|[1-9][0-9]{1,5}
console.log(toRegexRange('0', '999999', {shorthand: true}));
//=> \d|[1-9]\d{1,5}
```
### options.relaxZeros
**Type**: `boolean`
**Default**: `true`
This option only applies to **negative zero-padded ranges**. By default, when a negative zero-padded range is defined, the number of leading zeros is relaxed using `-0*`.
```js
console.log(toRegexRange('-001', '100'));
//=> -0*1|0{2}[0-9]|0[1-9][0-9]|100
console.log(toRegexRange('-001', '100', {relaxZeros: false}));
//=> -0{2}1|0{2}[0-9]|0[1-9][0-9]|100
```
<details>
<summary><strong>Why are zeros relaxed for negative zero-padded ranges by default?</strong></summary>
Consider the following.
```js
var regex = toRegexRange('-001', '100');
```
_Note that `-001` and `100` are both three digits long_.
In most zero-padding implementations, only a single leading zero is enough to indicate that zero-padding should be applied. Thus, the leading zeros would be "corrected" on the negative range in the example to `-01`, instead of `-001`, to make total length of each string no greater than the length of the largest number in the range (in other words, `-001` is 4 digits, but `100` is only three digits).
If zeros were not relaxed by default, you might expect the resulting regex of the above pattern to match `-001` - given that it's defined that way in the arguments - _but it wouldn't_. It would, however, match `-01`. This gets even more ambiguous with large ranges, like `-01` to `1000000`.
Thus, we relax zeros by default to provide a more predictable experience for users.
</details>
## Examples
| **Range** | **Result** | **Compile time** |
| --- | --- | --- |
| `toRegexRange('5, 5')` | `5` | _33μs_ |
| `toRegexRange('5, 6')` | `5\|6` | _53μs_ |
| `toRegexRange('29, 51')` | `29\|[34][0-9]\|5[01]` | _699μs_ |
| `toRegexRange('31, 877')` | `3[1-9]\|[4-9][0-9]\|[1-7][0-9]{2}\|8[0-6][0-9]\|87[0-7]` | _711μs_ |
| `toRegexRange('111, 555')` | `11[1-9]\|1[2-9][0-9]\|[2-4][0-9]{2}\|5[0-4][0-9]\|55[0-5]` | _62μs_ |
| `toRegexRange('-10, 10')` | `-[1-9]\|-?10\|[0-9]` | _74μs_ |
| `toRegexRange('-100, -10')` | `-1[0-9]\|-[2-9][0-9]\|-100` | _49μs_ |
| `toRegexRange('-100, 100')` | `-[1-9]\|-?[1-9][0-9]\|-?100\|[0-9]` | _45μs_ |
| `toRegexRange('001, 100')` | `0{2}[1-9]\|0[1-9][0-9]\|100` | _158μs_ |
| `toRegexRange('0010, 1000')` | `0{2}1[0-9]\|0{2}[2-9][0-9]\|0[1-9][0-9]{2}\|1000` | _61μs_ |
| `toRegexRange('1, 2')` | `1\|2` | _10μs_ |
| `toRegexRange('1, 5')` | `[1-5]` | _24μs_ |
| `toRegexRange('1, 10')` | `[1-9]\|10` | _23μs_ |
| `toRegexRange('1, 100')` | `[1-9]\|[1-9][0-9]\|100` | _30μs_ |
| `toRegexRange('1, 1000')` | `[1-9]\|[1-9][0-9]{1,2}\|1000` | _52μs_ |
| `toRegexRange('1, 10000')` | `[1-9]\|[1-9][0-9]{1,3}\|10000` | _47μs_ |
| `toRegexRange('1, 100000')` | `[1-9]\|[1-9][0-9]{1,4}\|100000` | _44μs_ |
| `toRegexRange('1, 1000000')` | `[1-9]\|[1-9][0-9]{1,5}\|1000000` | _49μs_ |
| `toRegexRange('1, 10000000')` | `[1-9]\|[1-9][0-9]{1,6}\|10000000` | _63μs_ |
## Heads up!
**Order of arguments**
When the `min` is larger than the `max`, values will be flipped to create a valid range:
```js
toRegexRange('51', '29');
```
Is effectively flipped to:
```js
toRegexRange('29', '51');
//=> 29|[3-4][0-9]|5[0-1]
```
**Steps / increments**
This library does not support steps (increments). A pr to add support would be welcome.
## History
### v2.0.0 - 2017-04-21
**New features**
Adds support for zero-padding!
### v1.0.0
**Optimizations**
Repeating ranges are now grouped using quantifiers. rocessing time is roughly the same, but the generated regex is much smaller, which should result in faster matching.
## Attribution
Inspired by the python library [range-regex](https://github.com/dimka665/range-regex).
## About
### Related projects
* [expand-range](https://www.npmjs.com/package/expand-range): Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See… [more](https://github.com/jonschlinkert/expand-range) | [homepage](https://github.com/jonschlinkert/expand-range "Fast, bash-like range expansion. Expand a range of numbers or letters, uppercase or lowercase. See the benchmarks. Used by micromatch.")
* [fill-range](https://www.npmjs.com/package/fill-range): Fill in a range of numbers or letters, optionally passing an increment or `step` to… [more](https://github.com/jonschlinkert/fill-range) | [homepage](https://github.com/jonschlinkert/fill-range "Fill in a range of numbers or letters, optionally passing an increment or `step` to use, or create a regex-compatible range with `options.toRegex`")
* [micromatch](https://www.npmjs.com/package/micromatch): Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch. | [homepage](https://github.com/jonschlinkert/micromatch "Glob matching for javascript/node.js. A drop-in replacement and faster alternative to minimatch and multimatch.")
* [repeat-element](https://www.npmjs.com/package/repeat-element): Create an array by repeating the given value n times. | [homepage](https://github.com/jonschlinkert/repeat-element "Create an array by repeating the given value n times.")
* [repeat-string](https://www.npmjs.com/package/repeat-string): Repeat the given string n times. Fastest implementation for repeating a string. | [homepage](https://github.com/jonschlinkert/repeat-string "Repeat the given string n times. Fastest implementation for repeating a string.")
### Contributing
Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](../../issues/new).
### Building docs
_(This project's readme.md is generated by [verb](https://github.com/verbose/verb-generate-readme), please don't edit the readme directly. Any changes to the readme must be made in the [.verb.md](.verb.md) readme template.)_
To generate the readme, run the following command:
```sh
$ npm install -g verbose/verb#dev verb-generate-readme && verb
```
### Running tests
Running and reviewing unit tests is a great way to get familiarized with a library and its API. You can install dependencies and run tests with the following command:
```sh
$ npm install && npm test
```
### Author
**Jon Schlinkert**
* [github/jonschlinkert](https://github.com/jonschlinkert)
* [twitter/jonschlinkert](https://twitter.com/jonschlinkert)
### License
Copyright © 2017, [Jon Schlinkert](https://github.com/jonschlinkert).
Released under the [MIT License](LICENSE).
***
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.6.0, on April 27, 2017._
+294
View File
@@ -0,0 +1,294 @@
/*!
* to-regex-range <https://github.com/jonschlinkert/to-regex-range>
*
* Copyright (c) 2015, 2017, Jon Schlinkert.
* Released under the MIT License.
*/
'use strict';
var repeat = require('repeat-string');
var isNumber = require('is-number');
var cache = {};
function toRegexRange(min, max, options) {
if (isNumber(min) === false) {
throw new RangeError('toRegexRange: first argument is invalid.');
}
if (typeof max === 'undefined' || min === max) {
return String(min);
}
if (isNumber(max) === false) {
throw new RangeError('toRegexRange: second argument is invalid.');
}
options = options || {};
var relax = String(options.relaxZeros);
var shorthand = String(options.shorthand);
var capture = String(options.capture);
var key = min + ':' + max + '=' + relax + shorthand + capture;
if (cache.hasOwnProperty(key)) {
return cache[key].result;
}
var a = Math.min(min, max);
var b = Math.max(min, max);
if (Math.abs(a - b) === 1) {
var result = min + '|' + max;
if (options.capture) {
return '(' + result + ')';
}
return result;
}
var isPadded = padding(min) || padding(max);
var positives = [];
var negatives = [];
var tok = {min: min, max: max, a: a, b: b};
if (isPadded) {
tok.isPadded = isPadded;
tok.maxLen = String(tok.max).length;
}
if (a < 0) {
var newMin = b < 0 ? Math.abs(b) : 1;
var newMax = Math.abs(a);
negatives = splitToPatterns(newMin, newMax, tok, options);
a = tok.a = 0;
}
if (b >= 0) {
positives = splitToPatterns(a, b, tok, options);
}
tok.negatives = negatives;
tok.positives = positives;
tok.result = siftPatterns(negatives, positives, options);
if (options.capture && (positives.length + negatives.length) > 1) {
tok.result = '(' + tok.result + ')';
}
cache[key] = tok;
return tok.result;
}
function siftPatterns(neg, pos, options) {
var onlyNegative = filterPatterns(neg, pos, '-', false, options) || [];
var onlyPositive = filterPatterns(pos, neg, '', false, options) || [];
var intersected = filterPatterns(neg, pos, '-?', true, options) || [];
var subpatterns = onlyNegative.concat(intersected).concat(onlyPositive);
return subpatterns.join('|');
}
function splitToRanges(min, max) {
min = Number(min);
max = Number(max);
var nines = 1;
var stops = [max];
var stop = +countNines(min, nines);
while (min <= stop && stop <= max) {
stops = push(stops, stop);
nines += 1;
stop = +countNines(min, nines);
}
var zeros = 1;
stop = countZeros(max + 1, zeros) - 1;
while (min < stop && stop <= max) {
stops = push(stops, stop);
zeros += 1;
stop = countZeros(max + 1, zeros) - 1;
}
stops.sort(compare);
return stops;
}
/**
* Convert a range to a regex pattern
* @param {Number} `start`
* @param {Number} `stop`
* @return {String}
*/
function rangeToPattern(start, stop, options) {
if (start === stop) {
return {pattern: String(start), digits: []};
}
var zipped = zip(String(start), String(stop));
var len = zipped.length, i = -1;
var pattern = '';
var digits = 0;
while (++i < len) {
var numbers = zipped[i];
var startDigit = numbers[0];
var stopDigit = numbers[1];
if (startDigit === stopDigit) {
pattern += startDigit;
} else if (startDigit !== '0' || stopDigit !== '9') {
pattern += toCharacterClass(startDigit, stopDigit);
} else {
digits += 1;
}
}
if (digits) {
pattern += options.shorthand ? '\\d' : '[0-9]';
}
return { pattern: pattern, digits: [digits] };
}
function splitToPatterns(min, max, tok, options) {
var ranges = splitToRanges(min, max);
var len = ranges.length;
var idx = -1;
var tokens = [];
var start = min;
var prev;
while (++idx < len) {
var range = ranges[idx];
var obj = rangeToPattern(start, range, options);
var zeros = '';
if (!tok.isPadded && prev && prev.pattern === obj.pattern) {
if (prev.digits.length > 1) {
prev.digits.pop();
}
prev.digits.push(obj.digits[0]);
prev.string = prev.pattern + toQuantifier(prev.digits);
start = range + 1;
continue;
}
if (tok.isPadded) {
zeros = padZeros(range, tok);
}
obj.string = zeros + obj.pattern + toQuantifier(obj.digits);
tokens.push(obj);
start = range + 1;
prev = obj;
}
return tokens;
}
function filterPatterns(arr, comparison, prefix, intersection, options) {
var res = [];
for (var i = 0; i < arr.length; i++) {
var tok = arr[i];
var ele = tok.string;
if (options.relaxZeros !== false) {
if (prefix === '-' && ele.charAt(0) === '0') {
if (ele.charAt(1) === '{') {
ele = '0*' + ele.replace(/^0\{\d+\}/, '');
} else {
ele = '0*' + ele.slice(1);
}
}
}
if (!intersection && !contains(comparison, 'string', ele)) {
res.push(prefix + ele);
}
if (intersection && contains(comparison, 'string', ele)) {
res.push(prefix + ele);
}
}
return res;
}
/**
* Zip strings (`for in` can be used on string characters)
*/
function zip(a, b) {
var arr = [];
for (var ch in a) arr.push([a[ch], b[ch]]);
return arr;
}
function compare(a, b) {
return a > b ? 1 : b > a ? -1 : 0;
}
function push(arr, ele) {
if (arr.indexOf(ele) === -1) arr.push(ele);
return arr;
}
function contains(arr, key, val) {
for (var i = 0; i < arr.length; i++) {
if (arr[i][key] === val) {
return true;
}
}
return false;
}
function countNines(min, len) {
return String(min).slice(0, -len) + repeat('9', len);
}
function countZeros(integer, zeros) {
return integer - (integer % Math.pow(10, zeros));
}
function toQuantifier(digits) {
var start = digits[0];
var stop = digits[1] ? (',' + digits[1]) : '';
if (!stop && (!start || start === 1)) {
return '';
}
return '{' + start + stop + '}';
}
function toCharacterClass(a, b) {
return '[' + a + ((b - a === 1) ? '' : '-') + b + ']';
}
function padding(str) {
return /^-?(0+)\d/.exec(str);
}
function padZeros(val, tok) {
if (tok.isPadded) {
var diff = Math.abs(tok.maxLen - String(val).length);
switch (diff) {
case 0:
return '';
case 1:
return '0';
default: {
return '0{' + diff + '}';
}
}
}
return val;
}
/**
* Expose `toRegexRange`
*/
module.exports = toRegexRange;
+120
View File
@@ -0,0 +1,120 @@
{
"_args": [
[
"to-regex-range@2.1.1",
"/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series"
]
],
"_from": "to-regex-range@2.1.1",
"_id": "to-regex-range@2.1.1",
"_inBundle": false,
"_integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"_location": "/webpack/to-regex-range",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "to-regex-range@2.1.1",
"name": "to-regex-range",
"escapedName": "to-regex-range",
"rawSpec": "2.1.1",
"saveSpec": null,
"fetchSpec": "2.1.1"
},
"_requiredBy": [
"/webpack/fill-range"
],
"_resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
"_spec": "2.1.1",
"_where": "/mnt/Foxconn/Digitalent/Deverloper/liff-push_2series",
"author": {
"name": "Jon Schlinkert",
"url": "https://github.com/jonschlinkert"
},
"bugs": {
"url": "https://github.com/micromatch/to-regex-range/issues"
},
"dependencies": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
},
"description": "Pass two numbers, get a regex-compatible source string for matching ranges. Validated against more than 2.78 million test assertions.",
"devDependencies": {
"fill-range": "^3.1.1",
"gulp-format-md": "^0.1.12",
"mocha": "^3.2.0",
"text-table": "^0.2.0",
"time-diff": "^0.3.1"
},
"engines": {
"node": ">=0.10.0"
},
"files": [
"index.js"
],
"homepage": "https://github.com/micromatch/to-regex-range",
"keywords": [
"alpha",
"alphabetical",
"bash",
"brace",
"date",
"expand",
"expansion",
"glob",
"match",
"matches",
"matching",
"number",
"numerical",
"range",
"ranges",
"regex",
"sequence",
"sh",
"to",
"year"
],
"license": "MIT",
"main": "index.js",
"name": "to-regex-range",
"repository": {
"type": "git",
"url": "git+https://github.com/micromatch/to-regex-range.git"
},
"scripts": {
"test": "mocha"
},
"verb": {
"related": {
"list": [
"expand-range",
"fill-range",
"micromatch",
"repeat-element",
"repeat-string"
]
},
"toc": false,
"layout": "default",
"tasks": [
"readme"
],
"plugins": [
"gulp-format-md"
],
"lint": {
"reflinks": true
},
"helpers": [
"./examples.js"
],
"reflinks": [
"0-5",
"0-9",
"1-5",
"1-9"
]
},
"version": "2.1.1"
}