├── test ├── .eslintrc.yml └── range-parser.js ├── .eslintrc.yml ├── .eslintignore ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── codeql.yml │ ├── scorecard.yml │ └── ci.yml ├── HISTORY.md ├── LICENSE ├── package.json ├── README.md └── index.js /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: standard 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | coverage 3 | node_modules 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | node_modules 3 | coverage 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | 8 | - package-ecosystem: npm 9 | directory: / 10 | schedule: 11 | interval: monthly 12 | time: "23:00" 13 | timezone: Europe/London 14 | open-pull-requests-limit: 10 15 | ignore: 16 | - dependency-name: "*" 17 | update-types: ["version-update:semver-major"] 18 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 1.2.1 / 2019-05-10 2 | ================== 3 | 4 | * Improve error when `str` is not a string 5 | 6 | 1.2.0 / 2016-06-01 7 | ================== 8 | 9 | * Add `combine` option to combine overlapping ranges 10 | 11 | 1.1.0 / 2016-05-13 12 | ================== 13 | 14 | * Fix incorrectly returning -1 when there is at least one valid range 15 | * perf: remove internal function 16 | 17 | 1.0.3 / 2015-10-29 18 | ================== 19 | 20 | * perf: enable strict mode 21 | 22 | 1.0.2 / 2014-09-08 23 | ================== 24 | 25 | * Support Node.js 0.6 26 | 27 | 1.0.1 / 2014-09-07 28 | ================== 29 | 30 | * Move repository to jshttp 31 | 32 | 1.0.0 / 2013-12-11 33 | ================== 34 | 35 | * Add repository to package.json 36 | * Add MIT license 37 | 38 | 0.0.4 / 2012-06-17 39 | ================== 40 | 41 | * Change ret -1 for unsatisfiable and -2 when invalid 42 | 43 | 0.0.3 / 2012-06-17 44 | ================== 45 | 46 | * Fix last-byte-pos default to len - 1 47 | 48 | 0.0.2 / 2012-06-14 49 | ================== 50 | 51 | * Add `.type` 52 | 53 | 0.0.1 / 2012-06-11 54 | ================== 55 | 56 | * Initial release 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012-2014 TJ Holowaychuk 4 | Copyright (c) 2015-2016 Douglas Christopher Wilson (http://tjholowaychuk.com)", 4 | "description": "Range header field string parser", 5 | "version": "1.2.1", 6 | "contributors": [ 7 | "Douglas Christopher Wilson ", 8 | "James Wyatt Cready ", 9 | "Jonathan Ong (http://jongleberry.com)" 10 | ], 11 | "license": "MIT", 12 | "keywords": [ 13 | "range", 14 | "parser", 15 | "http" 16 | ], 17 | "repository": "jshttp/range-parser", 18 | "funding": { 19 | "type": "opencollective", 20 | "url": "https://opencollective.com/express" 21 | }, 22 | "devDependencies": { 23 | "deep-equal": "1.0.1", 24 | "eslint": "6.0.1", 25 | "eslint-config-standard": "13.0.1", 26 | "eslint-plugin-markdown": "1.0.0", 27 | "eslint-plugin-import": "2.18.0", 28 | "eslint-plugin-node": "9.1.0", 29 | "eslint-plugin-promise": "4.2.1", 30 | "eslint-plugin-standard": "4.0.0", 31 | "mocha": "6.1.4", 32 | "nyc": "14.1.1" 33 | }, 34 | "files": [ 35 | "HISTORY.md", 36 | "LICENSE", 37 | "index.js" 38 | ], 39 | "engines": { 40 | "node": ">= 0.6" 41 | }, 42 | "scripts": { 43 | "lint": "eslint --plugin markdown --ext js,md .", 44 | "test": "mocha --reporter spec", 45 | "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", 46 | "test-cov": "nyc --reporter=html --reporter=text npm test" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # range-parser 2 | 3 | [![NPM Version][npm-version-image]][npm-url] 4 | [![NPM Downloads][npm-downloads-image]][npm-url] 5 | [![Node.js Version][node-image]][node-url] 6 | [![Build Status][ci-image]][ci-url] 7 | [![Test Coverage][coveralls-image]][coveralls-url] 8 | 9 | Range header field parser. 10 | 11 | ## Installation 12 | 13 | This is a [Node.js](https://nodejs.org/en/) module available through the 14 | [npm registry](https://www.npmjs.com/). Installation is done using the 15 | [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): 16 | 17 | ```sh 18 | $ npm install range-parser 19 | ``` 20 | 21 | ## API 22 | 23 | 24 | 25 | ```js 26 | var parseRange = require('range-parser') 27 | ``` 28 | 29 | ### parseRange(size, header, options) 30 | 31 | Parse the given `header` string where `size` is the size of the selected 32 | representation that is to be partitioned into subranges. An array of subranges 33 | will be returned or negative numbers indicating an error parsing. 34 | 35 | * `-2` signals a malformed header string 36 | * `-1` signals an unsatisfiable range 37 | 38 | 39 | 40 | ```js 41 | // parse header from request 42 | var subranges = parseRange(size, req.headers.range) 43 | 44 | // the type of the subranges 45 | if (subranges.type === 'bytes') { 46 | // the ranges 47 | subranges.forEach(function (r) { 48 | // do something with r.start and r.end 49 | }) 50 | } 51 | ``` 52 | 53 | #### Options 54 | 55 | These properties are accepted in the options object. 56 | 57 | ##### combine 58 | 59 | Specifies if overlapping & adjacent subranges should be combined, defaults to 60 | `false`. When `true`, ranges will be combined and returned as if they were 61 | specified that way in the header. 62 | 63 | 64 | 65 | ```js 66 | parseRange(100, 'bytes=50-55,0-10,5-10,56-60', { combine: true }) 67 | // => [ 68 | // { start: 0, end: 10 }, 69 | // { start: 50, end: 60 } 70 | // ] 71 | ``` 72 | 73 | ## License 74 | 75 | [MIT](LICENSE) 76 | 77 | [ci-image]: https://badgen.net/github/checks/jshttp/range-parser/master?label=ci 78 | [ci-url]: https://github.com/jshttp/range-parser/actions/workflows/ci.yml 79 | [coveralls-image]: https://badgen.net/coveralls/c/github/jshttp/range-parser/master 80 | [coveralls-url]: https://coveralls.io/r/jshttp/range-parser?branch=master 81 | [node-image]: https://badgen.net/npm/node/range-parser 82 | [node-url]: https://nodejs.org/en/download 83 | [npm-downloads-image]: https://badgen.net/npm/dm/range-parser 84 | [npm-url]: https://npmjs.org/package/range-parser 85 | [npm-version-image]: https://badgen.net/npm/v/range-parser 86 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: ["master"] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: ["master"] 20 | schedule: 21 | - cron: "0 0 * * 1" 22 | 23 | permissions: 24 | contents: read 25 | 26 | jobs: 27 | analyze: 28 | name: Analyze 29 | runs-on: ubuntu-latest 30 | permissions: 31 | actions: read 32 | contents: read 33 | security-events: write 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | language: ["javascript"] 39 | # CodeQL supports [ $supported-codeql-languages ] 40 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 41 | 42 | steps: 43 | - name: Checkout repository 44 | uses: actions/checkout@v6.0.0 45 | 46 | # Initializes the CodeQL tools for scanning. 47 | - name: Initialize CodeQL 48 | uses: github/codeql-action/init@v4.31.6 49 | with: 50 | languages: ${{ matrix.language }} 51 | # If you wish to specify custom queries, you can do so here or in a config file. 52 | # By default, queries listed here will override any specified in a config file. 53 | # Prefix the list here with "+" to use these queries and those in the config file. 54 | 55 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 56 | # If this step fails, then you should remove it and run the build manually (see below) 57 | - name: Autobuild 58 | uses: github/codeql-action/autobuild@v4.31.6 59 | 60 | # ℹ️ Command-line programs to run using the OS shell. 61 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 62 | 63 | # If the Autobuild fails above, remove it and uncomment the following three lines. 64 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 65 | 66 | # - run: | 67 | # echo "Run, Build Application using script" 68 | # ./location_of_script_within_repo/buildscript.sh 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v4.31.6 72 | with: 73 | category: "/language:${{matrix.language}}" 74 | -------------------------------------------------------------------------------- /.github/workflows/scorecard.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. They are provided 2 | # by a third-party and are governed by separate terms of service, privacy 3 | # policy, and support documentation. 4 | 5 | name: Scorecard supply-chain security 6 | on: 7 | # For Branch-Protection check. Only the default branch is supported. See 8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection 9 | branch_protection_rule: 10 | # To guarantee Maintained check is occasionally updated. See 11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained 12 | schedule: 13 | - cron: '16 21 * * 1' 14 | push: 15 | branches: [ "master" ] 16 | 17 | # Declare default permissions as read only. 18 | permissions: read-all 19 | 20 | jobs: 21 | analysis: 22 | name: Scorecard analysis 23 | runs-on: ubuntu-latest 24 | permissions: 25 | # Needed to upload the results to code-scanning dashboard. 26 | security-events: write 27 | # Needed to publish results and get a badge (see publish_results below). 28 | id-token: write 29 | # Uncomment the permissions below if installing in a private repository. 30 | # contents: read 31 | # actions: read 32 | 33 | steps: 34 | - name: "Checkout code" 35 | uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 36 | with: 37 | persist-credentials: false 38 | 39 | - name: "Run analysis" 40 | uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # v2.0.6 41 | with: 42 | results_file: results.sarif 43 | results_format: sarif 44 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: 45 | # - you want to enable the Branch-Protection check on a *public* repository, or 46 | # - you are installing Scorecard on a *private* repository 47 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. 48 | # repo_token: ${{ secrets.SCORECARD_TOKEN }} 49 | 50 | # Public repositories: 51 | # - Publish results to OpenSSF REST API for easy access by consumers 52 | # - Allows the repository to include the Scorecard badge. 53 | # - See https://github.com/ossf/scorecard-action#publishing-results. 54 | # For private repositories: 55 | # - `publish_results` will always be set to `false`, regardless 56 | # of the value entered here. 57 | publish_results: true 58 | 59 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF 60 | # format to the repository Actions tab. 61 | - name: "Upload artifact" 62 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 63 | with: 64 | name: SARIF file 65 | path: results.sarif 66 | retention-days: 5 67 | 68 | # Upload the results to GitHub's code scanning dashboard. 69 | - name: "Upload to code-scanning" 70 | uses: github/codeql-action/upload-sarif@f0ac9bfbe331b3cc7be1482df79cca2865cb79b6 # v3.31.2 71 | with: 72 | sarif_file: results.sarif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * range-parser 3 | * Copyright(c) 2012-2014 TJ Holowaychuk 4 | * Copyright(c) 2015-2016 Douglas Christopher Wilson 5 | * MIT Licensed 6 | */ 7 | 8 | 'use strict' 9 | 10 | /** 11 | * Module exports. 12 | * @public 13 | */ 14 | 15 | module.exports = rangeParser 16 | 17 | /** 18 | * Parse "Range" header `str` relative to the given file `size`. 19 | * 20 | * @param {Number} size 21 | * @param {String} str 22 | * @param {Object} [options] 23 | * @return {Array} 24 | * @public 25 | */ 26 | 27 | function rangeParser (size, str, options) { 28 | if (typeof str !== 'string') { 29 | throw new TypeError('argument str must be a string') 30 | } 31 | 32 | var index = str.indexOf('=') 33 | 34 | if (index === -1) { 35 | return -2 36 | } 37 | 38 | // split the range string 39 | var arr = str.slice(index + 1).split(',') 40 | var ranges = [] 41 | 42 | // add ranges type 43 | ranges.type = str.slice(0, index) 44 | 45 | // parse all ranges 46 | for (var i = 0; i < arr.length; i++) { 47 | var range = arr[i].split('-') 48 | var start = parseInt(range[0], 10) 49 | var end = parseInt(range[1], 10) 50 | 51 | // -nnn 52 | if (isNaN(start)) { 53 | start = size - end 54 | end = size - 1 55 | // nnn- 56 | } else if (isNaN(end)) { 57 | end = size - 1 58 | } 59 | 60 | // limit last-byte-pos to current length 61 | if (end > size - 1) { 62 | end = size - 1 63 | } 64 | 65 | // invalid or unsatisifiable 66 | if (isNaN(start) || isNaN(end) || start > end || start < 0) { 67 | continue 68 | } 69 | 70 | // add range 71 | ranges.push({ 72 | start: start, 73 | end: end 74 | }) 75 | } 76 | 77 | if (ranges.length < 1) { 78 | // unsatisifiable 79 | return -1 80 | } 81 | 82 | return options && options.combine 83 | ? combineRanges(ranges) 84 | : ranges 85 | } 86 | 87 | /** 88 | * Combine overlapping & adjacent ranges. 89 | * @private 90 | */ 91 | 92 | function combineRanges (ranges) { 93 | var ordered = ranges.map(mapWithIndex).sort(sortByRangeStart) 94 | 95 | for (var j = 0, i = 1; i < ordered.length; i++) { 96 | var range = ordered[i] 97 | var current = ordered[j] 98 | 99 | if (range.start > current.end + 1) { 100 | // next range 101 | ordered[++j] = range 102 | } else if (range.end > current.end) { 103 | // extend range 104 | current.end = range.end 105 | current.index = Math.min(current.index, range.index) 106 | } 107 | } 108 | 109 | // trim ordered array 110 | ordered.length = j + 1 111 | 112 | // generate combined range 113 | var combined = ordered.sort(sortByRangeIndex).map(mapWithoutIndex) 114 | 115 | // copy ranges type 116 | combined.type = ranges.type 117 | 118 | return combined 119 | } 120 | 121 | /** 122 | * Map function to add index value to ranges. 123 | * @private 124 | */ 125 | 126 | function mapWithIndex (range, index) { 127 | return { 128 | start: range.start, 129 | end: range.end, 130 | index: index 131 | } 132 | } 133 | 134 | /** 135 | * Map function to remove index value from ranges. 136 | * @private 137 | */ 138 | 139 | function mapWithoutIndex (range) { 140 | return { 141 | start: range.start, 142 | end: range.end 143 | } 144 | } 145 | 146 | /** 147 | * Sort function to sort ranges by index. 148 | * @private 149 | */ 150 | 151 | function sortByRangeIndex (a, b) { 152 | return a.index - b.index 153 | } 154 | 155 | /** 156 | * Sort function to sort ranges by start position. 157 | * @private 158 | */ 159 | 160 | function sortByRangeStart (a, b) { 161 | return a.start - b.start 162 | } 163 | -------------------------------------------------------------------------------- /test/range-parser.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert') 3 | var deepEqual = require('deep-equal') 4 | var parse = require('..') 5 | 6 | describe('parseRange(len, str)', function () { 7 | it('should reject non-string str', function () { 8 | assert.throws(parse.bind(null, 200, {}), 9 | /TypeError: argument str must be a string/) 10 | }) 11 | 12 | it('should return -2 for invalid str', function () { 13 | assert.strictEqual(parse(200, 'malformed'), -2) 14 | }) 15 | 16 | it('should return -1 if all specified ranges are invalid', function () { 17 | assert.strictEqual(parse(200, 'bytes=500-20'), -1) 18 | assert.strictEqual(parse(200, 'bytes=500-999'), -1) 19 | assert.strictEqual(parse(200, 'bytes=500-999,1000-1499'), -1) 20 | }) 21 | 22 | it('should parse str', function () { 23 | var range = parse(1000, 'bytes=0-499') 24 | assert.strictEqual(range.type, 'bytes') 25 | assert.strictEqual(range.length, 1) 26 | deepEqual(range[0], { start: 0, end: 499 }) 27 | }) 28 | 29 | it('should cap end at size', function () { 30 | var range = parse(200, 'bytes=0-499') 31 | assert.strictEqual(range.type, 'bytes') 32 | assert.strictEqual(range.length, 1) 33 | deepEqual(range[0], { start: 0, end: 199 }) 34 | }) 35 | 36 | it('should parse str', function () { 37 | var range = parse(1000, 'bytes=40-80') 38 | assert.strictEqual(range.type, 'bytes') 39 | assert.strictEqual(range.length, 1) 40 | deepEqual(range[0], { start: 40, end: 80 }) 41 | }) 42 | 43 | it('should parse str asking for last n bytes', function () { 44 | var range = parse(1000, 'bytes=-400') 45 | assert.strictEqual(range.type, 'bytes') 46 | assert.strictEqual(range.length, 1) 47 | deepEqual(range[0], { start: 600, end: 999 }) 48 | }) 49 | 50 | it('should parse str with only start', function () { 51 | var range = parse(1000, 'bytes=400-') 52 | assert.strictEqual(range.type, 'bytes') 53 | assert.strictEqual(range.length, 1) 54 | deepEqual(range[0], { start: 400, end: 999 }) 55 | }) 56 | 57 | it('should parse "bytes=0-"', function () { 58 | var range = parse(1000, 'bytes=0-') 59 | assert.strictEqual(range.type, 'bytes') 60 | assert.strictEqual(range.length, 1) 61 | deepEqual(range[0], { start: 0, end: 999 }) 62 | }) 63 | 64 | it('should parse str with no bytes', function () { 65 | var range = parse(1000, 'bytes=0-0') 66 | assert.strictEqual(range.type, 'bytes') 67 | assert.strictEqual(range.length, 1) 68 | deepEqual(range[0], { start: 0, end: 0 }) 69 | }) 70 | 71 | it('should parse str asking for last byte', function () { 72 | var range = parse(1000, 'bytes=-1') 73 | assert.strictEqual(range.type, 'bytes') 74 | assert.strictEqual(range.length, 1) 75 | deepEqual(range[0], { start: 999, end: 999 }) 76 | }) 77 | 78 | it('should parse str with multiple ranges', function () { 79 | var range = parse(1000, 'bytes=40-80,81-90,-1') 80 | assert.strictEqual(range.type, 'bytes') 81 | assert.strictEqual(range.length, 3) 82 | deepEqual(range[0], { start: 40, end: 80 }) 83 | deepEqual(range[1], { start: 81, end: 90 }) 84 | deepEqual(range[2], { start: 999, end: 999 }) 85 | }) 86 | 87 | it('should parse str with some invalid ranges', function () { 88 | var range = parse(200, 'bytes=0-499,1000-,500-999') 89 | assert.strictEqual(range.type, 'bytes') 90 | assert.strictEqual(range.length, 1) 91 | deepEqual(range[0], { start: 0, end: 199 }) 92 | }) 93 | 94 | it('should parse non-byte range', function () { 95 | var range = parse(1000, 'items=0-5') 96 | assert.strictEqual(range.type, 'items') 97 | assert.strictEqual(range.length, 1) 98 | deepEqual(range[0], { start: 0, end: 5 }) 99 | }) 100 | 101 | describe('when combine: true', function () { 102 | it('should combine overlapping ranges', function () { 103 | var range = parse(150, 'bytes=0-4,90-99,5-75,100-199,101-102', { combine: true }) 104 | assert.strictEqual(range.type, 'bytes') 105 | assert.strictEqual(range.length, 2) 106 | deepEqual(range[0], { start: 0, end: 75 }) 107 | deepEqual(range[1], { start: 90, end: 149 }) 108 | }) 109 | 110 | it('should retain original order', function () { 111 | var range = parse(150, 'bytes=-1,20-100,0-1,101-120', { combine: true }) 112 | assert.strictEqual(range.type, 'bytes') 113 | assert.strictEqual(range.length, 3) 114 | deepEqual(range[0], { start: 149, end: 149 }) 115 | deepEqual(range[1], { start: 20, end: 120 }) 116 | deepEqual(range[2], { start: 0, end: 1 }) 117 | }) 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | test: 12 | permissions: 13 | checks: write # for coverallsapp/github-action to create new checks 14 | contents: read # for actions/checkout to fetch code 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | name: 19 | - Node.js 0.8 20 | - Node.js 0.10 21 | - Node.js 0.12 22 | - io.js 1.x 23 | - io.js 2.x 24 | - io.js 3.x 25 | - Node.js 4.x 26 | - Node.js 5.x 27 | - Node.js 6.x 28 | - Node.js 7.x 29 | - Node.js 8.x 30 | - Node.js 9.x 31 | - Node.js 10.x 32 | - Node.js 11.x 33 | - Node.js 12.x 34 | - Node.js 13.x 35 | - Node.js 14.x 36 | - Node.js 15.x 37 | - Node.js 16.x 38 | - Node.js 17.x 39 | - Node.js 18.x 40 | - Node.js 19.x 41 | - Node.js 20.x 42 | - Node.js 21.x 43 | - Node.js 22.x 44 | 45 | include: 46 | 47 | - name: Node.js 0.8 48 | node-version: "0.8" 49 | npm-i: mocha@2.5.3 50 | npm-rm: nyc 51 | 52 | - name: Node.js 0.10 53 | node-version: "0.10" 54 | npm-i: mocha@3.5.3 nyc@10.3.2 55 | 56 | - name: Node.js 0.12 57 | node-version: "0.12" 58 | npm-i: mocha@3.5.3 nyc@10.3.2 59 | 60 | - name: io.js 1.x 61 | node-version: "1.8" 62 | npm-i: mocha@3.5.3 nyc@10.3.2 63 | 64 | - name: io.js 2.x 65 | node-version: "2.5" 66 | npm-i: mocha@3.5.3 nyc@10.3.2 67 | 68 | - name: io.js 3.x 69 | node-version: "3.3" 70 | npm-i: mocha@3.5.3 nyc@10.3.2 71 | 72 | - name: Node.js 4.x 73 | node-version: "4.9" 74 | npm-i: mocha@5.2.0 nyc@11.9.0 75 | 76 | - name: Node.js 5.x 77 | node-version: "5.12" 78 | npm-i: mocha@5.2.0 nyc@11.9.0 79 | 80 | - name: Node.js 6.x 81 | node-version: "6.17" 82 | npm-i: mocha@6.2.2 nyc@14.1.1 83 | 84 | - name: Node.js 7.x 85 | node-version: "7.10" 86 | npm-i: mocha@6.2.2 nyc@14.1.1 87 | 88 | - name: Node.js 8.x 89 | node-version: "8.17" 90 | npm-i: mocha@7.1.2 nyc@14.1.1 91 | 92 | - name: Node.js 9.x 93 | node-version: "9.11" 94 | npm-i: mocha@7.1.2 nyc@14.1.1 95 | 96 | - name: Node.js 10.x 97 | node-version: "10.24" 98 | npm-i: mocha@8.4.0 99 | 100 | - name: Node.js 11.x 101 | node-version: "11.15" 102 | npm-i: mocha@8.4.0 103 | 104 | - name: Node.js 12.x 105 | node-version: "12.22" 106 | npm-i: mocha@9.2.2 107 | 108 | - name: Node.js 13.x 109 | node-version: "13.14" 110 | npm-i: mocha@9.2.2 111 | 112 | - name: Node.js 14.x 113 | node-version: "14.21" 114 | 115 | - name: Node.js 15.x 116 | node-version: "15.14" 117 | 118 | - name: Node.js 16.x 119 | node-version: "16.20" 120 | 121 | - name: Node.js 17.x 122 | node-version: "17.9" 123 | 124 | - name: Node.js 18.x 125 | node-version: "18.18" 126 | 127 | - name: Node.js 19.x 128 | node-version: "19.9" 129 | 130 | - name: Node.js 20.x 131 | node-version: "20.9" 132 | 133 | - name: Node.js 21.x 134 | node-version: "21.7" 135 | 136 | - name: Node.js 22.x 137 | node-version: "22.0" 138 | 139 | steps: 140 | - uses: actions/checkout@v6.0.0 141 | 142 | - name: Install Node.js ${{ matrix.node-version }} 143 | shell: bash -eo pipefail -l {0} 144 | run: | 145 | nvm install --default ${{ matrix.node-version }} 146 | if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then 147 | nvm install --alias=npm 0.10 148 | nvm use ${{ matrix.node-version }} 149 | if [[ "$(npm -v)" == 1.1.* ]]; then 150 | nvm exec npm npm install -g npm@1.1 151 | ln -fs "$(which npm)" "$(dirname "$(nvm which npm)")/npm" 152 | else 153 | sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" 154 | fi 155 | npm config set strict-ssl false 156 | fi 157 | dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" 158 | 159 | - name: Configure npm 160 | run: | 161 | if [[ "$(npm config get package-lock)" == "true" ]]; then 162 | npm config set package-lock false 163 | else 164 | npm config set shrinkwrap false 165 | fi 166 | 167 | - name: Remove npm module(s) ${{ matrix.npm-rm }} 168 | run: npm rm --silent --save-dev ${{ matrix.npm-rm }} 169 | if: matrix.npm-rm != '' 170 | 171 | - name: Install npm module(s) ${{ matrix.npm-i }} 172 | run: npm install --save-dev ${{ matrix.npm-i }} 173 | if: matrix.npm-i != '' 174 | 175 | - name: Setup Node.js version-specific dependencies 176 | shell: bash 177 | run: | 178 | # eslint for linting 179 | # - remove on Node.js < 8 180 | if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 8 ]]; then 181 | node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ 182 | grep -E '^eslint(-|$)' | \ 183 | sort -r | \ 184 | xargs -n1 npm rm --silent --save-dev 185 | fi 186 | 187 | - name: Install Node.js dependencies 188 | run: npm install 189 | 190 | - name: List environment 191 | id: list_env 192 | shell: bash 193 | run: | 194 | echo "node@$(node -v)" 195 | echo "npm@$(npm -v)" 196 | npm -s ls ||: 197 | (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' 198 | 199 | - name: Run tests 200 | shell: bash 201 | run: | 202 | if npm -ps ls nyc | grep -q nyc; then 203 | npm run test-ci 204 | else 205 | npm test 206 | fi 207 | 208 | - name: Lint code 209 | if: steps.list_env.outputs.eslint != '' 210 | run: npm run lint 211 | 212 | - name: Collect code coverage 213 | uses: coverallsapp/github-action@master 214 | if: steps.list_env.outputs.nyc != '' 215 | with: 216 | github-token: ${{ secrets.GITHUB_TOKEN }} 217 | flag-name: run-${{ matrix.test_number }} 218 | parallel: true 219 | 220 | coverage: 221 | permissions: 222 | checks: write # for coverallsapp/github-action to create new checks 223 | needs: test 224 | runs-on: ubuntu-latest 225 | steps: 226 | - name: Upload code coverage 227 | uses: coverallsapp/github-action@master 228 | with: 229 | github-token: ${{ secrets.GITHUB_TOKEN }} 230 | parallel-finished: true 231 | --------------------------------------------------------------------------------