├── .commitlintrc.js ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── config.yml ├── actions │ ├── create-check │ │ └── action.yml │ └── install-latest-npm │ │ └── action.yml ├── dependabot.yml ├── matchers │ └── tap.json ├── settings.yml └── workflows │ ├── audit.yml │ ├── ci-release.yml │ ├── ci.yml │ ├── codeql-analysis.yml │ ├── post-dependabot.yml │ ├── pull-request.yml │ ├── release-integration.yml │ └── release.yml ├── .gitignore ├── .npmrc ├── .release-please-manifest.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── SECURITY.md ├── lib ├── advisory.js ├── get-dep-spec.js ├── hash.js └── index.js ├── map.js ├── package.json ├── release-please-config.json └── test ├── advisory.js ├── fixtures ├── advisories │ ├── graphql-codegen-plugin-helpers.json │ ├── index.js │ ├── minimist.json │ ├── no-severity-specified.json │ ├── no-vulnerable-versions-specified.json │ └── semver.json └── packuments │ ├── graphql-codegen-plugin-helpers.json │ ├── graphql-codegen-visitor-plugin-common.json │ ├── index.js │ ├── minimist.json │ ├── mkdirp.json │ ├── pacote.json │ └── semver.json ├── get-dep-spec.js ├── hash.js └── index.js /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */ 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']], 7 | 'header-max-length': [2, 'always', 80], 8 | 'subject-case': [0], 9 | 'body-max-line-length': [0], 10 | 'footer-max-line-length': [0], 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */ 2 | 3 | 'use strict' 4 | 5 | const { readdirSync: readdir } = require('fs') 6 | 7 | const localConfigs = readdir(__dirname) 8 | .filter((file) => file.startsWith('.eslintrc.local.')) 9 | .map((file) => `./${file}`) 10 | 11 | module.exports = { 12 | root: true, 13 | ignorePatterns: [ 14 | 'tap-testdir*/', 15 | ], 16 | extends: [ 17 | '@npmcli', 18 | ...localConfigs, 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | * @npm/cli-team 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Bug 4 | description: File a bug/issue 5 | title: "[BUG] " 6 | labels: [ Bug, Needs Triage ] 7 | 8 | body: 9 | - type: checkboxes 10 | attributes: 11 | label: Is there an existing issue for this? 12 | description: Please [search here](./issues) to see if an issue already exists for your problem. 13 | options: 14 | - label: I have searched the existing issues 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: Current Behavior 19 | description: A clear & concise description of what you're experiencing. 20 | validations: 21 | required: false 22 | - type: textarea 23 | attributes: 24 | label: Expected Behavior 25 | description: A clear & concise description of what you expected to happen. 26 | validations: 27 | required: false 28 | - type: textarea 29 | attributes: 30 | label: Steps To Reproduce 31 | description: Steps to reproduce the behavior. 32 | value: | 33 | 1. In this environment... 34 | 2. With this config... 35 | 3. Run '...' 36 | 4. See error... 37 | validations: 38 | required: false 39 | - type: textarea 40 | attributes: 41 | label: Environment 42 | description: | 43 | examples: 44 | - **npm**: 7.6.3 45 | - **Node**: 13.14.0 46 | - **OS**: Ubuntu 20.04 47 | - **platform**: Macbook Pro 48 | value: | 49 | - npm: 50 | - Node: 51 | - OS: 52 | - platform: 53 | validations: 54 | required: false 55 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | blank_issues_enabled: true 4 | -------------------------------------------------------------------------------- /.github/actions/create-check/action.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: 'Create Check' 4 | inputs: 5 | name: 6 | required: true 7 | token: 8 | required: true 9 | sha: 10 | required: true 11 | check-name: 12 | default: '' 13 | outputs: 14 | check-id: 15 | value: ${{ steps.create-check.outputs.check_id }} 16 | runs: 17 | using: "composite" 18 | steps: 19 | - name: Get Workflow Job 20 | uses: actions/github-script@v7 21 | id: workflow 22 | env: 23 | JOB_NAME: "${{ inputs.name }}" 24 | SHA: "${{ inputs.sha }}" 25 | with: 26 | result-encoding: string 27 | script: | 28 | const { repo: { owner, repo}, runId, serverUrl } = context 29 | const { JOB_NAME, SHA } = process.env 30 | 31 | const job = await github.rest.actions.listJobsForWorkflowRun({ 32 | owner, 33 | repo, 34 | run_id: runId, 35 | per_page: 100 36 | }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME))) 37 | 38 | return [ 39 | `This check is assosciated with ${serverUrl}/${owner}/${repo}/commit/${SHA}.`, 40 | 'Run logs:', 41 | job?.html_url || `could not be found for a job ending with: "${JOB_NAME}"`, 42 | ].join(' ') 43 | - name: Create Check 44 | uses: LouisBrunner/checks-action@v1.6.0 45 | id: create-check 46 | with: 47 | token: ${{ inputs.token }} 48 | sha: ${{ inputs.sha }} 49 | status: in_progress 50 | name: ${{ inputs.check-name || inputs.name }} 51 | output: | 52 | {"summary":"${{ steps.workflow.outputs.result }}"} 53 | -------------------------------------------------------------------------------- /.github/actions/install-latest-npm/action.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: 'Install Latest npm' 4 | description: 'Install the latest version of npm compatible with the Node version' 5 | inputs: 6 | node: 7 | description: 'Current Node version' 8 | required: true 9 | runs: 10 | using: "composite" 11 | steps: 12 | # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows 13 | - name: Update Windows npm 14 | if: | 15 | runner.os == 'Windows' && ( 16 | startsWith(inputs.node, 'v10.') || 17 | startsWith(inputs.node, 'v12.') || 18 | startsWith(inputs.node, 'v14.') 19 | ) 20 | shell: cmd 21 | run: | 22 | curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz 23 | tar xf npm-7.5.4.tgz 24 | cd package 25 | node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz 26 | cd .. 27 | rmdir /s /q package 28 | - name: Install Latest npm 29 | shell: bash 30 | env: 31 | NODE_VERSION: ${{ inputs.node }} 32 | working-directory: ${{ runner.temp }} 33 | run: | 34 | MATCH="" 35 | SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") 36 | 37 | echo "node@$NODE_VERSION" 38 | 39 | for SPEC in ${SPECS[@]}; do 40 | ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') 41 | echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" 42 | 43 | if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then 44 | MATCH=$SPEC 45 | echo "Found compatible version: npm@$MATCH" 46 | break 47 | fi 48 | done 49 | 50 | if [ -z $MATCH ]; then 51 | echo "Could not find a compatible version of npm for node@$NODE_VERSION" 52 | exit 1 53 | fi 54 | 55 | npm i --prefer-online --no-fund --no-audit -g npm@$MATCH 56 | - name: npm Version 57 | shell: bash 58 | run: npm -v 59 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: npm 7 | directory: / 8 | schedule: 9 | interval: daily 10 | target-branch: "main" 11 | allow: 12 | - dependency-type: direct 13 | versioning-strategy: increase-if-necessary 14 | commit-message: 15 | prefix: deps 16 | prefix-development: chore 17 | labels: 18 | - "Dependencies" 19 | open-pull-requests-limit: 10 20 | -------------------------------------------------------------------------------- /.github/matchers/tap.json: -------------------------------------------------------------------------------- 1 | { 2 | "//@npmcli/template-oss": "This file is automatically added by @npmcli/template-oss. Do not edit.", 3 | "problemMatcher": [ 4 | { 5 | "owner": "tap", 6 | "pattern": [ 7 | { 8 | "regexp": "^\\s*not ok \\d+ - (.*)", 9 | "message": 1 10 | }, 11 | { 12 | "regexp": "^\\s*---" 13 | }, 14 | { 15 | "regexp": "^\\s*at:" 16 | }, 17 | { 18 | "regexp": "^\\s*line:\\s*(\\d+)", 19 | "line": 1 20 | }, 21 | { 22 | "regexp": "^\\s*column:\\s*(\\d+)", 23 | "column": 1 24 | }, 25 | { 26 | "regexp": "^\\s*file:\\s*(.*)", 27 | "file": 1 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | repository: 4 | allow_merge_commit: false 5 | allow_rebase_merge: true 6 | allow_squash_merge: true 7 | squash_merge_commit_title: PR_TITLE 8 | squash_merge_commit_message: PR_BODY 9 | delete_branch_on_merge: true 10 | enable_automated_security_fixes: true 11 | enable_vulnerability_alerts: true 12 | 13 | branches: 14 | - name: main 15 | protection: 16 | required_status_checks: null 17 | enforce_admins: true 18 | block_creations: true 19 | required_pull_request_reviews: 20 | required_approving_review_count: 1 21 | require_code_owner_reviews: true 22 | require_last_push_approval: true 23 | dismiss_stale_reviews: true 24 | restrictions: 25 | apps: [] 26 | users: [] 27 | teams: [ "cli-team" ] 28 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Audit 4 | 5 | on: 6 | workflow_dispatch: 7 | schedule: 8 | # "At 08:00 UTC (01:00 PT) on Monday" https://crontab.guru/#0_8_*_*_1 9 | - cron: "0 8 * * 1" 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | audit: 16 | name: Audit Dependencies 17 | if: github.repository_owner == 'npm' 18 | runs-on: ubuntu-latest 19 | defaults: 20 | run: 21 | shell: bash 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | - name: Setup Git User 26 | run: | 27 | git config --global user.email "npm-cli+bot@github.com" 28 | git config --global user.name "npm CLI robot" 29 | - name: Setup Node 30 | uses: actions/setup-node@v4 31 | id: node 32 | with: 33 | node-version: 22.x 34 | check-latest: contains('22.x', '.x') 35 | - name: Install Latest npm 36 | uses: ./.github/actions/install-latest-npm 37 | with: 38 | node: ${{ steps.node.outputs.node-version }} 39 | - name: Install Dependencies 40 | run: npm i --ignore-scripts --no-audit --no-fund --package-lock 41 | - name: Run Production Audit 42 | run: npm audit --omit=dev 43 | - name: Run Full Audit 44 | run: npm audit --audit-level=none 45 | -------------------------------------------------------------------------------- /.github/workflows/ci-release.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: CI - Release 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | ref: 9 | required: true 10 | type: string 11 | default: main 12 | workflow_call: 13 | inputs: 14 | ref: 15 | required: true 16 | type: string 17 | check-sha: 18 | required: true 19 | type: string 20 | 21 | permissions: 22 | contents: read 23 | checks: write 24 | 25 | jobs: 26 | lint-all: 27 | name: Lint All 28 | if: github.repository_owner == 'npm' 29 | runs-on: ubuntu-latest 30 | defaults: 31 | run: 32 | shell: bash 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | with: 37 | ref: ${{ inputs.ref }} 38 | - name: Setup Git User 39 | run: | 40 | git config --global user.email "npm-cli+bot@github.com" 41 | git config --global user.name "npm CLI robot" 42 | - name: Create Check 43 | id: create-check 44 | if: ${{ inputs.check-sha }} 45 | uses: ./.github/actions/create-check 46 | with: 47 | name: "Lint All" 48 | token: ${{ secrets.GITHUB_TOKEN }} 49 | sha: ${{ inputs.check-sha }} 50 | - name: Setup Node 51 | uses: actions/setup-node@v4 52 | id: node 53 | with: 54 | node-version: 22.x 55 | check-latest: contains('22.x', '.x') 56 | - name: Install Latest npm 57 | uses: ./.github/actions/install-latest-npm 58 | with: 59 | node: ${{ steps.node.outputs.node-version }} 60 | - name: Install Dependencies 61 | run: npm i --ignore-scripts --no-audit --no-fund 62 | - name: Lint 63 | run: npm run lint --ignore-scripts 64 | - name: Post Lint 65 | run: npm run postlint --ignore-scripts 66 | - name: Conclude Check 67 | uses: LouisBrunner/checks-action@v1.6.0 68 | if: steps.create-check.outputs.check-id && always() 69 | with: 70 | token: ${{ secrets.GITHUB_TOKEN }} 71 | conclusion: ${{ job.status }} 72 | check_id: ${{ steps.create-check.outputs.check-id }} 73 | 74 | test-all: 75 | name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }} 76 | if: github.repository_owner == 'npm' 77 | strategy: 78 | fail-fast: false 79 | matrix: 80 | platform: 81 | - name: Linux 82 | os: ubuntu-latest 83 | shell: bash 84 | - name: macOS 85 | os: macos-latest 86 | shell: bash 87 | - name: macOS 88 | os: macos-13 89 | shell: bash 90 | - name: Windows 91 | os: windows-latest 92 | shell: cmd 93 | node-version: 94 | - 16.14.0 95 | - 16.x 96 | - 18.0.0 97 | - 18.x 98 | - 20.17.0 99 | - 20.x 100 | - 22.9.0 101 | - 22.x 102 | exclude: 103 | - platform: { name: macOS, os: macos-13, shell: bash } 104 | node-version: 16.14.0 105 | - platform: { name: macOS, os: macos-13, shell: bash } 106 | node-version: 16.x 107 | - platform: { name: macOS, os: macos-13, shell: bash } 108 | node-version: 18.0.0 109 | - platform: { name: macOS, os: macos-13, shell: bash } 110 | node-version: 18.x 111 | - platform: { name: macOS, os: macos-13, shell: bash } 112 | node-version: 20.17.0 113 | - platform: { name: macOS, os: macos-13, shell: bash } 114 | node-version: 20.x 115 | - platform: { name: macOS, os: macos-13, shell: bash } 116 | node-version: 22.9.0 117 | - platform: { name: macOS, os: macos-13, shell: bash } 118 | node-version: 22.x 119 | runs-on: ${{ matrix.platform.os }} 120 | defaults: 121 | run: 122 | shell: ${{ matrix.platform.shell }} 123 | steps: 124 | - name: Checkout 125 | uses: actions/checkout@v4 126 | with: 127 | ref: ${{ inputs.ref }} 128 | - name: Setup Git User 129 | run: | 130 | git config --global user.email "npm-cli+bot@github.com" 131 | git config --global user.name "npm CLI robot" 132 | - name: Create Check 133 | id: create-check 134 | if: ${{ inputs.check-sha }} 135 | uses: ./.github/actions/create-check 136 | with: 137 | name: "Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}" 138 | token: ${{ secrets.GITHUB_TOKEN }} 139 | sha: ${{ inputs.check-sha }} 140 | - name: Setup Node 141 | uses: actions/setup-node@v4 142 | id: node 143 | with: 144 | node-version: ${{ matrix.node-version }} 145 | check-latest: contains(matrix.node-version, '.x') 146 | - name: Install Latest npm 147 | uses: ./.github/actions/install-latest-npm 148 | with: 149 | node: ${{ steps.node.outputs.node-version }} 150 | - name: Install Dependencies 151 | run: npm i --ignore-scripts --no-audit --no-fund 152 | - name: Add Problem Matcher 153 | run: echo "::add-matcher::.github/matchers/tap.json" 154 | - name: Test 155 | run: npm test --ignore-scripts 156 | - name: Conclude Check 157 | uses: LouisBrunner/checks-action@v1.6.0 158 | if: steps.create-check.outputs.check-id && always() 159 | with: 160 | token: ${{ secrets.GITHUB_TOKEN }} 161 | conclusion: ${{ job.status }} 162 | check_id: ${{ steps.create-check.outputs.check-id }} 163 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: CI 4 | 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | schedule: 12 | # "At 09:00 UTC (02:00 PT) on Monday" https://crontab.guru/#0_9_*_*_1 13 | - cron: "0 9 * * 1" 14 | 15 | permissions: 16 | contents: read 17 | 18 | jobs: 19 | lint: 20 | name: Lint 21 | if: github.repository_owner == 'npm' 22 | runs-on: ubuntu-latest 23 | defaults: 24 | run: 25 | shell: bash 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | - name: Setup Git User 30 | run: | 31 | git config --global user.email "npm-cli+bot@github.com" 32 | git config --global user.name "npm CLI robot" 33 | - name: Setup Node 34 | uses: actions/setup-node@v4 35 | id: node 36 | with: 37 | node-version: 22.x 38 | check-latest: contains('22.x', '.x') 39 | - name: Install Latest npm 40 | uses: ./.github/actions/install-latest-npm 41 | with: 42 | node: ${{ steps.node.outputs.node-version }} 43 | - name: Install Dependencies 44 | run: npm i --ignore-scripts --no-audit --no-fund 45 | - name: Lint 46 | run: npm run lint --ignore-scripts 47 | - name: Post Lint 48 | run: npm run postlint --ignore-scripts 49 | 50 | test: 51 | name: Test - ${{ matrix.platform.name }} - ${{ matrix.node-version }} 52 | if: github.repository_owner == 'npm' 53 | strategy: 54 | fail-fast: false 55 | matrix: 56 | platform: 57 | - name: Linux 58 | os: ubuntu-latest 59 | shell: bash 60 | - name: macOS 61 | os: macos-latest 62 | shell: bash 63 | - name: macOS 64 | os: macos-13 65 | shell: bash 66 | - name: Windows 67 | os: windows-latest 68 | shell: cmd 69 | node-version: 70 | - 16.14.0 71 | - 16.x 72 | - 18.0.0 73 | - 18.x 74 | - 20.17.0 75 | - 20.x 76 | - 22.9.0 77 | - 22.x 78 | exclude: 79 | - platform: { name: macOS, os: macos-13, shell: bash } 80 | node-version: 16.14.0 81 | - platform: { name: macOS, os: macos-13, shell: bash } 82 | node-version: 16.x 83 | - platform: { name: macOS, os: macos-13, shell: bash } 84 | node-version: 18.0.0 85 | - platform: { name: macOS, os: macos-13, shell: bash } 86 | node-version: 18.x 87 | - platform: { name: macOS, os: macos-13, shell: bash } 88 | node-version: 20.17.0 89 | - platform: { name: macOS, os: macos-13, shell: bash } 90 | node-version: 20.x 91 | - platform: { name: macOS, os: macos-13, shell: bash } 92 | node-version: 22.9.0 93 | - platform: { name: macOS, os: macos-13, shell: bash } 94 | node-version: 22.x 95 | runs-on: ${{ matrix.platform.os }} 96 | defaults: 97 | run: 98 | shell: ${{ matrix.platform.shell }} 99 | steps: 100 | - name: Checkout 101 | uses: actions/checkout@v4 102 | - name: Setup Git User 103 | run: | 104 | git config --global user.email "npm-cli+bot@github.com" 105 | git config --global user.name "npm CLI robot" 106 | - name: Setup Node 107 | uses: actions/setup-node@v4 108 | id: node 109 | with: 110 | node-version: ${{ matrix.node-version }} 111 | check-latest: contains(matrix.node-version, '.x') 112 | - name: Install Latest npm 113 | uses: ./.github/actions/install-latest-npm 114 | with: 115 | node: ${{ steps.node.outputs.node-version }} 116 | - name: Install Dependencies 117 | run: npm i --ignore-scripts --no-audit --no-fund 118 | - name: Add Problem Matcher 119 | run: echo "::add-matcher::.github/matchers/tap.json" 120 | - name: Test 121 | run: npm test --ignore-scripts 122 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: CodeQL 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | pull_request: 10 | branches: 11 | - main 12 | schedule: 13 | # "At 10:00 UTC (03:00 PT) on Monday" https://crontab.guru/#0_10_*_*_1 14 | - cron: "0 10 * * 1" 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | analyze: 21 | name: Analyze 22 | runs-on: ubuntu-latest 23 | permissions: 24 | actions: read 25 | contents: read 26 | security-events: write 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Setup Git User 31 | run: | 32 | git config --global user.email "npm-cli+bot@github.com" 33 | git config --global user.name "npm CLI robot" 34 | - name: Initialize CodeQL 35 | uses: github/codeql-action/init@v3 36 | with: 37 | languages: javascript 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v3 40 | -------------------------------------------------------------------------------- /.github/workflows/post-dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Post Dependabot 4 | 5 | on: pull_request 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | template-oss: 12 | name: template-oss 13 | if: github.repository_owner == 'npm' && github.actor == 'dependabot[bot]' 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | shell: bash 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | ref: ${{ github.event.pull_request.head.ref }} 23 | - name: Setup Git User 24 | run: | 25 | git config --global user.email "npm-cli+bot@github.com" 26 | git config --global user.name "npm CLI robot" 27 | - name: Setup Node 28 | uses: actions/setup-node@v4 29 | id: node 30 | with: 31 | node-version: 22.x 32 | check-latest: contains('22.x', '.x') 33 | - name: Install Latest npm 34 | uses: ./.github/actions/install-latest-npm 35 | with: 36 | node: ${{ steps.node.outputs.node-version }} 37 | - name: Install Dependencies 38 | run: npm i --ignore-scripts --no-audit --no-fund 39 | - name: Fetch Dependabot Metadata 40 | id: metadata 41 | uses: dependabot/fetch-metadata@v1 42 | with: 43 | github-token: ${{ secrets.GITHUB_TOKEN }} 44 | 45 | # Dependabot can update multiple directories so we output which directory 46 | # it is acting on so we can run the command for the correct root or workspace 47 | - name: Get Dependabot Directory 48 | if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss') 49 | id: flags 50 | run: | 51 | dependabot_dir="${{ steps.metadata.outputs.directory }}" 52 | if [[ "$dependabot_dir" == "/" || "$dependabot_dir" == "/main" ]]; then 53 | echo "workspace=-iwr" >> $GITHUB_OUTPUT 54 | else 55 | # strip leading slash from directory so it works as a 56 | # a path to the workspace flag 57 | echo "workspace=-w ${dependabot_dir#/}" >> $GITHUB_OUTPUT 58 | fi 59 | 60 | - name: Apply Changes 61 | if: steps.flags.outputs.workspace 62 | id: apply 63 | run: | 64 | npm run template-oss-apply ${{ steps.flags.outputs.workspace }} 65 | if [[ `git status --porcelain` ]]; then 66 | echo "changes=true" >> $GITHUB_OUTPUT 67 | fi 68 | # This only sets the conventional commit prefix. This workflow can't reliably determine 69 | # what the breaking change is though. If a BREAKING CHANGE message is required then 70 | # this PR check will fail and the commit will be amended with stafftools 71 | if [[ "${{ steps.metadata.outputs.update-type }}" == "version-update:semver-major" ]]; then 72 | prefix='feat!' 73 | else 74 | prefix='chore' 75 | fi 76 | echo "message=$prefix: postinstall for dependabot template-oss PR" >> $GITHUB_OUTPUT 77 | 78 | # This step will fail if template-oss has made any workflow updates. It is impossible 79 | # for a workflow to update other workflows. In the case it does fail, we continue 80 | # and then try to apply only a portion of the changes in the next step 81 | - name: Push All Changes 82 | if: steps.apply.outputs.changes 83 | id: push 84 | continue-on-error: true 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 87 | run: | 88 | git commit -am "${{ steps.apply.outputs.message }}" 89 | git push 90 | 91 | # If the previous step failed, then reset the commit and remove any workflow changes 92 | # and attempt to commit and push again. This is helpful because we will have a commit 93 | # with the correct prefix that we can then --amend with @npmcli/stafftools later. 94 | - name: Push All Changes Except Workflows 95 | if: steps.apply.outputs.changes && steps.push.outcome == 'failure' 96 | env: 97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 98 | run: | 99 | git reset HEAD~ 100 | git checkout HEAD -- .github/workflows/ 101 | git clean -fd .github/workflows/ 102 | git commit -am "${{ steps.apply.outputs.message }}" 103 | git push 104 | 105 | # Check if all the necessary template-oss changes were applied. Since we continued 106 | # on errors in one of the previous steps, this check will fail if our follow up 107 | # only applied a portion of the changes and we need to followup manually. 108 | # 109 | # Note that this used to run `lint` and `postlint` but that will fail this action 110 | # if we've also shipped any linting changes separate from template-oss. We do 111 | # linting in another action, so we want to fail this one only if there are 112 | # template-oss changes that could not be applied. 113 | - name: Check Changes 114 | if: steps.apply.outputs.changes 115 | run: | 116 | npm exec --offline ${{ steps.flags.outputs.workspace }} -- template-oss-check 117 | 118 | - name: Fail on Breaking Change 119 | if: steps.apply.outputs.changes && startsWith(steps.apply.outputs.message, 'feat!') 120 | run: | 121 | echo "This PR has a breaking change. Run 'npx -p @npmcli/stafftools gh template-oss-fix'" 122 | echo "for more information on how to fix this with a BREAKING CHANGE footer." 123 | exit 1 124 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Pull Request 4 | 5 | on: 6 | pull_request: 7 | types: 8 | - opened 9 | - reopened 10 | - edited 11 | - synchronize 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | commitlint: 18 | name: Lint Commits 19 | if: github.repository_owner == 'npm' 20 | runs-on: ubuntu-latest 21 | defaults: 22 | run: 23 | shell: bash 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | - name: Setup Git User 30 | run: | 31 | git config --global user.email "npm-cli+bot@github.com" 32 | git config --global user.name "npm CLI robot" 33 | - name: Setup Node 34 | uses: actions/setup-node@v4 35 | id: node 36 | with: 37 | node-version: 22.x 38 | check-latest: contains('22.x', '.x') 39 | - name: Install Latest npm 40 | uses: ./.github/actions/install-latest-npm 41 | with: 42 | node: ${{ steps.node.outputs.node-version }} 43 | - name: Install Dependencies 44 | run: npm i --ignore-scripts --no-audit --no-fund 45 | - name: Run Commitlint on Commits 46 | id: commit 47 | continue-on-error: true 48 | run: npx --offline commitlint -V --from 'origin/${{ github.base_ref }}' --to ${{ github.event.pull_request.head.sha }} 49 | - name: Run Commitlint on PR Title 50 | if: steps.commit.outcome == 'failure' 51 | env: 52 | PR_TITLE: ${{ github.event.pull_request.title }} 53 | run: echo "$PR_TITLE" | npx --offline commitlint -V 54 | -------------------------------------------------------------------------------- /.github/workflows/release-integration.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Release Integration 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | releases: 9 | required: true 10 | type: string 11 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' 12 | workflow_call: 13 | inputs: 14 | releases: 15 | required: true 16 | type: string 17 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' 18 | secrets: 19 | PUBLISH_TOKEN: 20 | required: true 21 | 22 | permissions: 23 | contents: read 24 | id-token: write 25 | 26 | jobs: 27 | publish: 28 | name: Publish 29 | runs-on: ubuntu-latest 30 | defaults: 31 | run: 32 | shell: bash 33 | permissions: 34 | id-token: write 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | with: 39 | ref: ${{ fromJSON(inputs.releases)[0].tagName }} 40 | - name: Setup Git User 41 | run: | 42 | git config --global user.email "npm-cli+bot@github.com" 43 | git config --global user.name "npm CLI robot" 44 | - name: Setup Node 45 | uses: actions/setup-node@v4 46 | id: node 47 | with: 48 | node-version: 22.x 49 | check-latest: contains('22.x', '.x') 50 | - name: Install Latest npm 51 | uses: ./.github/actions/install-latest-npm 52 | with: 53 | node: ${{ steps.node.outputs.node-version }} 54 | - name: Install Dependencies 55 | run: npm i --ignore-scripts --no-audit --no-fund 56 | - name: Set npm authToken 57 | run: npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN} 58 | - name: Publish 59 | env: 60 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} 61 | RELEASES: ${{ inputs.releases }} 62 | run: | 63 | EXIT_CODE=0 64 | 65 | for release in $(echo $RELEASES | jq -r '.[] | @base64'); do 66 | PUBLISH_TAG=$(echo "$release" | base64 --decode | jq -r .publishTag) 67 | npm publish --provenance --tag="$PUBLISH_TAG" 68 | STATUS=$? 69 | if [[ "$STATUS" -eq 1 ]]; then 70 | EXIT_CODE=$STATUS 71 | fi 72 | done 73 | 74 | exit $EXIT_CODE 75 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Release 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | 10 | permissions: 11 | contents: write 12 | pull-requests: write 13 | checks: write 14 | 15 | jobs: 16 | release: 17 | outputs: 18 | pr: ${{ steps.release.outputs.pr }} 19 | pr-branch: ${{ steps.release.outputs.pr-branch }} 20 | pr-number: ${{ steps.release.outputs.pr-number }} 21 | pr-sha: ${{ steps.release.outputs.pr-sha }} 22 | releases: ${{ steps.release.outputs.releases }} 23 | comment-id: ${{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }} 24 | check-id: ${{ steps.create-check.outputs.check-id }} 25 | name: Release 26 | if: github.repository_owner == 'npm' 27 | runs-on: ubuntu-latest 28 | defaults: 29 | run: 30 | shell: bash 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Git User 35 | run: | 36 | git config --global user.email "npm-cli+bot@github.com" 37 | git config --global user.name "npm CLI robot" 38 | - name: Setup Node 39 | uses: actions/setup-node@v4 40 | id: node 41 | with: 42 | node-version: 22.x 43 | check-latest: contains('22.x', '.x') 44 | - name: Install Latest npm 45 | uses: ./.github/actions/install-latest-npm 46 | with: 47 | node: ${{ steps.node.outputs.node-version }} 48 | - name: Install Dependencies 49 | run: npm i --ignore-scripts --no-audit --no-fund 50 | - name: Release Please 51 | id: release 52 | env: 53 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 54 | run: npx --offline template-oss-release-please --branch="${{ github.ref_name }}" --backport="" --defaultTag="latest" 55 | - name: Create Release Manager Comment Text 56 | if: steps.release.outputs.pr-number 57 | uses: actions/github-script@v7 58 | id: comment-text 59 | with: 60 | result-encoding: string 61 | script: | 62 | const { runId, repo: { owner, repo } } = context 63 | const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId }) 64 | return['## Release Manager', `Release workflow run: ${workflow.html_url}`].join('\n\n') 65 | - name: Find Release Manager Comment 66 | uses: peter-evans/find-comment@v2 67 | if: steps.release.outputs.pr-number 68 | id: found-comment 69 | with: 70 | issue-number: ${{ steps.release.outputs.pr-number }} 71 | comment-author: 'github-actions[bot]' 72 | body-includes: '## Release Manager' 73 | - name: Create Release Manager Comment 74 | id: create-comment 75 | if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id 76 | uses: peter-evans/create-or-update-comment@v3 77 | with: 78 | issue-number: ${{ steps.release.outputs.pr-number }} 79 | body: ${{ steps.comment-text.outputs.result }} 80 | - name: Update Release Manager Comment 81 | id: update-comment 82 | if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id 83 | uses: peter-evans/create-or-update-comment@v3 84 | with: 85 | comment-id: ${{ steps.found-comment.outputs.comment-id }} 86 | body: ${{ steps.comment-text.outputs.result }} 87 | edit-mode: 'replace' 88 | - name: Create Check 89 | id: create-check 90 | uses: ./.github/actions/create-check 91 | if: steps.release.outputs.pr-sha 92 | with: 93 | name: "Release" 94 | token: ${{ secrets.GITHUB_TOKEN }} 95 | sha: ${{ steps.release.outputs.pr-sha }} 96 | 97 | update: 98 | needs: release 99 | outputs: 100 | sha: ${{ steps.commit.outputs.sha }} 101 | check-id: ${{ steps.create-check.outputs.check-id }} 102 | name: Update - Release 103 | if: github.repository_owner == 'npm' && needs.release.outputs.pr 104 | runs-on: ubuntu-latest 105 | defaults: 106 | run: 107 | shell: bash 108 | steps: 109 | - name: Checkout 110 | uses: actions/checkout@v4 111 | with: 112 | fetch-depth: 0 113 | ref: ${{ needs.release.outputs.pr-branch }} 114 | - name: Setup Git User 115 | run: | 116 | git config --global user.email "npm-cli+bot@github.com" 117 | git config --global user.name "npm CLI robot" 118 | - name: Setup Node 119 | uses: actions/setup-node@v4 120 | id: node 121 | with: 122 | node-version: 22.x 123 | check-latest: contains('22.x', '.x') 124 | - name: Install Latest npm 125 | uses: ./.github/actions/install-latest-npm 126 | with: 127 | node: ${{ steps.node.outputs.node-version }} 128 | - name: Install Dependencies 129 | run: npm i --ignore-scripts --no-audit --no-fund 130 | - name: Create Release Manager Checklist Text 131 | id: comment-text 132 | env: 133 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 134 | run: npm exec --offline -- template-oss-release-manager --pr="${{ needs.release.outputs.pr-number }}" --backport="" --defaultTag="latest" --publish 135 | - name: Append Release Manager Comment 136 | uses: peter-evans/create-or-update-comment@v3 137 | with: 138 | comment-id: ${{ needs.release.outputs.comment-id }} 139 | body: ${{ steps.comment-text.outputs.result }} 140 | edit-mode: 'append' 141 | - name: Run Post Pull Request Actions 142 | env: 143 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 144 | run: npm run rp-pull-request --ignore-scripts --if-present -- --pr="${{ needs.release.outputs.pr-number }}" --commentId="${{ needs.release.outputs.comment-id }}" 145 | - name: Commit 146 | id: commit 147 | env: 148 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 149 | run: | 150 | git commit --all --amend --no-edit || true 151 | git push --force-with-lease 152 | echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT 153 | - name: Create Check 154 | id: create-check 155 | uses: ./.github/actions/create-check 156 | with: 157 | name: "Update - Release" 158 | check-name: "Release" 159 | token: ${{ secrets.GITHUB_TOKEN }} 160 | sha: ${{ steps.commit.outputs.sha }} 161 | - name: Conclude Check 162 | uses: LouisBrunner/checks-action@v1.6.0 163 | with: 164 | token: ${{ secrets.GITHUB_TOKEN }} 165 | conclusion: ${{ job.status }} 166 | check_id: ${{ needs.release.outputs.check-id }} 167 | 168 | ci: 169 | name: CI - Release 170 | needs: [ release, update ] 171 | if: needs.release.outputs.pr 172 | uses: ./.github/workflows/ci-release.yml 173 | with: 174 | ref: ${{ needs.release.outputs.pr-branch }} 175 | check-sha: ${{ needs.update.outputs.sha }} 176 | 177 | post-ci: 178 | needs: [ release, update, ci ] 179 | name: Post CI - Release 180 | if: github.repository_owner == 'npm' && needs.release.outputs.pr && always() 181 | runs-on: ubuntu-latest 182 | defaults: 183 | run: 184 | shell: bash 185 | steps: 186 | - name: Get CI Conclusion 187 | id: conclusion 188 | run: | 189 | result="" 190 | if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then 191 | result="failure" 192 | elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then 193 | result="cancelled" 194 | else 195 | result="success" 196 | fi 197 | echo "result=$result" >> $GITHUB_OUTPUT 198 | - name: Conclude Check 199 | uses: LouisBrunner/checks-action@v1.6.0 200 | with: 201 | token: ${{ secrets.GITHUB_TOKEN }} 202 | conclusion: ${{ steps.conclusion.outputs.result }} 203 | check_id: ${{ needs.update.outputs.check-id }} 204 | 205 | post-release: 206 | needs: release 207 | outputs: 208 | comment-id: ${{ steps.create-comment.outputs.comment-id }} 209 | name: Post Release - Release 210 | if: github.repository_owner == 'npm' && needs.release.outputs.releases 211 | runs-on: ubuntu-latest 212 | defaults: 213 | run: 214 | shell: bash 215 | steps: 216 | - name: Create Release PR Comment Text 217 | id: comment-text 218 | uses: actions/github-script@v7 219 | env: 220 | RELEASES: ${{ needs.release.outputs.releases }} 221 | with: 222 | result-encoding: string 223 | script: | 224 | const releases = JSON.parse(process.env.RELEASES) 225 | const { runId, repo: { owner, repo } } = context 226 | const issue_number = releases[0].prNumber 227 | const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}` 228 | 229 | return [ 230 | '## Release Workflow\n', 231 | ...releases.map(r => `- \`${r.pkgName}@${r.version}\` ${r.url}`), 232 | `- Workflow run: :arrows_counterclockwise: ${runUrl}`, 233 | ].join('\n') 234 | - name: Create Release PR Comment 235 | id: create-comment 236 | uses: peter-evans/create-or-update-comment@v3 237 | with: 238 | issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} 239 | body: ${{ steps.comment-text.outputs.result }} 240 | 241 | release-integration: 242 | needs: release 243 | name: Release Integration 244 | if: needs.release.outputs.releases 245 | uses: ./.github/workflows/release-integration.yml 246 | permissions: 247 | contents: read 248 | id-token: write 249 | secrets: 250 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} 251 | with: 252 | releases: ${{ needs.release.outputs.releases }} 253 | 254 | post-release-integration: 255 | needs: [ release, release-integration, post-release ] 256 | name: Post Release Integration - Release 257 | if: github.repository_owner == 'npm' && needs.release.outputs.releases && always() 258 | runs-on: ubuntu-latest 259 | defaults: 260 | run: 261 | shell: bash 262 | steps: 263 | - name: Get Post Release Conclusion 264 | id: conclusion 265 | run: | 266 | if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then 267 | result="x" 268 | elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then 269 | result="heavy_multiplication_x" 270 | else 271 | result="white_check_mark" 272 | fi 273 | echo "result=$result" >> $GITHUB_OUTPUT 274 | - name: Find Release PR Comment 275 | uses: peter-evans/find-comment@v2 276 | id: found-comment 277 | with: 278 | issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }} 279 | comment-author: 'github-actions[bot]' 280 | body-includes: '## Release Workflow' 281 | - name: Create Release PR Comment Text 282 | id: comment-text 283 | if: steps.found-comment.outputs.comment-id 284 | uses: actions/github-script@v7 285 | env: 286 | RESULT: ${{ steps.conclusion.outputs.result }} 287 | BODY: ${{ steps.found-comment.outputs.comment-body }} 288 | with: 289 | result-encoding: string 290 | script: | 291 | const { RESULT, BODY } = process.env 292 | const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, `$1${RESULT}$2`)] 293 | if (RESULT !== 'white_check_mark') { 294 | body.push(':rotating_light::rotating_light::rotating_light:') 295 | body.push([ 296 | '@npm/cli-team: The post-release workflow failed for this release.', 297 | 'Manual steps may need to be taken after examining the workflow output.' 298 | ].join(' ')) 299 | body.push(':rotating_light::rotating_light::rotating_light:') 300 | } 301 | return body.join('\n\n').trim() 302 | - name: Update Release PR Comment 303 | if: steps.comment-text.outputs.result 304 | uses: peter-evans/create-or-update-comment@v3 305 | with: 306 | comment-id: ${{ steps.found-comment.outputs.comment-id }} 307 | body: ${{ steps.comment-text.outputs.result }} 308 | edit-mode: 'replace' 309 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | # ignore everything in the root 4 | /* 5 | 6 | !**/.gitignore 7 | !/.commitlintrc.js 8 | !/.eslint.config.js 9 | !/.eslintrc.js 10 | !/.eslintrc.local.* 11 | !/.git-blame-ignore-revs 12 | !/.github/ 13 | !/.gitignore 14 | !/.npmrc 15 | !/.prettierignore 16 | !/.prettierrc.js 17 | !/.release-please-manifest.json 18 | !/bin/ 19 | !/CHANGELOG* 20 | !/CODE_OF_CONDUCT.md 21 | !/CONTRIBUTING.md 22 | !/docs/ 23 | !/lib/ 24 | !/LICENSE* 25 | !/map.js 26 | !/package.json 27 | !/README* 28 | !/release-please-config.json 29 | !/scripts/ 30 | !/SECURITY.md 31 | !/tap-snapshots/ 32 | !/test/ 33 | !/tsconfig.json 34 | tap-testdir*/ 35 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ; This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | package-lock=false 4 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "9.0.0" 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [9.0.0](https://github.com/npm/metavuln-calculator/compare/v8.0.1...v9.0.0) (2024-11-25) 4 | ### ⚠️ BREAKING CHANGES 5 | * this module is now compatible with the following node versions: ^20.17.0 || >=22.9.0 6 | ### Bug Fixes 7 | * [`af594f0`](https://github.com/npm/metavuln-calculator/commit/af594f045c0f40a5513d059ed5fc22931c284dd7) update node engines to ^20.17.0 || >=22.9.0 (#159) (@wraithgar) 8 | ### Dependencies 9 | * [`f6e645c`](https://github.com/npm/metavuln-calculator/commit/f6e645ce0b47867a0dcff8840aeb7545d7eea21a) [#161](https://github.com/npm/metavuln-calculator/pull/161) `pacote@21.0.0` (#161) 10 | ### Chores 11 | * [`efcc950`](https://github.com/npm/metavuln-calculator/commit/efcc950f3ee44b04415c2dd2f04f83564aa26705) [#156](https://github.com/npm/metavuln-calculator/pull/156) bump @npmcli/template-oss from 4.23.3 to 4.23.4 (#156) (@dependabot[bot], @npm-cli-bot) 12 | 13 | ## [8.0.1](https://github.com/npm/metavuln-calculator/compare/v8.0.0...v8.0.1) (2024-10-17) 14 | ### Dependencies 15 | * [`c92af1b`](https://github.com/npm/metavuln-calculator/commit/c92af1b1a63bc9d554046c6d1b93684fbcaa56fc) [#157](https://github.com/npm/metavuln-calculator/pull/157) bump pacote from ^19.0.0 to ^20.0.0 (#157) 16 | 17 | ## [8.0.0](https://github.com/npm/metavuln-calculator/compare/v7.1.1...v8.0.0) (2024-10-01) 18 | ### ⚠️ BREAKING CHANGES 19 | * `@npmcli/metavuln-calculator` now supports node `^18.17.0 || >=20.5.0` 20 | ### Bug Fixes 21 | * [`76124e1`](https://github.com/npm/metavuln-calculator/commit/76124e1bd9c6a016146c1dd728cc980f6104fcd5) [#152](https://github.com/npm/metavuln-calculator/pull/152) align to npm 10 node engine range (@reggi) 22 | ### Dependencies 23 | * [`397f279`](https://github.com/npm/metavuln-calculator/commit/397f279faefa34b856783bf9e815869ec9148989) [#152](https://github.com/npm/metavuln-calculator/pull/152) `proc-log@5.0.0` 24 | * [`47f88c9`](https://github.com/npm/metavuln-calculator/commit/47f88c99c19a32ef54162fa65378f672173726b2) [#152](https://github.com/npm/metavuln-calculator/pull/152) `pacote@19.0.0` 25 | * [`aefff63`](https://github.com/npm/metavuln-calculator/commit/aefff631dd4907cca93e65522cdded865c3c5c12) [#152](https://github.com/npm/metavuln-calculator/pull/152) `json-parse-even-better-errors@4.0.0` 26 | * [`348ed6d`](https://github.com/npm/metavuln-calculator/commit/348ed6dbcee832e6530ce99fe6ab5bac0da7f4df) [#152](https://github.com/npm/metavuln-calculator/pull/152) `cacache@19.0.0` 27 | ### Chores 28 | * [`a13b312`](https://github.com/npm/metavuln-calculator/commit/a13b31278cf6bfdc6ba0205736eb7d9a2fe2e9ff) [#152](https://github.com/npm/metavuln-calculator/pull/152) run template-oss-apply (@reggi) 29 | * [`1db6387`](https://github.com/npm/metavuln-calculator/commit/1db638776d5b60b68fbc0ad78fcb30bf0ec4c16c) [#146](https://github.com/npm/metavuln-calculator/pull/146) bump @npmcli/eslint-config from 4.0.5 to 5.0.0 (@dependabot[bot]) 30 | * [`5b3ccdf`](https://github.com/npm/metavuln-calculator/commit/5b3ccdf32ee815e255a91899fab51d6290adcfce) [#147](https://github.com/npm/metavuln-calculator/pull/147) postinstall for dependabot template-oss PR (@hashtagchris) 31 | * [`91ab93e`](https://github.com/npm/metavuln-calculator/commit/91ab93eec74d9ded622c76d6f8d1f3e500aaf0ac) [#147](https://github.com/npm/metavuln-calculator/pull/147) bump @npmcli/template-oss from 4.23.1 to 4.23.3 (@dependabot[bot]) 32 | 33 | ## [7.1.1](https://github.com/npm/metavuln-calculator/compare/v7.1.0...v7.1.1) (2024-05-04) 34 | 35 | ### Bug Fixes 36 | 37 | * [`1631d76`](https://github.com/npm/metavuln-calculator/commit/1631d76897b249983ab1ee4a9962ff6dc595af06) [#134](https://github.com/npm/metavuln-calculator/pull/134) linting: no-unused-vars (@lukekarrys) 38 | 39 | ### Chores 40 | 41 | * [`c506f88`](https://github.com/npm/metavuln-calculator/commit/c506f886c6318e5760239cb444ef35c662ad4dad) [#134](https://github.com/npm/metavuln-calculator/pull/134) bump @npmcli/template-oss to 4.22.0 (@lukekarrys) 42 | * [`b81c6ed`](https://github.com/npm/metavuln-calculator/commit/b81c6edb86423aa86e7c49278264204aa0065560) [#134](https://github.com/npm/metavuln-calculator/pull/134) postinstall for dependabot template-oss PR (@lukekarrys) 43 | 44 | ## [7.1.0](https://github.com/npm/metavuln-calculator/compare/v7.0.1...v7.1.0) (2024-04-16) 45 | 46 | ### Features 47 | 48 | * [`beb5c1f`](https://github.com/npm/metavuln-calculator/commit/beb5c1f91b041113a5ac92ec91da4ce3737c8580) [#132](https://github.com/npm/metavuln-calculator/pull/132) emit timers with proc-log (@lukekarrys) 49 | 50 | ### Dependencies 51 | 52 | * [`434f93e`](https://github.com/npm/metavuln-calculator/commit/434f93ea8faab8bf9ae50afeb739f0b75eba9c14) [#132](https://github.com/npm/metavuln-calculator/pull/132) `proc-log@4.1.0` 53 | 54 | ## [7.0.1](https://github.com/npm/metavuln-calculator/compare/v7.0.0...v7.0.1) (2024-04-15) 55 | 56 | ### Dependencies 57 | 58 | * [`a23259a`](https://github.com/npm/metavuln-calculator/commit/a23259a8b4516c8e485b614d40657f8ffa99dd96) [#131](https://github.com/npm/metavuln-calculator/pull/131) bump pacote from 17.0.7 to 18.0.0 (#131) (@dependabot[bot]) 59 | 60 | ### Chores 61 | 62 | * [`bd72803`](https://github.com/npm/metavuln-calculator/commit/bd728031aac8bfb6f71d3220cb3d5edbcbf8f2a1) [#130](https://github.com/npm/metavuln-calculator/pull/130) postinstall for dependabot template-oss PR (@lukekarrys) 63 | * [`290eb7c`](https://github.com/npm/metavuln-calculator/commit/290eb7c0b74fef50b2a07fb7bb0da11ed06c3177) [#130](https://github.com/npm/metavuln-calculator/pull/130) bump @npmcli/template-oss from 4.21.3 to 4.21.4 (@dependabot[bot]) 64 | 65 | ## [7.0.0](https://github.com/npm/metavuln-calculator/compare/v6.0.1...v7.0.0) (2023-08-15) 66 | 67 | ### ⚠️ BREAKING CHANGES 68 | 69 | * support for node <=16.13 has been removed 70 | 71 | ### Bug Fixes 72 | 73 | * [`071449d`](https://github.com/npm/metavuln-calculator/commit/071449da2467c0795406b17cfc0962df2d0a9d3c) [#99](https://github.com/npm/metavuln-calculator/pull/99) drop node 16.13.x support (@lukekarrys) 74 | 75 | ### Dependencies 76 | 77 | * [`d5ba3e4`](https://github.com/npm/metavuln-calculator/commit/d5ba3e4332896370d344723f65fd07e7f50af752) [#101](https://github.com/npm/metavuln-calculator/pull/101) bump pacote from 16.0.0 to 17.0.0 78 | * [`49e9861`](https://github.com/npm/metavuln-calculator/commit/49e986183484a8323882cd1d542cb5850b09b9c2) [#97](https://github.com/npm/metavuln-calculator/pull/97) bump cacache from 17.1.4 to 18.0.0 79 | 80 | ## [6.0.1](https://github.com/npm/metavuln-calculator/compare/v6.0.0...v6.0.1) (2023-08-14) 81 | 82 | ### Dependencies 83 | 84 | * [`907daf1`](https://github.com/npm/metavuln-calculator/commit/907daf1390e835245cb9f00b9436169964c80876) [#93](https://github.com/npm/metavuln-calculator/pull/93) bump pacote from 15.2.0 to 16.0.0 85 | 86 | ## [6.0.0](https://github.com/npm/metavuln-calculator/compare/v5.0.1...v6.0.0) (2023-08-14) 87 | 88 | ### ⚠️ BREAKING CHANGES 89 | 90 | * support for node 14 has been removed 91 | 92 | ### Bug Fixes 93 | 94 | * [`0e95702`](https://github.com/npm/metavuln-calculator/commit/0e957021b882a930f4fae5653ee0bbaa434018d1) [#94](https://github.com/npm/metavuln-calculator/pull/94) drop node14 support (@lukekarrys) 95 | 96 | ## [5.0.1](https://github.com/npm/metavuln-calculator/compare/v5.0.0...v5.0.1) (2023-04-12) 97 | 98 | ### Bug Fixes 99 | 100 | * [`d89da3f`](https://github.com/npm/metavuln-calculator/commit/d89da3fdeddd3aa8c6255ccf86741dda9dbaed59) [#85](https://github.com/npm/metavuln-calculator/pull/85) support packument without versions (#85) (@vigan-abd) 101 | 102 | ## [5.0.0](https://github.com/npm/metavuln-calculator/compare/v4.0.0...v5.0.0) (2022-10-13) 103 | 104 | ### ⚠️ BREAKING CHANGES 105 | 106 | * this module no longer attempts to change file ownership automatically 107 | 108 | ### Dependencies 109 | 110 | * [`56c1950`](https://github.com/npm/metavuln-calculator/commit/56c19503e3211fbc046d2c7c556f6b5b2ad04e38) [#69](https://github.com/npm/metavuln-calculator/pull/69) bump cacache from 16.1.3 to 17.0.0 (#69) 111 | * [`e31f928`](https://github.com/npm/metavuln-calculator/commit/e31f9284962b165500e9d2aa4a577b954205cc57) [#70](https://github.com/npm/metavuln-calculator/pull/70) bump pacote from 14.0.0 to 15.0.0 (#70) 112 | * [`2154d72`](https://github.com/npm/metavuln-calculator/commit/2154d72b0c881be7c3ca68bd1fe1b89c1f865831) [#67](https://github.com/npm/metavuln-calculator/pull/67) bump json-parse-even-better-errors from 2.3.1 to 3.0.0 113 | 114 | ## [4.0.0](https://github.com/npm/metavuln-calculator/compare/v4.0.0-pre.0...v4.0.0) (2022-10-05) 115 | 116 | ### Dependencies 117 | 118 | * [`cfadd1b`](https://github.com/npm/metavuln-calculator/commit/cfadd1b203b99e364ba24326b3350236268bb3fa) [#63](https://github.com/npm/metavuln-calculator/pull/63) remove pacote@14 prerelease ranges (#63) 119 | 120 | ## [4.0.0-pre.0](https://github.com/npm/metavuln-calculator/compare/v3.1.1...v4.0.0-pre.0) (2022-09-23) 121 | 122 | ### ⚠️ BREAKING CHANGES 123 | 124 | * `@npmcli/metavuln-calculator` is now compatible with the following semver range for node: `^14.17.0 || ^16.13.0 || >=18.0.0` 125 | 126 | ### Features 127 | 128 | * [`a20ebd2`](https://github.com/npm/metavuln-calculator/commit/a20ebd2f3713f7909a8f92e4239bf2ab8dda9756) [#55](https://github.com/npm/metavuln-calculator/pull/55) postinstall for dependabot template-oss PR (@lukekarrys) 129 | 130 | ### Dependencies 131 | 132 | * [`cfb8511`](https://github.com/npm/metavuln-calculator/commit/cfb8511a7ed3cb0b8cdec1617583b098150f87b9) [#57](https://github.com/npm/metavuln-calculator/pull/57) pacote@14||14.pre 133 | 134 | ## [3.1.1](https://github.com/npm/metavuln-calculator/compare/v3.1.0...v3.1.1) (2022-06-29) 135 | 136 | 137 | ### Bug Fixes 138 | 139 | * don't throw on invalid semver versions ([#43](https://github.com/npm/metavuln-calculator/issues/43)) ([7c9f14c](https://github.com/npm/metavuln-calculator/commit/7c9f14cc48037186b76b7e483188a8f7dc9f603f)) 140 | 141 | ## [3.1.0](https://github.com/npm/metavuln-calculator/compare/v3.0.1...v3.1.0) (2022-04-04) 142 | 143 | 144 | ### Features 145 | 146 | * include cwe and cvss ([#34](https://github.com/npm/metavuln-calculator/issues/34)) ([5286f6b](https://github.com/npm/metavuln-calculator/commit/5286f6b9281312628baa8a4ea898da7a0ca2e394)) 147 | 148 | ### [3.0.1](https://www.github.com/npm/metavuln-calculator/compare/v3.0.0...v3.0.1) (2022-03-14) 149 | 150 | 151 | ### Dependencies 152 | 153 | * bump cacache from 15.3.0 to 16.0.0 ([#25](https://www.github.com/npm/metavuln-calculator/issues/25)) ([6493a7e](https://www.github.com/npm/metavuln-calculator/commit/6493a7e5a5e9d28ab44b57f5c33a5e63e959c5b4)) 154 | * update pacote requirement from ^13.0.1 to ^13.0.2 ([#20](https://www.github.com/npm/metavuln-calculator/issues/20)) ([94654bf](https://www.github.com/npm/metavuln-calculator/commit/94654bfcaa754a0065f671f6dc9fd4c0bf2c247f)) 155 | * update pacote requirement from ^13.0.2 to ^13.0.3 ([#23](https://www.github.com/npm/metavuln-calculator/issues/23)) ([5be49ab](https://www.github.com/npm/metavuln-calculator/commit/5be49ab411bc1dc04af16bda801e3de70785e016)) 156 | 157 | ## [3.0.0](https://www.github.com/npm/metavuln-calculator/compare/v2.0.0...v3.0.0) (2022-02-16) 158 | 159 | 160 | ### ⚠ BREAKING CHANGES 161 | 162 | * the options passed directly to pacote now take a `silent` option instead of `log.loglevel` 163 | 164 | ### Dependencies 165 | 166 | * bump pacote from 12.0.3 to 13.0.1 ([#17](https://www.github.com/npm/metavuln-calculator/issues/17)) ([b295177](https://www.github.com/npm/metavuln-calculator/commit/b295177dfa7dbaf68abb58340b4b0e29529be9ee)) 167 | * update cacache requirement from ^15.0.5 to ^15.3.0 ([#15](https://www.github.com/npm/metavuln-calculator/issues/15)) ([1cd2f0a](https://www.github.com/npm/metavuln-calculator/commit/1cd2f0a113a776a981f2046310e13ca6a560e4cf)) 168 | * update semver requirement from ^7.3.2 to ^7.3.5 ([#14](https://www.github.com/npm/metavuln-calculator/issues/14)) ([314da9b](https://www.github.com/npm/metavuln-calculator/commit/314da9b625f1f7e9bb32104dae3727656678224f)) 169 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | <!-- This file is automatically added by @npmcli/template-oss. Do not edit. --> 2 | 3 | All interactions in this repo are covered by the [npm Code of 4 | Conduct](https://docs.npmjs.com/policies/conduct) 5 | 6 | The npm cli team may, at its own discretion, moderate, remove, or edit 7 | any interactions such as pull requests, issues, and comments. 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | <!-- This file is automatically added by @npmcli/template-oss. Do not edit. --> 2 | 3 | # Contributing 4 | 5 | ## Code of Conduct 6 | 7 | All interactions in the **npm** organization on GitHub are considered to be covered by our standard [Code of Conduct](https://docs.npmjs.com/policies/conduct). 8 | 9 | ## Reporting Bugs 10 | 11 | Before submitting a new bug report please search for an existing or similar report. 12 | 13 | Use one of our existing issue templates if you believe you've come across a unique problem. 14 | 15 | Duplicate issues, or issues that don't use one of our templates may get closed without a response. 16 | 17 | ## Pull Request Conventions 18 | 19 | ### Commits 20 | 21 | We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 22 | 23 | When opening a pull request please be sure that either the pull request title, or each commit in the pull request, has one of the following prefixes: 24 | 25 | - `feat`: For when introducing a new feature. The result will be a new semver minor version of the package when it is next published. 26 | - `fix`: For bug fixes. The result will be a new semver patch version of the package when it is next published. 27 | - `docs`: For documentation updates. The result will be a new semver patch version of the package when it is next published. 28 | - `chore`: For changes that do not affect the published module. Often these are changes to tests. The result will be *no* change to the version of the package when it is next published (as the commit does not affect the published version). 29 | 30 | ### Test Coverage 31 | 32 | Pull requests made against this repo will run `npm test` automatically. Please make sure tests pass locally before submitting a PR. 33 | 34 | Every new feature or bug fix should come with a corresponding test or tests that validate the solutions. Testing also reports on code coverage and will fail if code coverage drops. 35 | 36 | ### Linting 37 | 38 | Linting is also done automatically once tests pass. `npm run lintfix` will fix most linting errors automatically. 39 | 40 | Please make sure linting passes before submitting a PR. 41 | 42 | ## What _not_ to contribute? 43 | 44 | ### Dependencies 45 | 46 | It should be noted that our team does not accept third-party dependency updates/PRs. If you submit a PR trying to update our dependencies we will close it with or without a reference to these contribution guidelines. 47 | 48 | ### Tools/Automation 49 | 50 | Our core team is responsible for the maintenance of the tooling/automation in this project and we ask contributors to not make changes to these when contributing (e.g. `.github/*`, `.eslintrc.json`, `.licensee.json`). Most of those files also have a header at the top to remind folks they are automatically generated. Pull requests that alter these will not be accepted. 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) npm, Inc. 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @npmcli/metavuln-calculator 2 | 3 | Calculate meta-vulnerabilities from package security advisories 4 | 5 | This is a pretty low-level package to abstract out the parts of 6 | [@npmcli/arborist](http://npm.im/@npmcli/arborist) that calculate 7 | metavulnerabilities from security advisories. If you just want to get an 8 | audit for a package tree, probably what you want to use is 9 | `arborist.audit()`. 10 | 11 | ## USAGE 12 | 13 | ```js 14 | const Calculator = require('@npmcli/metavuln-calculator') 15 | // pass in any options for cacache and pacote 16 | // see those modules for option descriptions 17 | const calculator = new Calculator(options) 18 | 19 | // get an advisory somehow, typically by POSTing a JSON payload like: 20 | // {"pkgname":["1.2.3","4.3.5", ...versions], ...packages} 21 | // to /-/npm/v1/security/advisories/bulk 22 | // to get a payload response like: 23 | // { 24 | // "semver": [ 25 | // { 26 | // "id": 31, 27 | // "url": "https://npmjs.com/advisories/31", 28 | // "title": "Regular Expression Denial of Service", 29 | // "severity": "moderate", 30 | // "vulnerable_versions": "<4.3.2" 31 | // } 32 | // ], 33 | // ...advisories 34 | // } 35 | const arb = new Aborist(options) 36 | const tree = await arb.loadActual() 37 | const advisories = await getBulkAdvisoryReportSomehow(tree) 38 | 39 | // then to get a comprehensive set of advisories including metavulns: 40 | const set = new Set() 41 | for (const [name, advisory] of Object.entries(advisories)) { 42 | // make sure we have the advisories loaded with latest version lists 43 | set.add(await calculator.calculate(name, {advisory})) 44 | } 45 | 46 | for (const vuln of set) { 47 | for (const node of tree.inventory.query('name', vuln.name)) { 48 | // not vulnerable, just keep looking 49 | if (!vuln.testVersion(node.version)) 50 | continue 51 | for (const { from: dep, spec } of node.edgesIn) { 52 | const metaAdvisory = await calculator.calculate(dep.name, vuln) 53 | if (metaAdvisory.testVersion(dep.version, spec)) { 54 | set.add(metaAdvisory) 55 | } 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | ## API 62 | 63 | ### Class: Advisory 64 | 65 | The `Calculator.calculate` method returns a Promise that resolves to a 66 | `Advisory` object, filled in from the cache and updated if necessary with 67 | the available advisory data. 68 | 69 | Do not instantiate `Advisory` objects directly. Use the `calculate()` 70 | method to get one with appropriate data filled in. 71 | 72 | Do not mutate `Advisory` objects. Use the supplied methods only. 73 | 74 | #### Fields 75 | 76 | - `name` The name of the package that this vulnerability is about 77 | - `id` The unique cache key for this vuln or metavuln. (See **Cache Keys** 78 | below.) 79 | - `dependency` For metavulns, the dependency that causes this package to be 80 | have a vulnerability. For advisories, the same as `name`. 81 | - `type` Either `'advisory'` or `'metavuln'`, depending on the type of 82 | vulnerability that this object represents. 83 | - `url` The url for the advisory (`null` for metavulns) 84 | - `title` The text title of the advisory or metavuln 85 | - `severity` The severity level info/low/medium/high/critical 86 | - `range` The range that is vulnerable 87 | - `versions` The set of available versions of the package 88 | - `vulnerableVersions` The set of versions that are vulnerable 89 | - `source` The numeric ID of the advisory, or the cache key of the 90 | vulnerability that causes this metavuln 91 | - `updated` Boolean indicating whether this vulnerability was updated since 92 | being read from cache. 93 | - `packument` The packument object for the package that this vulnerability 94 | is about. 95 | 96 | #### `vuln.testVersion(version, [dependencySpecifier]) -> Boolean` 97 | 98 | Check to see if a given version is vulnerable. Returns `true` if the 99 | version is vulnerable, and should be avoided. 100 | 101 | For metavulns, `dependencySpecifier` indicates the version range of the 102 | source of the vulnerability, which the module depends on. If not provided, 103 | will attempt to read from the packument. If not provided, and unable to 104 | read from the packument, then `true` is returned, indicating that the (not 105 | installable) package version should be avoided. 106 | 107 | #### Cache Keys 108 | 109 | The cache keys are calculated by hashing together the `source` and `name` 110 | fields, prefixing with the string `'security-advisory:'` and the name of 111 | the dependency that is vulnerable. 112 | 113 | So, a third-level metavulnerability might have a key like: 114 | 115 | ``` 116 | 'security-advisory:foo:'+ hash(['foo', hash(['bar', hash(['baz', 123])])]) 117 | ``` 118 | 119 | Thus, the cached entry with this key would reflect the version of `foo` 120 | that is vulnerable by virtue of dependending exclusively on versions of 121 | `bar` which are vulnerable by virtue of depending exclusively on versions 122 | of `baz` which are vulnerable by virtue of advisory ID `123`. 123 | 124 | Loading advisory data entirely from cache without hitting an npm registry 125 | security advisory endpoint is not supported at this time, but technically 126 | possible, and likely to come in a future version of this library. 127 | 128 | ### `calculator = new Calculator(options)` 129 | 130 | Options object is used for `cacache` and `pacote` calls. 131 | 132 | ### `calculator.calculate(name, source)` 133 | 134 | - `name` The name of the package that the advisory is about 135 | - `source` Advisory object from the npm security endpoint, or a `Advisory` 136 | object returned by a previous call to the `calculate()` method. 137 | "Advisory" objects need to have: 138 | - `id` id of the advisory or Advisory object 139 | - `vulnerable_versions` range of versions affected 140 | - `url` 141 | - `title` 142 | - `severity` 143 | 144 | Fetches the packument and returns a Promise that resolves to a 145 | vulnerability object described above. 146 | 147 | Will perform required I/O to fetch package metadata from registry and 148 | read from cache. Advisory information written back to cache. 149 | 150 | ## Dependent Version Sampling 151 | 152 | Typically, dependency ranges don't change very frequently, and the most 153 | recent version published on a given release line is most likely to contain 154 | the fix for a given vulnerability. 155 | 156 | So, we see things like this: 157 | 158 | ``` 159 | 3.0.4 - not vulnerable 160 | 3.0.3 - vulnerable 161 | 3.0.2 - vulnerable 162 | 3.0.1 - vulnerable 163 | 3.0.0 - vulnerable 164 | 2.3.107 - not vulnerable 165 | 2.3.106 - not vulnerable 166 | 2.3.105 - vulnerable 167 | ... 523 more vulnerable versions ... 168 | 2.0.0 - vulnerable 169 | 1.1.102 - not vulnerable 170 | 1.1.101 - vulnerable 171 | ... 387 more vulnerable versions ... 172 | 0.0.0 - vulnerable 173 | ``` 174 | 175 | In order to determine which versions of a package are affected by a 176 | vulnerability in a dependency, this module uses the following algorithm to 177 | minimize the number of tests required by performing a binary search on each 178 | version set, and presuming that versions _between_ vulnerable versions 179 | within a given set are also vulnerable. 180 | 181 | 1. Sort list of available versions by SemVer precedence 182 | 2. Group versions into sets based on MAJOR/MINOR versions. 183 | 184 | 3.0.0 - 3.0.4 185 | 2.3.0 - 2.3.107 186 | 2.2.0 - 2.2.43 187 | 2.1.0 - 2.1.432 188 | 2.0.0 - 2.0.102 189 | 1.1.0 - 1.1.102 190 | 1.0.0 - 1.0.157 191 | 0.1.0 - 0.1.123 192 | 0.0.0 - 0.0.57 193 | 194 | 3. Test the highest and lowest in each MAJOR/MINOR set, and mark highest 195 | and lowest with known-vulnerable status. (`(s)` means "safe" and `(v)` 196 | means "vulnerable".) 197 | 198 | 3.0.0(v) - 3.0.4(s) 199 | 2.3.0(v) - 2.3.107(s) 200 | 2.2.0(v) - 2.2.43(v) 201 | 2.1.0(v) - 2.1.432(v) 202 | 2.0.0(v) - 2.0.102(v) 203 | 1.1.0(v) - 1.1.102(s) 204 | 1.0.0(v) - 1.0.157(v) 205 | 0.1.0(v) - 0.1.123(v) 206 | 0.0.0(v) - 0.0.57(v) 207 | 208 | 4. For each set of package versions: 209 | 210 | 1. If highest and lowest both vulnerable, assume entire set is 211 | vulnerable, and continue to next set. Ie, in the example, throw out 212 | the following version sets: 213 | 214 | 2.2.0(v) - 2.2.43(v) 215 | 2.1.0(v) - 2.1.432(v) 216 | 2.0.0(v) - 2.0.102(v) 217 | 1.0.0(v) - 1.0.157(v) 218 | 0.1.0(v) - 0.1.123(v) 219 | 0.0.0(v) - 0.0.57(v) 220 | 221 | 2. Test middle version MID in set, splitting into two sets. 222 | 223 | 3.0.0(v) - 3.0.2(v) - 3.0.4(s) 224 | 2.3.0(v) - 2.3.54(v) - 2.3.107(s) 225 | 1.1.0(v) - 1.1.51(v) - 1.1.102(s) 226 | 227 | 3. If any untested versions in Set(mid..highest) or Set(lowest..mid), 228 | add to list of sets to test. 229 | 230 | 3.0.0(v) - 3.0.2(v) <-- thrown out on next iteration 231 | 3.0.2(v) - 3.0.4(s) 232 | 2.3.0(v) - 2.3.54(v) <-- thrown out on next iteration 233 | 2.3.54(v) - 2.3.107(s) 234 | 1.1.0(v) - 1.1.51(v) <-- thrown out on next iteration 235 | 1.1.51(v) - 1.1.102(s) 236 | 237 | When the process finishes, all versions are either confirmed safe, or 238 | confirmed/assumed vulnerable, and we avoid checking large sets of versions 239 | where vulnerabilities went unfixed. 240 | 241 | ### Testing Version for MetaVuln Status 242 | 243 | When the dependency is in `bundleDependencies`, we treat any dependent 244 | version that _may_ be vulnerable as a vulnerability. If the dependency is 245 | not in `bundleDependencies`, then we treat the dependent module as a 246 | vulnerability if it can _only_ resolve to dependency versions that are 247 | vulnerable. 248 | 249 | This relies on the reasonable assumption that the version of a bundled 250 | dependency will be within the stated dependency range, and accounts for the 251 | fact that we can't know ahead of time which version of a dependency may be 252 | bundled. So, we avoid versions that _may_ bundle a vulnerable dependency. 253 | 254 | For example: 255 | 256 | Package `foo` depends on package `bar` at the following version ranges: 257 | 258 | ``` 259 | foo version bar version range 260 | 1.0.0 ^1.2.3 261 | 1.0.1 ^1.2.4 262 | 1.0.2 ^1.2.5 263 | 1.1.0 ^1.3.1 264 | 1.1.1 ^1.3.2 265 | 1.1.2 ^1.3.3 266 | 2.0.0 ^2.0.0 267 | 2.0.1 ^2.0.1 268 | 2.0.2 ^2.0.2 269 | ``` 270 | 271 | There is an advisory for `bar@1.2.4 - 1.3.2`. So: 272 | 273 | ``` 274 | foo version vulnerable? 275 | 1.0.0 if bundled (can use 1.2.3, which is not vulnerable) 276 | 1.0.1 yes (must use ^1.2.4, entirely contained in vuln range) 277 | 1.0.2 yes (must use ^1.2.5, entirely contained in vuln range) 278 | 1.1.0 if bundled (can use 1.3.3, which is not vulnerable) 279 | 1.1.1 if bundled (can use 1.3.3, which is not vulnerable) 280 | 1.1.2 no (dep is outside of vuln range) 281 | 2.0.0 no (dep is outside of vuln range) 282 | 2.0.1 no (dep is outside of vuln range) 283 | 2.0.2 no (dep is outside of vuln range) 284 | ``` 285 | 286 | To test a package version for metaVulnerable status, we attempt to load the 287 | manifest of the dependency, using the vulnerable version set as the `avoid` 288 | versions. If we end up selecting a version that should be avoided, then 289 | that means that the package is vulnerable by virtue of its dependency. 290 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | <!-- This file is automatically added by @npmcli/template-oss. Do not edit. --> 2 | 3 | GitHub takes the security of our software products and services seriously, including the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 4 | 5 | If you believe you have found a security vulnerability in this GitHub-owned open source repository, you can report it to us in one of two ways. 6 | 7 | If the vulnerability you have found is *not* [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) or if you do not wish to be considered for a bounty reward, please report the issue to us directly through [opensource-security@github.com](mailto:opensource-security@github.com). 8 | 9 | If the vulnerability you have found is [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) and you would like for your finding to be considered for a bounty reward, please submit the vulnerability to us through [HackerOne](https://hackerone.com/github) in order to be eligible to receive a bounty award. 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 12 | 13 | Thanks for helping make GitHub safe for everyone. 14 | -------------------------------------------------------------------------------- /lib/advisory.js: -------------------------------------------------------------------------------- 1 | const hash = require('./hash.js') 2 | const semver = require('semver') 3 | const semverOpt = { includePrerelease: true, loose: true } 4 | const getDepSpec = require('./get-dep-spec.js') 5 | 6 | // any fields that we don't want in the cache need to be hidden 7 | const _source = Symbol('source') 8 | const _packument = Symbol('packument') 9 | const _versionVulnMemo = Symbol('versionVulnMemo') 10 | const _updated = Symbol('updated') 11 | const _options = Symbol('options') 12 | const _specVulnMemo = Symbol('specVulnMemo') 13 | const _testVersion = Symbol('testVersion') 14 | const _testVersions = Symbol('testVersions') 15 | const _calculateRange = Symbol('calculateRange') 16 | const _markVulnerable = Symbol('markVulnerable') 17 | const _testSpec = Symbol('testSpec') 18 | 19 | class Advisory { 20 | constructor (name, source, options = {}) { 21 | this.source = source.id 22 | this[_source] = source 23 | this[_options] = options 24 | this.name = name 25 | if (!source.name) { 26 | source.name = name 27 | } 28 | 29 | this.dependency = source.name 30 | 31 | if (this.type === 'advisory') { 32 | this.title = source.title 33 | this.url = source.url 34 | } else { 35 | this.title = `Depends on vulnerable versions of ${source.name}` 36 | this.url = null 37 | } 38 | 39 | this.severity = source.severity || 'high' 40 | this.versions = [] 41 | this.vulnerableVersions = [] 42 | this.cwe = source.cwe 43 | this.cvss = source.cvss 44 | 45 | // advisories have the range, metavulns do not 46 | // if an advisory doesn't specify range, assume all are vulnerable 47 | this.range = this.type === 'advisory' ? source.vulnerable_versions || '*' 48 | : null 49 | 50 | this.id = hash(this) 51 | 52 | this[_packument] = null 53 | // memoized list of which versions are vulnerable 54 | this[_versionVulnMemo] = new Map() 55 | // memoized list of which dependency specs are vulnerable 56 | this[_specVulnMemo] = new Map() 57 | this[_updated] = false 58 | } 59 | 60 | // true if we updated from what we had in cache 61 | get updated () { 62 | return this[_updated] 63 | } 64 | 65 | get type () { 66 | return this.dependency === this.name ? 'advisory' : 'metavuln' 67 | } 68 | 69 | get packument () { 70 | return this[_packument] 71 | } 72 | 73 | // load up the data from a cache entry and a fetched packument 74 | load (cached, packument) { 75 | // basic data integrity gutcheck 76 | if (!cached || typeof cached !== 'object') { 77 | throw new TypeError('invalid cached data, expected object') 78 | } 79 | 80 | if (!packument || typeof packument !== 'object') { 81 | throw new TypeError('invalid packument data, expected object') 82 | } 83 | 84 | if (cached.id && cached.id !== this.id) { 85 | throw Object.assign(new Error('loading from incorrect cache entry'), { 86 | expected: this.id, 87 | actual: cached.id, 88 | }) 89 | } 90 | if (packument.name !== this.name) { 91 | throw Object.assign(new Error('loading from incorrect packument'), { 92 | expected: this.name, 93 | actual: packument.name, 94 | }) 95 | } 96 | if (this[_packument]) { 97 | throw new Error('advisory object already loaded') 98 | } 99 | 100 | // if we have a range from the initialization, and the cached 101 | // data has a *different* range, then we know we have to recalc. 102 | // just don't use the cached data, so we will definitely not match later 103 | if (!this.range || cached.range && cached.range === this.range) { 104 | Object.assign(this, cached) 105 | } 106 | 107 | this[_packument] = packument 108 | 109 | const pakuVersions = Object.keys(packument.versions || {}) 110 | const allVersions = new Set([...pakuVersions, ...this.versions]) 111 | const versionsAdded = [] 112 | const versionsRemoved = [] 113 | for (const v of allVersions) { 114 | if (!this.versions.includes(v)) { 115 | versionsAdded.push(v) 116 | this.versions.push(v) 117 | } else if (!pakuVersions.includes(v)) { 118 | versionsRemoved.push(v) 119 | } 120 | } 121 | 122 | // strip out any removed versions from our lists, and sort by semver 123 | this.versions = semver.sort(this.versions.filter(v => 124 | !versionsRemoved.includes(v)), semverOpt) 125 | 126 | // if no changes, then just return what we got from cache 127 | // versions added or removed always means we changed 128 | // otherwise, advisories change if the range changes, and 129 | // metavulns change if the source was updated 130 | const unchanged = this.type === 'advisory' 131 | ? this.range && this.range === cached.range 132 | : !this[_source].updated 133 | 134 | // if the underlying source changed, by an advisory updating the 135 | // range, or a source advisory being updated, then we have to re-check 136 | // otherwise, only recheck the new ones. 137 | this.vulnerableVersions = !unchanged ? [] 138 | : semver.sort(this.vulnerableVersions.filter(v => 139 | !versionsRemoved.includes(v)), semverOpt) 140 | 141 | if (unchanged && !versionsAdded.length && !versionsRemoved.length) { 142 | // nothing added or removed, nothing to do here. use the cached copy. 143 | return this 144 | } 145 | 146 | this[_updated] = true 147 | 148 | // test any versions newly added 149 | if (!unchanged || versionsAdded.length) { 150 | this[_testVersions](unchanged ? versionsAdded : this.versions) 151 | } 152 | this.vulnerableVersions = semver.sort(this.vulnerableVersions, semverOpt) 153 | 154 | // metavulns have to calculate their range, since cache is invalidated 155 | // advisories just get their range from the advisory above 156 | if (this.type === 'metavuln') { 157 | this[_calculateRange]() 158 | } 159 | 160 | return this 161 | } 162 | 163 | [_calculateRange] () { 164 | // calling semver.simplifyRange with a massive list of versions, and those 165 | // versions all concatenated with `||` is a geometric CPU explosion! 166 | // we can try to be a *little* smarter up front by doing x-y for all 167 | // contiguous version sets in the list 168 | const ranges = [] 169 | this.versions = semver.sort(this.versions, semverOpt) 170 | this.vulnerableVersions = semver.sort(this.vulnerableVersions, semverOpt) 171 | for (let v = 0, vulnVer = 0; v < this.versions.length; v++) { 172 | // figure out the vulnerable subrange 173 | const vr = [this.versions[v]] 174 | while (v < this.versions.length) { 175 | if (this.versions[v] !== this.vulnerableVersions[vulnVer]) { 176 | // we don't test prerelease versions, so just skip past it 177 | if (/-/.test(this.versions[v])) { 178 | v++ 179 | continue 180 | } 181 | break 182 | } 183 | if (vr.length > 1) { 184 | vr[1] = this.versions[v] 185 | } else { 186 | vr.push(this.versions[v]) 187 | } 188 | v++ 189 | vulnVer++ 190 | } 191 | // it'll either be just the first version, which means no overlap, 192 | // or the start and end versions, which might be the same version 193 | if (vr.length > 1) { 194 | const tail = this.versions[this.versions.length - 1] 195 | ranges.push(vr[1] === tail ? `>=${vr[0]}` 196 | : vr[0] === vr[1] ? vr[0] 197 | : vr.join(' - ')) 198 | } 199 | } 200 | const metavuln = ranges.join(' || ').trim() 201 | this.range = !metavuln ? '<0.0.0-0' 202 | : semver.simplifyRange(this.versions, metavuln, semverOpt) 203 | } 204 | 205 | // returns true if marked as vulnerable, false if ok 206 | // spec is a dependency specifier, for metavuln cases 207 | // where the version might not be in the packument. if 208 | // we have the packument and spec is not provided, then 209 | // we use the dependency version from the manifest. 210 | testVersion (version, spec = null) { 211 | const sv = String(version) 212 | if (this[_versionVulnMemo].has(sv)) { 213 | return this[_versionVulnMemo].get(sv) 214 | } 215 | 216 | const result = this[_testVersion](version, spec) 217 | if (result) { 218 | this[_markVulnerable](version) 219 | } 220 | this[_versionVulnMemo].set(sv, !!result) 221 | return result 222 | } 223 | 224 | [_markVulnerable] (version) { 225 | const sv = String(version) 226 | if (!this.vulnerableVersions.includes(sv)) { 227 | this.vulnerableVersions.push(sv) 228 | } 229 | } 230 | 231 | [_testVersion] (version, spec) { 232 | const sv = String(version) 233 | if (this.vulnerableVersions.includes(sv)) { 234 | return true 235 | } 236 | 237 | if (this.type === 'advisory') { 238 | // advisory, just test range 239 | return semver.satisfies(version, this.range, semverOpt) 240 | } 241 | 242 | // check the dependency of this version on the vulnerable dep 243 | // if we got a version that's not in the packument, fall back on 244 | // the spec provided, if possible. 245 | const mani = this[_packument]?.versions?.[version] || { 246 | dependencies: { 247 | [this.dependency]: spec, 248 | }, 249 | } 250 | 251 | if (!spec) { 252 | spec = getDepSpec(mani, this.dependency) 253 | } 254 | 255 | // no dep, no vuln 256 | if (spec === null) { 257 | return false 258 | } 259 | 260 | if (!semver.validRange(spec, semverOpt)) { 261 | // not a semver range, nothing we can hope to do about it 262 | return true 263 | } 264 | 265 | const bd = mani.bundleDependencies 266 | const bundled = bd && bd.includes(this[_source].name) 267 | // XXX if bundled, then semver.intersects() means vulnerable 268 | // else, pick a manifest and see if it can't be avoided 269 | // try to pick a version of the dep that isn't vulnerable 270 | const avoid = this[_source].range 271 | 272 | if (bundled) { 273 | return semver.intersects(spec, avoid, semverOpt) 274 | } 275 | 276 | return this[_source].testSpec(spec) 277 | } 278 | 279 | testSpec (spec) { 280 | // testing all the versions is a bit costly, and the spec tends to stay 281 | // consistent across multiple versions, so memoize this as well, in case 282 | // we're testing lots of versions. 283 | const memo = this[_specVulnMemo] 284 | if (memo.has(spec)) { 285 | return memo.get(spec) 286 | } 287 | 288 | const res = this[_testSpec](spec) 289 | memo.set(spec, res) 290 | return res 291 | } 292 | 293 | [_testSpec] (spec) { 294 | for (const v of this.versions) { 295 | const satisfies = semver.satisfies(v, spec) 296 | if (!satisfies) { 297 | continue 298 | } 299 | if (!this.testVersion(v)) { 300 | return false 301 | } 302 | } 303 | // either vulnerable, or not installable because nothing satisfied 304 | // either way, best avoided. 305 | return true 306 | } 307 | 308 | [_testVersions] (versions) { 309 | if (!versions.length) { 310 | return 311 | } 312 | 313 | // set of lists of versions 314 | const versionSets = new Set() 315 | versions = semver.sort(versions.map(v => semver.parse(v, semverOpt))) 316 | 317 | // start out with the versions grouped by major and minor 318 | let last = versions[0].major + '.' + versions[0].minor 319 | let list = [] 320 | versionSets.add(list) 321 | for (const v of versions) { 322 | const k = v.major + '.' + v.minor 323 | if (k !== last) { 324 | last = k 325 | list = [] 326 | versionSets.add(list) 327 | } 328 | list.push(v) 329 | } 330 | 331 | for (const set of versionSets) { 332 | // it's common to have version lists like: 333 | // 1.0.0 334 | // 1.0.1-alpha.0 335 | // 1.0.1-alpha.1 336 | // ... 337 | // 1.0.1-alpha.999 338 | // 1.0.1 339 | // 1.0.2-alpha.0 340 | // ... 341 | // 1.0.2-alpha.99 342 | // 1.0.2 343 | // with a huge number of prerelease versions that are not installable 344 | // anyway. 345 | // If mid has a prerelease tag, and set[0] does not, then walk it 346 | // back until we hit a non-prerelease version 347 | // If mid has a prerelease tag, and set[set.length-1] does not, 348 | // then walk it forward until we hit a version without a prerelease tag 349 | // Similarly, if the head/tail is a prerelease, but there is a non-pr 350 | // version in the set, then start there instead. 351 | let h = 0 352 | const origHeadVuln = this.testVersion(set[h]) 353 | while (h < set.length && /-/.test(String(set[h]))) { 354 | h++ 355 | } 356 | 357 | // don't filter out the whole list! they might all be pr's 358 | if (h === set.length) { 359 | h = 0 360 | } else if (origHeadVuln) { 361 | // if the original was vulnerable, assume so are all of these 362 | for (let hh = 0; hh < h; hh++) { 363 | this[_markVulnerable](set[hh]) 364 | } 365 | } 366 | 367 | let t = set.length - 1 368 | const origTailVuln = this.testVersion(set[t]) 369 | while (t > h && /-/.test(String(set[t]))) { 370 | t-- 371 | } 372 | 373 | // don't filter out the whole list! might all be pr's 374 | if (t === h) { 375 | t = set.length - 1 376 | } else if (origTailVuln) { 377 | // if original tail was vulnerable, assume these are as well 378 | for (let tt = set.length - 1; tt > t; tt--) { 379 | this[_markVulnerable](set[tt]) 380 | } 381 | } 382 | 383 | const headVuln = h === 0 ? origHeadVuln 384 | : this.testVersion(set[h]) 385 | 386 | const tailVuln = t === set.length - 1 ? origTailVuln 387 | : this.testVersion(set[t]) 388 | 389 | // if head and tail both vulnerable, whole list is thrown out 390 | if (headVuln && tailVuln) { 391 | for (let v = h; v < t; v++) { 392 | this[_markVulnerable](set[v]) 393 | } 394 | continue 395 | } 396 | 397 | // if length is 2 or 1, then we marked them all already 398 | if (t < h + 2) { 399 | continue 400 | } 401 | 402 | const mid = Math.floor(set.length / 2) 403 | const pre = set.slice(0, mid) 404 | const post = set.slice(mid) 405 | 406 | // if the parent list wasn't prereleases, then drop pr tags 407 | // from end of the pre list, and beginning of the post list, 408 | // marking as vulnerable if the midpoint item we picked is. 409 | if (!/-/.test(String(pre[0]))) { 410 | const midVuln = this.testVersion(pre[pre.length - 1]) 411 | while (/-/.test(String(pre[pre.length - 1]))) { 412 | const v = pre.pop() 413 | if (midVuln) { 414 | this[_markVulnerable](v) 415 | } 416 | } 417 | } 418 | 419 | if (!/-/.test(String(post[post.length - 1]))) { 420 | const midVuln = this.testVersion(post[0]) 421 | while (/-/.test(String(post[0]))) { 422 | const v = post.shift() 423 | if (midVuln) { 424 | this[_markVulnerable](v) 425 | } 426 | } 427 | } 428 | 429 | versionSets.add(pre) 430 | versionSets.add(post) 431 | } 432 | } 433 | } 434 | 435 | module.exports = Advisory 436 | -------------------------------------------------------------------------------- /lib/get-dep-spec.js: -------------------------------------------------------------------------------- 1 | module.exports = (mani, name) => { 2 | // skip dev because that only matters at the root, 3 | // where we aren't fetching a manifest from the registry 4 | // with multiple versions anyway. 5 | const { 6 | dependencies: deps = {}, 7 | optionalDependencies: optDeps = {}, 8 | peerDependencies: peerDeps = {}, 9 | } = mani 10 | 11 | return deps && typeof deps[name] === 'string' ? deps[name] 12 | : optDeps && typeof optDeps[name] === 'string' ? optDeps[name] 13 | : peerDeps && typeof peerDeps[name] === 'string' ? peerDeps[name] 14 | : null 15 | } 16 | -------------------------------------------------------------------------------- /lib/hash.js: -------------------------------------------------------------------------------- 1 | const { createHash } = require('crypto') 2 | 3 | module.exports = ({ name, source }) => createHash('sha512') 4 | .update(JSON.stringify([name, source])) 5 | .digest('base64') 6 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | // this is the public class that is used by consumers. 2 | // the Advisory class handles all the calculation, and this 3 | // class handles all the IO with the registry and cache. 4 | const pacote = require('pacote') 5 | const cacache = require('cacache') 6 | const { time } = require('proc-log') 7 | const Advisory = require('./advisory.js') 8 | const { homedir } = require('os') 9 | const jsonParse = require('json-parse-even-better-errors') 10 | 11 | const _packument = Symbol('packument') 12 | const _cachePut = Symbol('cachePut') 13 | const _cacheGet = Symbol('cacheGet') 14 | const _cacheData = Symbol('cacheData') 15 | const _packuments = Symbol('packuments') 16 | const _cache = Symbol('cache') 17 | const _options = Symbol('options') 18 | const _advisories = Symbol('advisories') 19 | const _calculate = Symbol('calculate') 20 | 21 | class Calculator { 22 | constructor (options = {}) { 23 | this[_options] = { ...options } 24 | this[_cache] = this[_options].cache || (homedir() + '/.npm/_cacache') 25 | this[_options].cache = this[_cache] 26 | this[_packuments] = new Map() 27 | this[_cacheData] = new Map() 28 | this[_advisories] = new Map() 29 | } 30 | 31 | get cache () { 32 | return this[_cache] 33 | } 34 | 35 | get options () { 36 | return { ...this[_options] } 37 | } 38 | 39 | async calculate (name, source) { 40 | const k = `security-advisory:${name}:${source.id}` 41 | if (this[_advisories].has(k)) { 42 | return this[_advisories].get(k) 43 | } 44 | 45 | const p = this[_calculate](name, source) 46 | this[_advisories].set(k, p) 47 | return p 48 | } 49 | 50 | async [_calculate] (name, source) { 51 | const k = `security-advisory:${name}:${source.id}` 52 | const timeEnd = time.start(`metavuln:calculate:${k}`) 53 | const advisory = new Advisory(name, source, this[_options]) 54 | // load packument and cached advisory 55 | const [cached, packument] = await Promise.all([ 56 | this[_cacheGet](advisory), 57 | this[_packument](name), 58 | ]) 59 | const timeEndLoad = time.start(`metavuln:load:${k}`) 60 | advisory.load(cached, packument) 61 | timeEndLoad() 62 | if (advisory.updated) { 63 | await this[_cachePut](advisory) 64 | } 65 | this[_advisories].set(k, advisory) 66 | timeEnd() 67 | return advisory 68 | } 69 | 70 | async [_cachePut] (advisory) { 71 | const { name, id } = advisory 72 | const key = `security-advisory:${name}:${id}` 73 | const timeEnd = time.start(`metavuln:cache:put:${key}`) 74 | const data = JSON.stringify(advisory) 75 | const options = { ...this[_options] } 76 | this[_cacheData].set(key, jsonParse(data)) 77 | await cacache.put(this[_cache], key, data, options).catch(() => {}) 78 | timeEnd() 79 | } 80 | 81 | async [_cacheGet] (advisory) { 82 | const { name, id } = advisory 83 | const key = `security-advisory:${name}:${id}` 84 | /* istanbul ignore if - should be impossible, since we memoize the 85 | * advisory object itself using the same key, just being cautious */ 86 | if (this[_cacheData].has(key)) { 87 | return this[_cacheData].get(key) 88 | } 89 | 90 | const timeEnd = time.start(`metavuln:cache:get:${key}`) 91 | const p = cacache.get(this[_cache], key, { ...this[_options] }) 92 | .catch(() => ({ data: '{}' })) 93 | .then(({ data }) => { 94 | data = jsonParse(data) 95 | timeEnd() 96 | this[_cacheData].set(key, data) 97 | return data 98 | }) 99 | this[_cacheData].set(key, p) 100 | return p 101 | } 102 | 103 | async [_packument] (name) { 104 | if (this[_packuments].has(name)) { 105 | return this[_packuments].get(name) 106 | } 107 | 108 | const timeEnd = time.start(`metavuln:packument:${name}`) 109 | const p = pacote.packument(name, { ...this[_options] }) 110 | .catch(() => { 111 | // presumably not something from the registry. 112 | // an empty packument will have an effective range of * 113 | return { 114 | name, 115 | versions: {}, 116 | } 117 | }) 118 | .then(paku => { 119 | timeEnd() 120 | this[_packuments].set(name, paku) 121 | return paku 122 | }) 123 | this[_packuments].set(name, p) 124 | return p 125 | } 126 | } 127 | 128 | module.exports = Calculator 129 | -------------------------------------------------------------------------------- /map.js: -------------------------------------------------------------------------------- 1 | module.exports = t => t.replace(/^test/, 'lib') 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@npmcli/metavuln-calculator", 3 | "version": "9.0.0", 4 | "main": "lib/index.js", 5 | "files": [ 6 | "bin/", 7 | "lib/" 8 | ], 9 | "description": "Calculate meta-vulnerabilities from package security advisories", 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/npm/metavuln-calculator.git" 13 | }, 14 | "author": "GitHub Inc.", 15 | "license": "ISC", 16 | "scripts": { 17 | "test": "tap", 18 | "posttest": "npm run lint", 19 | "snap": "tap", 20 | "postsnap": "npm run lint", 21 | "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"", 22 | "lint": "npm run eslint", 23 | "lintfix": "npm run eslint -- --fix", 24 | "postlint": "template-oss-check", 25 | "template-oss-apply": "template-oss-apply --force" 26 | }, 27 | "tap": { 28 | "check-coverage": true, 29 | "coverage-map": "map.js", 30 | "nyc-arg": [ 31 | "--exclude", 32 | "tap-snapshots/**" 33 | ] 34 | }, 35 | "devDependencies": { 36 | "@npmcli/eslint-config": "^5.0.0", 37 | "@npmcli/template-oss": "4.24.3", 38 | "require-inject": "^1.4.4", 39 | "tap": "^16.0.1" 40 | }, 41 | "dependencies": { 42 | "cacache": "^19.0.0", 43 | "json-parse-even-better-errors": "^4.0.0", 44 | "pacote": "^21.0.0", 45 | "proc-log": "^5.0.0", 46 | "semver": "^7.3.5" 47 | }, 48 | "engines": { 49 | "node": "^20.17.0 || >=22.9.0" 50 | }, 51 | "templateOSS": { 52 | "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", 53 | "version": "4.24.3", 54 | "publish": "true", 55 | "ciVersions": [ 56 | "16.14.0", 57 | "16.x", 58 | "18.0.0", 59 | "18.x" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "group-pull-request-title-pattern": "chore: release ${version}", 3 | "pull-request-title-pattern": "chore: release${component} ${version}", 4 | "changelog-sections": [ 5 | { 6 | "type": "feat", 7 | "section": "Features", 8 | "hidden": false 9 | }, 10 | { 11 | "type": "fix", 12 | "section": "Bug Fixes", 13 | "hidden": false 14 | }, 15 | { 16 | "type": "docs", 17 | "section": "Documentation", 18 | "hidden": false 19 | }, 20 | { 21 | "type": "deps", 22 | "section": "Dependencies", 23 | "hidden": false 24 | }, 25 | { 26 | "type": "chore", 27 | "section": "Chores", 28 | "hidden": true 29 | } 30 | ], 31 | "packages": { 32 | ".": { 33 | "package-name": "", 34 | "prerelease": false 35 | } 36 | }, 37 | "prerelease-type": "pre.0" 38 | } 39 | -------------------------------------------------------------------------------- /test/advisory.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | 3 | const t = require('tap') 4 | const Advisory = require('../lib/advisory.js') 5 | const advisories = require('./fixtures/advisories/index.js') 6 | const packuments = require('./fixtures/packuments/index.js') 7 | const semver = require('semver') 8 | const so = { includePrerelease: true, loose: true } 9 | 10 | t.test('create vulns from advisory', t => { 11 | const v = new Advisory('semver', advisories.semver) 12 | t.match(v, { 13 | constructor: Advisory, 14 | source: 31, 15 | name: 'semver', 16 | title: 'Regular Expression Denial of Service', 17 | severity: 'moderate', 18 | versions: [], 19 | vulnerableVersions: [], 20 | url: 'https://npmjs.com/advisories/31', 21 | range: '<4.3.2', 22 | id: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 23 | dependency: 'semver', 24 | updated: false, 25 | }, 'from raw advisory') 26 | 27 | // load without a cache entry 28 | v.load({}, packuments.semver) 29 | t.match(v, { 30 | constructor: Advisory, 31 | source: 31, 32 | name: 'semver', 33 | title: 'Regular Expression Denial of Service', 34 | severity: 'moderate', 35 | versions: semver.sort(Object.keys(packuments.semver.versions), so), 36 | vulnerableVersions: semver.sort(Object.keys(packuments.semver.versions).filter(vulnerable => 37 | semver.satisfies(vulnerable, '<4.3.2', so)), so), 38 | url: 'https://npmjs.com/advisories/31', 39 | range: '<4.3.2', 40 | id: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 41 | dependency: 'semver', 42 | updated: true, 43 | packument: packuments.semver, 44 | }, 'updated from what was in the cache') 45 | 46 | const cached = JSON.parse(JSON.stringify(v)) 47 | t.match(cached, v, 'cached copy matches the advisory') 48 | 49 | const vFromCache = new Advisory('semver', advisories.semver) 50 | vFromCache.load(cached, packuments.semver) 51 | 52 | t.match(vFromCache, { 53 | constructor: Advisory, 54 | source: 31, 55 | name: 'semver', 56 | title: 'Regular Expression Denial of Service', 57 | severity: 'moderate', 58 | versions: semver.sort(Object.keys(packuments.semver.versions), so), 59 | vulnerableVersions: semver.sort(Object.keys(packuments.semver.versions).filter(vulnerable => 60 | semver.satisfies(vulnerable, '<4.3.2', so)), so), 61 | url: 'https://npmjs.com/advisories/31', 62 | range: '<4.3.2', 63 | id: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 64 | dependency: 'semver', 65 | updated: false, 66 | packument: packuments.semver, 67 | }, 'not updated from what was in the cache') 68 | 69 | const mv = new Advisory('pacote', v) 70 | t.match(mv, { 71 | source: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 72 | name: 'pacote', 73 | dependency: 'semver', 74 | title: 'Depends on vulnerable versions of semver', 75 | url: null, 76 | severity: 'moderate', 77 | versions: [], 78 | vulnerableVersions: [], 79 | range: null, 80 | id: 'gJzFN5q57zHrhqiRn+lNQXirLlMUtC+8bFqEBGqE3XW/5VC880QTk2o/iRXUfJPT+jhc90QD+d9QIADI68nYlg==', 81 | updated: false, 82 | }, 'initial state') 83 | 84 | mv.load({}, packuments.pacote) 85 | t.match(mv, { 86 | source: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 87 | name: 'pacote', 88 | dependency: 'semver', 89 | title: 'Depends on vulnerable versions of semver', 90 | url: null, 91 | severity: 'moderate', 92 | versions: semver.sort(Object.keys(packuments.pacote.versions), so), 93 | vulnerableVersions: [], 94 | range: '<0.0.0-0', 95 | id: 'gJzFN5q57zHrhqiRn+lNQXirLlMUtC+8bFqEBGqE3XW/5VC880QTk2o/iRXUfJPT+jhc90QD+d9QIADI68nYlg==', 96 | updated: true, 97 | packument: packuments.pacote, 98 | }, 'loaded with empty cache') 99 | 100 | const mvCached = JSON.parse(JSON.stringify(mv)) 101 | const mvFromCache = new Advisory('pacote', vFromCache) 102 | mvFromCache.load(mvCached, packuments.pacote) 103 | t.match(mvFromCache, { 104 | source: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 105 | name: 'pacote', 106 | dependency: 'semver', 107 | title: 'Depends on vulnerable versions of semver', 108 | url: null, 109 | severity: 'moderate', 110 | versions: semver.sort(Object.keys(packuments.pacote.versions), so), 111 | vulnerableVersions: [], 112 | range: '<0.0.0-0', 113 | id: 'gJzFN5q57zHrhqiRn+lNQXirLlMUtC+8bFqEBGqE3XW/5VC880QTk2o/iRXUfJPT+jhc90QD+d9QIADI68nYlg==', 114 | updated: false, 115 | packument: packuments.pacote, 116 | }, 'loaded from full cache') 117 | 118 | const mvFromCacheUpdatedSource = new Advisory('pacote', v) 119 | mvFromCacheUpdatedSource.load(mvCached, packuments.pacote) 120 | t.match(mvFromCacheUpdatedSource, { 121 | source: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 122 | name: 'pacote', 123 | dependency: 'semver', 124 | title: 'Depends on vulnerable versions of semver', 125 | url: null, 126 | severity: 'moderate', 127 | versions: semver.sort(Object.keys(packuments.pacote.versions), so), 128 | vulnerableVersions: [], 129 | range: '<0.0.0-0', 130 | id: 'gJzFN5q57zHrhqiRn+lNQXirLlMUtC+8bFqEBGqE3XW/5VC880QTk2o/iRXUfJPT+jhc90QD+d9QIADI68nYlg==', 131 | updated: true, 132 | packument: packuments.pacote, 133 | }, 'loaded from full cache with an advisory that was updated or not cached') 134 | 135 | const minimistVuln = new Advisory('minimist', advisories.minimist) 136 | minimistVuln.load({}, packuments.minimist) 137 | const mkdirpVuln = new Advisory('mkdirp', minimistVuln) 138 | mkdirpVuln.load({}, packuments.mkdirp) 139 | t.match(mkdirpVuln, { 140 | source: '8MDgP3O3yM8t8dcQHSMUtmH4UKJrKhWmsmV44L4YChIzoahEo+G6j24b+4BPItZck5h5zQFPFD39kOC/789lfA==', 141 | name: 'mkdirp', 142 | dependency: 'minimist', 143 | title: 'Depends on vulnerable versions of minimist', 144 | url: null, 145 | severity: 'low', 146 | versions: semver.sort(Object.keys(packuments.mkdirp.versions), so), 147 | vulnerableVersions: ['0.4.1', '0.4.2', '0.5.0', '0.5.1'], 148 | range: '0.4.1 - 0.5.1', 149 | id: 'dOqvv9Jcyhu8PueSJZB+eZ0G/JI7mVomMmOBSku5SA7OScjvKmHq9jcLVFKmH1wsW2LcZATEOArlMxt/fa5LmA==', 150 | updated: true, 151 | }, 'metavuln that has some vulnerable versions') 152 | const miniFromCache = new Advisory('minimist', advisories.minimist) 153 | miniFromCache.load(JSON.parse(JSON.stringify(minimistVuln)), packuments.minimist) 154 | 155 | // make a version of mkdirp that depends on an impossible version of minimist 156 | packuments.mkdirp.versions['99.99.99'] = { 157 | name: 'mkdirp', 158 | version: '99.99.99', 159 | dependencies: { minimist: '99.99.99' }, 160 | } 161 | const mkdirpVulnBorked = new Advisory('mkdirp', miniFromCache) 162 | // this also covers the case when we load an otherwise cacheable 163 | // advisory from cache, but the packument has new versions to check. 164 | mkdirpVulnBorked.load(JSON.parse(JSON.stringify(mkdirpVuln)), packuments.mkdirp) 165 | t.match(mkdirpVulnBorked, { 166 | source: '8MDgP3O3yM8t8dcQHSMUtmH4UKJrKhWmsmV44L4YChIzoahEo+G6j24b+4BPItZck5h5zQFPFD39kOC/789lfA==', 167 | name: 'mkdirp', 168 | dependency: 'minimist', 169 | title: 'Depends on vulnerable versions of minimist', 170 | url: null, 171 | severity: 'low', 172 | versions: semver.sort(Object.keys(packuments.mkdirp.versions), so), 173 | vulnerableVersions: ['0.4.1', '0.4.2', '0.5.0', '0.5.1', '99.99.99'], 174 | range: '0.4.1 - 0.5.1 || >=99.99.99', 175 | id: 'dOqvv9Jcyhu8PueSJZB+eZ0G/JI7mVomMmOBSku5SA7OScjvKmHq9jcLVFKmH1wsW2LcZATEOArlMxt/fa5LmA==', 176 | updated: true, 177 | }, 'impossible to resolve versions are also treated as vulnerabilities to avoid') 178 | 179 | // a version of mkdirp that bundles a potentially vulnerable minimist 180 | packuments.mkdirp.versions['0.5.0-bundler'] = { 181 | name: 'mkdirp', 182 | version: '0.5.0-bundler', 183 | bundleDependencies: ['minimist'], 184 | dependencies: { minimist: '' }, 185 | } 186 | const mkdirpVulnBundled = new Advisory('mkdirp', minimistVuln) 187 | mkdirpVulnBundled.load(JSON.parse(JSON.stringify(mkdirpVuln)), packuments.mkdirp) 188 | t.match(mkdirpVulnBundled, { 189 | source: '8MDgP3O3yM8t8dcQHSMUtmH4UKJrKhWmsmV44L4YChIzoahEo+G6j24b+4BPItZck5h5zQFPFD39kOC/789lfA==', 190 | name: 'mkdirp', 191 | dependency: 'minimist', 192 | title: 'Depends on vulnerable versions of minimist', 193 | url: null, 194 | severity: 'low', 195 | versions: semver.sort(Object.keys(packuments.mkdirp.versions), so), 196 | vulnerableVersions: ['0.4.1', '0.4.2', '0.5.0-bundler', '0.5.0', '0.5.1', '99.99.99'], 197 | range: '0.4.1 - 0.5.1 || >=99.99.99', 198 | id: 'dOqvv9Jcyhu8PueSJZB+eZ0G/JI7mVomMmOBSku5SA7OScjvKmHq9jcLVFKmH1wsW2LcZATEOArlMxt/fa5LmA==', 199 | updated: true, 200 | }, 'bundled deps are vulnerable if intersecting range') 201 | 202 | t.notOk(mkdirpVulnBundled.testVersion('1234.1234.1234'), 203 | 'missing version is not treated as vulnerable') 204 | t.ok(mkdirpVulnBundled.testVersion('1234.1234.1235', '0.0.8'), 205 | 'missing version with spec is treated as vulnerable based on spec') 206 | t.ok(mkdirpVulnBundled.testVersion('1234.1234.1236', 'github:foo/bar'), 207 | 'missing version with git spec is treated as vulnerable always') 208 | t.ok(mkdirpVulnBundled.testVersion('0.5.0'), 209 | 'known vulnerable version is shown as vulnerable') 210 | 211 | // ok now remove the weird versions, like they were unpublished 212 | delete packuments.mkdirp.versions['0.5.0-bundler'] 213 | delete packuments.mkdirp.versions['99.99.99'] 214 | const mkdirpVulnRmVers = new Advisory('mkdirp', miniFromCache) 215 | mkdirpVulnRmVers.load(JSON.parse(JSON.stringify(mkdirpVulnBundled)), packuments.mkdirp) 216 | t.match(mkdirpVulnRmVers, { 217 | source: '8MDgP3O3yM8t8dcQHSMUtmH4UKJrKhWmsmV44L4YChIzoahEo+G6j24b+4BPItZck5h5zQFPFD39kOC/789lfA==', 218 | name: 'mkdirp', 219 | dependency: 'minimist', 220 | title: 'Depends on vulnerable versions of minimist', 221 | url: null, 222 | severity: 'low', 223 | versions: semver.sort(Object.keys(packuments.mkdirp.versions), so), 224 | vulnerableVersions: ['0.4.1', '0.4.2', '0.5.0', '0.5.1'], 225 | range: '0.4.1 - 0.5.1', 226 | id: 'dOqvv9Jcyhu8PueSJZB+eZ0G/JI7mVomMmOBSku5SA7OScjvKmHq9jcLVFKmH1wsW2LcZATEOArlMxt/fa5LmA==', 227 | updated: true, 228 | }, 'updated to remove versions that were in cache but not packument') 229 | 230 | // test invalid loads 231 | t.throws(() => mkdirpVuln.load(null, {}), { 232 | message: 'invalid cached data, expected object', 233 | }) 234 | t.throws(() => mkdirpVuln.load({}, null), { 235 | message: 'invalid packument data, expected object', 236 | }) 237 | t.throws(() => mkdirpVuln.load({ id: 'wrong' }, {}), { 238 | message: 'loading from incorrect cache entry', 239 | expected: mkdirpVuln.id, 240 | actual: 'wrong', 241 | }) 242 | t.throws(() => mkdirpVuln.load({}, packuments.minimist), { 243 | message: 'loading from incorrect packument', 244 | expected: 'mkdirp', 245 | actual: 'minimist', 246 | }) 247 | t.throws(() => mkdirpVuln.load({}, mkdirpVuln.packument), { 248 | message: 'advisory object already loaded', 249 | }) 250 | 251 | t.end() 252 | }) 253 | 254 | t.test('load with empty packument', t => { 255 | const v = new Advisory('semver', advisories.semver) 256 | v.load({}, { name: 'semver', versions: {} }) 257 | t.match(v, { 258 | constructor: Advisory, 259 | source: 31, 260 | name: 'semver', 261 | dependency: 'semver', 262 | title: 'Regular Expression Denial of Service', 263 | url: 'https://npmjs.com/advisories/31', 264 | severity: 'moderate', 265 | versions: [], 266 | vulnerableVersions: [], 267 | range: '<4.3.2', 268 | id: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 269 | }) 270 | 271 | t.ok(v.testVersion('4.3.1'), 'version covered by range is vulnerable') 272 | t.match(v, { vulnerableVersions: ['4.3.1'], versions: [] }, 'added to set') 273 | t.end() 274 | }) 275 | 276 | t.test('load with no package version in packument', t => { 277 | const v = new Advisory('semver', advisories.semver) 278 | v.load({}, { name: 'semver' }) 279 | t.match(v, { 280 | constructor: Advisory, 281 | source: 31, 282 | name: 'semver', 283 | dependency: 'semver', 284 | title: 'Regular Expression Denial of Service', 285 | url: 'https://npmjs.com/advisories/31', 286 | severity: 'moderate', 287 | versions: [], 288 | vulnerableVersions: [], 289 | range: '<4.3.2', 290 | id: 'jETG9IyfV60PqVhvt3BAecPdQKL2CvXOXr1GeFeSsTkGn8YHi+dU93h8zcjK/xptcxeaYeUBBKmD83eafSecwA==', 291 | }) 292 | 293 | t.ok(v.testVersion('4.3.1'), 'version covered by range is vulnerable') 294 | t.match(v, { vulnerableVersions: ['4.3.1'], versions: [] }, 'added to set') 295 | t.end() 296 | }) 297 | 298 | t.test('a package with a lot of prerelease versions', t => { 299 | const a = advisories['graphql-codegen-plugin-helpers'] 300 | const v = new Advisory('@graphql-codegen/plugin-helpers', a) 301 | v.load({}, packuments['graphql-codegen-plugin-helpers']) 302 | const meta = new Advisory('@graphql-codegen/visitor-plugin-common', v) 303 | t.same(meta.cwe, ['CWE-1234']) 304 | meta.load({}, packuments['graphql-codegen-visitor-plugin-common']) 305 | // kinda weird range here because git tags don't sort alphabetically lol 306 | t.equal(meta.range, '<=1.17.8-alpha-f79b3113.0 || 1.17.13-alpha-7d3e78ce.0') 307 | t.end() 308 | }) 309 | 310 | t.test('a package with only prerelease versions', t => { 311 | const a = { 312 | id: 1234567890, 313 | url: 'https://npmjs.com/advisories/1234567890', 314 | title: 'lol yolo idfk whatever bbq', 315 | vulnerable_versions: '<=0.0.0-pre.5', 316 | severity: 'low', 317 | } 318 | const v = new Advisory('foo', a) 319 | v.load({}, { 320 | name: 'foo', 321 | 'dist-tags': { latest: '0.0.0-pre.7' }, 322 | versions: { 323 | '0.0.0-pre.1': {}, 324 | '0.0.0-pre.2': {}, 325 | '0.0.0-pre.3': {}, 326 | '0.0.0-pre.4': {}, 327 | '0.0.0-pre.5': {}, 328 | '0.0.0-pre.6': {}, 329 | '0.0.0-pre.7': {}, 330 | }, 331 | }) 332 | const meta = new Advisory('bar', v) 333 | meta.load({}, { 334 | name: 'bar', 335 | 'dist-tags': { latest: '0.0.0-pre.7' }, 336 | versions: { 337 | '0.0.0-pre.1': { dependencies: { foo: '0.0.0-pre.1' } }, 338 | '0.0.0-pre.2': { dependencies: { foo: '0.0.0-pre.2' } }, 339 | '0.0.0-pre.3': { dependencies: { foo: '0.0.0-pre.3' } }, 340 | '0.0.0-pre.4': { dependencies: { foo: '0.0.0-pre.4' } }, 341 | '0.0.0-pre.5': { dependencies: { foo: '0.0.0-pre.5' } }, 342 | '0.0.0-pre.6': { dependencies: { foo: '0.0.0-pre.6' } }, 343 | '0.0.0-pre.7': { dependencies: { foo: '0.0.0-pre.7' } }, 344 | }, 345 | }) 346 | t.equal(meta.range, '<=0.0.0-pre.5') 347 | t.end() 348 | }) 349 | 350 | t.test('default to * when no vulnerable_versions specified', t => { 351 | const name = 'no-vulnerable-versions-specified' 352 | const v = new Advisory(name, advisories[name]) 353 | t.same(v, { 354 | source: 123456789, 355 | name: 'no-vulnerable-versions-specified', 356 | dependency: 'no-vulnerable-versions-specified', 357 | title: 'No versions, so all are vulnerable', 358 | url: 'https://npmjs.com/advisories/123456789', 359 | severity: 'low', 360 | versions: [], 361 | vulnerableVersions: [], 362 | cwe: ['CWE-9876'], 363 | cvss: { 364 | score: 0, 365 | vectorString: null, 366 | }, 367 | range: '*', 368 | id: 'scjW9DzqGzCfXM/NEoe9MtD/27lWe9N5ezyJTS2HbpWLiB4FNH5GNenSysezlswMnQwIUtWkVPbWUqRJtUfUJA==', 369 | }, 'default to all versions being considered vulnerable') 370 | t.end() 371 | }) 372 | 373 | t.test('default to "high" when no severity specified', t => { 374 | const name = 'no-severity-specified' 375 | const v = new Advisory(name, advisories[name]) 376 | t.same(v, { 377 | source: 123456789, 378 | name: 'no-severity-specified', 379 | dependency: 'no-severity-specified', 380 | title: 'No severity, so high severity', 381 | url: 'https://npmjs.com/advisories/123456789', 382 | severity: 'high', 383 | versions: [], 384 | vulnerableVersions: [], 385 | cwe: ['CWE-2345'], 386 | cvss: { 387 | score: 7.4, 388 | vectorString: 'CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H', 389 | }, 390 | range: '1.x', 391 | id: 'ajZ5Jt7T99fpH0t8LgyBbDVivYlv/1OGrs/o+D8KmLDl+LKTjObUEt19cAZGaWdqiemuQOnvdZD577nKU+giIQ==', 392 | }, 'default to all versions being considered vulnerable') 393 | t.end() 394 | }) 395 | -------------------------------------------------------------------------------- /test/fixtures/advisories/graphql-codegen-plugin-helpers.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1234567890, 3 | "url": "https://npmjs.com/advisories/1234567890", 4 | "title": "This is fake, and not a real advisory", 5 | "severity": "low", 6 | "vulnerable_versions": "<1.18.0", 7 | "cwe": [ 8 | "CWE-1234" 9 | ], 10 | "cvss": { 11 | "score": 0, 12 | "vectorString": null 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/advisories/index.js: -------------------------------------------------------------------------------- 1 | const { readdirSync } = require('fs') 2 | const { basename } = require('path') 3 | for (const f of readdirSync(__dirname).filter(s => /\.json$/.test(s))) { 4 | exports[basename(f, '.json')] = require(`./${f}`) 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/advisories/minimist.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1179, 3 | "url": "https://npmjs.com/advisories/1179", 4 | "title": "Prototype Pollution", 5 | "severity": "low", 6 | "vulnerable_versions": "<0.2.1 || >=1.0.0 <1.2.3" 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/advisories/no-severity-specified.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 123456789, 3 | "url": "https://npmjs.com/advisories/123456789", 4 | "title": "No severity, so high severity", 5 | "vulnerable_versions": "1.x", 6 | "cwe": [ 7 | "CWE-2345" 8 | ], 9 | "cvss": { 10 | "score": 7.4, 11 | "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:H" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/advisories/no-vulnerable-versions-specified.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 123456789, 3 | "url": "https://npmjs.com/advisories/123456789", 4 | "title": "No versions, so all are vulnerable", 5 | "severity": "low", 6 | "cwe": [ 7 | "CWE-9876" 8 | ], 9 | "cvss": { 10 | "score": 0, 11 | "vectorString": null 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/advisories/semver.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 31, 3 | "url": "https://npmjs.com/advisories/31", 4 | "title": "Regular Expression Denial of Service", 5 | "severity": "moderate", 6 | "vulnerable_versions": "<4.3.2" 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/packuments/index.js: -------------------------------------------------------------------------------- 1 | const { readdirSync } = require('fs') 2 | const { basename } = require('path') 3 | for (const f of readdirSync(__dirname).filter(s => /\.json$/.test(s))) { 4 | exports[basename(f, '.json')] = require(`./${f}`) 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/packuments/minimist.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "minimist", 3 | "dist-tags": { 4 | "latest": "1.2.5" 5 | }, 6 | "versions": { 7 | "0.0.0": { 8 | "name": "minimist", 9 | "version": "0.0.0", 10 | "devDependencies": { 11 | "tape": "~1.0.4", 12 | "tap": "~0.4.0" 13 | }, 14 | "dist": { 15 | "shasum": "0f62459b3333ea881e554e400243e130ef123568", 16 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.0.tgz" 17 | } 18 | }, 19 | "0.0.1": { 20 | "name": "minimist", 21 | "version": "0.0.1", 22 | "devDependencies": { 23 | "tape": "~1.0.4", 24 | "tap": "~0.4.0" 25 | }, 26 | "dist": { 27 | "shasum": "fa2439fbf7da8525c51b2a74e2815b380abc8ab6", 28 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.1.tgz" 29 | } 30 | }, 31 | "0.0.2": { 32 | "name": "minimist", 33 | "version": "0.0.2", 34 | "devDependencies": { 35 | "tape": "~1.0.4", 36 | "tap": "~0.4.0" 37 | }, 38 | "dist": { 39 | "shasum": "3297e0500be195b8fcb56668c45b925bc9bca7ab", 40 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.2.tgz" 41 | } 42 | }, 43 | "0.0.3": { 44 | "name": "minimist", 45 | "version": "0.0.3", 46 | "devDependencies": { 47 | "tape": "~1.0.4", 48 | "tap": "~0.4.0" 49 | }, 50 | "dist": { 51 | "shasum": "a7a2ef8fbafecbae6c1baa4e56ad81e77acacb94", 52 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.3.tgz" 53 | } 54 | }, 55 | "0.0.4": { 56 | "name": "minimist", 57 | "version": "0.0.4", 58 | "devDependencies": { 59 | "tape": "~1.0.4", 60 | "tap": "~0.4.0" 61 | }, 62 | "dist": { 63 | "shasum": "db41b1028484927a9425765b954075f5082f5048", 64 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.4.tgz" 65 | } 66 | }, 67 | "0.0.5": { 68 | "name": "minimist", 69 | "version": "0.0.5", 70 | "devDependencies": { 71 | "tape": "~1.0.4", 72 | "tap": "~0.4.0" 73 | }, 74 | "dist": { 75 | "shasum": "d7aa327bcecf518f9106ac6b8f003fa3bcea8566", 76 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.5.tgz" 77 | } 78 | }, 79 | "0.0.6": { 80 | "name": "minimist", 81 | "version": "0.0.6", 82 | "devDependencies": { 83 | "tape": "~1.0.4", 84 | "tap": "~0.4.0" 85 | }, 86 | "dist": { 87 | "shasum": "a515054dd7de651410a5511ef6b2d7fdcb596394", 88 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.6.tgz" 89 | } 90 | }, 91 | "0.0.7": { 92 | "name": "minimist", 93 | "version": "0.0.7", 94 | "devDependencies": { 95 | "tape": "~1.0.4", 96 | "tap": "~0.4.0" 97 | }, 98 | "dist": { 99 | "shasum": "dc4c620253c542eda0d2eb91c3c6a971a11e63e7", 100 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.7.tgz" 101 | } 102 | }, 103 | "0.0.8": { 104 | "name": "minimist", 105 | "version": "0.0.8", 106 | "devDependencies": { 107 | "tape": "~1.0.4", 108 | "tap": "~0.4.0" 109 | }, 110 | "dist": { 111 | "shasum": "857fcabfc3397d2625b8228262e86aa7a011b05d", 112 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" 113 | } 114 | }, 115 | "0.0.9": { 116 | "name": "minimist", 117 | "version": "0.0.9", 118 | "devDependencies": { 119 | "tape": "~1.0.4", 120 | "tap": "~0.4.0" 121 | }, 122 | "dist": { 123 | "shasum": "04e6034ffbf572be2fe42cf1da2c696be0901917", 124 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.9.tgz" 125 | } 126 | }, 127 | "0.0.10": { 128 | "name": "minimist", 129 | "version": "0.0.10", 130 | "devDependencies": { 131 | "tape": "~1.0.4", 132 | "tap": "~0.4.0" 133 | }, 134 | "dist": { 135 | "shasum": "de3f98543dbf96082be48ad1a0c7cda836301dcf", 136 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" 137 | } 138 | }, 139 | "0.1.0": { 140 | "name": "minimist", 141 | "version": "0.1.0", 142 | "devDependencies": { 143 | "tape": "~1.0.4", 144 | "tap": "~0.4.0" 145 | }, 146 | "dist": { 147 | "shasum": "99df657a52574c21c9057497df742790b2b4c0de", 148 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz" 149 | } 150 | }, 151 | "0.2.0": { 152 | "name": "minimist", 153 | "version": "0.2.0", 154 | "devDependencies": { 155 | "tape": "~1.0.4", 156 | "tap": "~0.4.0" 157 | }, 158 | "dist": { 159 | "shasum": "4dffe525dae2b864c66c2e23c6271d7afdecefce", 160 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz" 161 | } 162 | }, 163 | "1.0.0": { 164 | "name": "minimist", 165 | "version": "1.0.0", 166 | "devDependencies": { 167 | "tape": "~1.0.4", 168 | "tap": "~0.4.0" 169 | }, 170 | "dist": { 171 | "shasum": "9429c4f83e0d22590a76aeb610006dcc3f89c70f", 172 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.0.0.tgz" 173 | } 174 | }, 175 | "1.1.0": { 176 | "name": "minimist", 177 | "version": "1.1.0", 178 | "devDependencies": { 179 | "tape": "~1.0.4", 180 | "tap": "~0.4.0", 181 | "covert": "^1.0.0" 182 | }, 183 | "dist": { 184 | "shasum": "cdf225e8898f840a258ded44fc91776770afdc93", 185 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.1.0.tgz" 186 | } 187 | }, 188 | "1.1.1": { 189 | "name": "minimist", 190 | "version": "1.1.1", 191 | "devDependencies": { 192 | "covert": "^1.0.0", 193 | "tap": "~0.4.0", 194 | "tape": "^3.5.0" 195 | }, 196 | "dist": { 197 | "shasum": "1bc2bc71658cdca5712475684363615b0b4f695b", 198 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.1.1.tgz" 199 | } 200 | }, 201 | "1.1.2": { 202 | "name": "minimist", 203 | "version": "1.1.2", 204 | "devDependencies": { 205 | "covert": "^1.0.0", 206 | "tap": "~0.4.0", 207 | "tape": "^3.5.0" 208 | }, 209 | "dist": { 210 | "shasum": "af960b80caf71b38236352af7fef10a8efceeae3", 211 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.1.2.tgz" 212 | } 213 | }, 214 | "1.1.3": { 215 | "name": "minimist", 216 | "version": "1.1.3", 217 | "devDependencies": { 218 | "covert": "^1.0.0", 219 | "tap": "~0.4.0", 220 | "tape": "^3.5.0" 221 | }, 222 | "dist": { 223 | "shasum": "3bedfd91a92d39016fcfaa1c681e8faa1a1efda8", 224 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz" 225 | } 226 | }, 227 | "1.2.0": { 228 | "name": "minimist", 229 | "version": "1.2.0", 230 | "devDependencies": { 231 | "covert": "^1.0.0", 232 | "tap": "~0.4.0", 233 | "tape": "^3.5.0" 234 | }, 235 | "dist": { 236 | "shasum": "a35008b20f41383eec1fb914f4cd5df79a264284", 237 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" 238 | } 239 | }, 240 | "1.2.1": { 241 | "name": "minimist", 242 | "version": "1.2.1", 243 | "devDependencies": { 244 | "covert": "^1.0.0", 245 | "tap": "~0.4.0", 246 | "tape": "^3.5.0" 247 | }, 248 | "dist": { 249 | "integrity": "sha512-MxgrjJPBtptee4aL2EExBK3tS4KUc1RtuRluaHZCmxKII8SBkvUP6z7XDmxEaUxdvpp++waKrqvSzuFXTRntVg==", 250 | "shasum": "f41818114b8d4be3c0a4febdf3c432b956680ece", 251 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.2.1.tgz", 252 | "fileCount": 20, 253 | "unpackedSize": 30683, 254 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeZ9cNCRA9TVsSAnZWagAAtWMP+wR5OEzH8TkYeKqLbQZn\nvDcc3Dppv/TiPzbcEkHj5Cg72TVv4o3zX5seBR8eKOTHA3l/hwbet+MemOtn\n8h81N/3iFmeV+qd0w1r9PxMRsLUbK1x83CV/QvYUiOTVuDiIBD4KUnaBhzZH\nQgjsyAWLAX2tDN0RHpMXQSG31Ya1Bu5mWJ/AQU//X8Fxy1HgiuI1PfN+6+Sz\nuMrFx7uxzLX6dF/BKZ/24OHzuMTz29qKdvv/l4PLECtQ+MYh4WTtFW8zKEzT\nXN7DW5stdThhbZnRfLae3eozVZZhCDogPNJ19HKGNinu6f7E6YdFAKtUEQf/\n/kP67e5NhZHYZnLkY6c3ATQa8QuRCs8c/rJftbmxYcze+8HnyYTu5JmkVgAn\nNcoW70e0S+DHXrMJ4DILPMmtuCyUmbaSMVTeeykjPGyouM85a0/pVP1oRVmJ\npTNBpWY/8H2XJiOPJjIY+86tYUmwyD9eL34xYbTWuB1qGm1q9lzcb862thnl\nTgy/HyE2kaLke12J0PH0NU9djftMyxVSnebttUOjRneTZ3U/Xf+U5BJH6oPA\nDLVPWCa3Jxq98E8KBlMdPK/xMBD8HLdTLoNUkNJamPiZxvUxJwkM++jph3G2\n1/Iw7MvrckGKhKU16c7qNocQXXk73vBHOq8kyefX7WUmDOEJcbcMlesmhF0u\nNOr6\r\n=99eC\r\n-----END PGP SIGNATURE-----\r\n" 255 | } 256 | }, 257 | "1.2.2": { 258 | "name": "minimist", 259 | "version": "1.2.2", 260 | "devDependencies": { 261 | "covert": "^1.0.0", 262 | "tap": "~0.4.0", 263 | "tape": "^3.5.0" 264 | }, 265 | "dist": { 266 | "integrity": "sha512-rIqbOrKb8GJmx/5bc2M0QchhUouMXSpd1RTclXsB41JdL+VtnojfaJR+h7F9k18/4kHUsBFgk80Uk+q569vjPA==", 267 | "shasum": "b00a00230a1108c48c169e69a291aafda3aacd63", 268 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.2.2.tgz", 269 | "fileCount": 21, 270 | "unpackedSize": 30741, 271 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeZ95cCRA9TVsSAnZWagAA56QP+gK14eqYLhwCvOl2CS/v\nJlCozvb1bOJROoCBu4gZRro830Z5Sx1L5NuMetqLyKd4hbeotDsrymXCgebJ\nS+gAQmtLXV3EGSwAgK0bHrI2+GrfOc70kx5lBTYwXXNVn6oZU6ONM+ERWYNs\ncf6I/ZZX424u8uE0086Q8Zp9fPp/qcf76p0P5zWJuaY+t7gUV2l3NDGXITaz\n7s9hfKKIX92++w44RAXDwoyXi7gb57mIKatSYplQCtQuIjISA0Lo7KIU7mhH\nvLHbzHIusgm6KQ4wdtpHNbcClF8kZIqdzJ4rCrWAT6/wKeDnrx+4kLzN+Xvq\nRA2qvb2xsN8/1g7qbu5elxNPuHwPjmjcf1ic2x0/mzmQEV/wqPHSDB7isT8U\ns4G2UPC9SztuTXf80Zd8K6SI0Qu8GzomhdV+9QMB5PbBvkGkVTjTOY3PRmdF\nxG74/RLGft9+iKKPIitmkDU8XXiKi2WJnB2pShtHDDTXQVlrDMxvD85uDzXL\nkqiohLXMp1brNCENQFKUBR7Cg405b/O5NBgNFxnPdHpFE7makXHhjiKAo3nX\nntDBKN3Px7+iKu4zvb9L9k9v3mj6YxGWRhMYemRv4ci2K6fGTj3Oz2zxNosp\nDKLU9RHkV+HCtRJloF3vj6EeIOLD7yPqk/KTJC4TtlwjoUznuejAPxi4ju6J\niyOA\r\n=cJCk\r\n-----END PGP SIGNATURE-----\r\n" 272 | } 273 | }, 274 | "1.2.3": { 275 | "name": "minimist", 276 | "version": "1.2.3", 277 | "devDependencies": { 278 | "covert": "^1.0.0", 279 | "tap": "~0.4.0", 280 | "tape": "^3.5.0" 281 | }, 282 | "dist": { 283 | "integrity": "sha512-+bMdgqjMN/Z77a6NlY/I3U5LlRDbnmaAk6lDveAPKwSpcPM4tKAuYsvYF8xjhOPXhOYGe/73vVLVez5PW+jqhw==", 284 | "shasum": "3db5c0765545ab8637be71f333a104a965a9ca3f", 285 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.2.3.tgz", 286 | "fileCount": 21, 287 | "unpackedSize": 31954, 288 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeZ+WoCRA9TVsSAnZWagAA4+EP/iTCVS055EBuEuRFMplv\n+3IvmNilogYEWtZpz6HF5mVJA1eksI3LrbcXEzJjd+C0sZ+eZwONxPN7AbEX\nAJ8kIlTQ3n/1qGCtpwnLtBd158tzyLBprxvtab3BMDO49hx8uSuedeOa8Fh/\nxGghAeLZZfPYqQQ9RTcgpFzPbvmN2CWNPeNT90a3E4mHlkcYRO/lWnz5IroI\n+5igofAC0foyr/lQqcXLO4jTreEt/6Lo153a0h8Tl6rW5sq1P7BB/4mGA21l\nZpvonhpMXrLL4eHCbVMPlS6GNISI9xvctp7J+CPNIy8S2FMZemYKb3EBkbBE\n4jUElnxyxdhNA6sZQTT1wryiqvD82qkCwPyDQOjT38tBaFf93+JaegsKR2gX\nr0X7t1w9IgEVuJJ6rYKh41w5R4f/H7wV9I87xW1IlhsFLSq4+F0bHb5xYk2P\naWqEl5n09yQ4a5VLYOwWn15QHVIJ8x5tB2puNrm1kzq5GHNw8tMKoRAmExCP\nvlgi1S4ymbWvZc8HVjQDkWNVkWDZZKPsd6hsTwLURnbicoXJbEAo0jPAc+/M\nQBah70YQ7bqsEk9E9iDPPVibB8TiM4ZLr5lvk2wAG9sjROjYSZMCmtJZN7tv\nq5tXp2BgYQIyCA4TqAn0nk+IaXgzRjRKeDOOwhbrdcEVwp2YfjWe1n4Uq/5G\nqsro\r\n=WPB0\r\n-----END PGP SIGNATURE-----\r\n" 289 | } 290 | }, 291 | "1.2.4": { 292 | "name": "minimist", 293 | "version": "1.2.4", 294 | "devDependencies": { 295 | "covert": "^1.0.0", 296 | "tap": "~0.4.0", 297 | "tape": "^3.5.0" 298 | }, 299 | "dist": { 300 | "integrity": "sha512-wTiNDqe4D2rbTJGZk1qcdZgFtY0/r+iuE6GDT7V0/+Gu5MLpIDm4+CssDECR79OJs/OxLPXMzdxy153b5Qy3hg==", 301 | "shasum": "40357ef9582f8cd42ba8eaa274ddcf27f0c979b7", 302 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.2.4.tgz", 303 | "fileCount": 21, 304 | "unpackedSize": 32384, 305 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeaTsaCRA9TVsSAnZWagAAOMgQAJylxIJH+awQLgWzP9Ik\nmbQvKz5Bk7E+keg0vGA/ecxMspZGZiTsXnO72JL0+DFpXxbdcs04uXm822MS\nizWri8HZrdJwQqb/E6ZnqSdKij+C6YXM3yUutKdeN56h5dAZNiZotsyVdHcu\nmhpAf0AHyYhG9cS2Gz+wNCQDTpz6HZ3lqvxonLsotTHsp6i1JyQaBygq+kj9\nnCs/rvxpBnACjHJmtLmlrMcRZL8FiwhOYJp5vUA8TNcRFF/FHCsXRo2UGyzr\nbFDXysU9tQIe5Omt47ZAwLw1m3xu37S6DK8ABewWA8JE6twmsk65HXQq7ea1\nHRiQs6fdMieUeOy4083sgsejsWSsB1d4tlkdeOjW0nFfkVlDJoocU90AIcD9\nZac17pFUsVmmhwCeEtoR9SQ7sYgTTFtdE/YOeoN7z0AhjJ+OW54qPoEC2neM\nQvd+w7KioIHNwivH5JkMgE43WJFwxnNlFqRMMtrQzCxYTk9QqSRYqzblv5lG\na2kHMaW82dMZzp8d7wL1JadtA+lVwz+P2fspMCsgiFc8jnci4r9z+A76fj4W\nW94AmC8mZ6IVaaLgRVZnRmWRoEbjuv8whQf3hneQ+XwnMakBRzkMJUfs8XEI\ndSD4GTqgoikn4qrOTh0bNp6pqZfSJQspHnfDQFIa/LWfqlI2en+RC+AXcDKC\nIgE+\r\n=oj4i\r\n-----END PGP SIGNATURE-----\r\n" 306 | } 307 | }, 308 | "0.2.1": { 309 | "name": "minimist", 310 | "version": "0.2.1", 311 | "devDependencies": { 312 | "tape": "~1.0.4", 313 | "tap": "~0.4.0" 314 | }, 315 | "dist": { 316 | "integrity": "sha512-GY8fANSrTMfBVfInqJAY41QkOM+upUTytK1jZ0c8+3HdHrJxBJ3rF5i9moClXTE8uUSnUo8cAsCoxDXvSY4DHg==", 317 | "shasum": "827ba4e7593464e7c221e8c5bed930904ee2c455", 318 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-0.2.1.tgz", 319 | "fileCount": 18, 320 | "unpackedSize": 25782, 321 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJearRoCRA9TVsSAnZWagAA3DgP/A7mSWMTpBKTc8MQ9jt6\n1Q1DL0Eoi16FJrFuxwhVPQyVjSvsq1mMdb34hWkS8ZTuFFo6HrL9SFzjLoBa\nkYk+V6eZD8qqCKdQz2HK/q+EFBVysZz/7S1WwNgrx947fEXhfu4A0khcq4AA\nvur3ZSNkfg0P7Dn+CbKX7sUMdkjLL949tH9R2Bs60JOj1ywsXFB3+yz6yMy2\ndtgjpjIh3qK86vWHQ626RpWcnvOrU8hiTvZpTEeKIx5xdwCaSfiQDaGmGRj0\nnwDd9i9y6bIZXav01/L+gIYhlfg/xCK3jBbLETozYRQv7pNaA7Qh15c1PTHW\n1QGS5/VChuwp6AGcFkM9XDnOBEK/xn+1gqGYYnEnGr8iRdBmNjCyQaUSy31J\n1FA3nb91fW6JvUsmKYRlBP/O38mnx4m40gP8SLUnSllJkOrZ0+aq5DG+c+/X\nl5j257jCUFtdnvGn7LEKU5qO9m8jehzLP4fUmn7kD7d2BfNPzIwY6jnfCxvh\ntT5nEEl8WZaQo5sADKu48jJbluZSj+bD8aGc4Qf7l+qWXQKlrRj8ZSO/bFyY\nVhB3MzR0P6UO9Wu7rWbf6Fu5+kdRBWZ0KjmJSBAW2BMw54s2TZ7MvQ0tJJFi\nuC8OEDzaNSwB0dcOmVRasgW8IIplWbj+JveQ+XW2sfyJ92yFfOoreZY6TQJY\n67eN\r\n=fsdQ\r\n-----END PGP SIGNATURE-----\r\n" 322 | } 323 | }, 324 | "1.2.5": { 325 | "name": "minimist", 326 | "version": "1.2.5", 327 | "devDependencies": { 328 | "covert": "^1.0.0", 329 | "tap": "~0.4.0", 330 | "tape": "^3.5.0" 331 | }, 332 | "dist": { 333 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 334 | "shasum": "67d66014b66a6a8aaa0c083c5fd58df4e4e97602", 335 | "tarball": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 336 | "fileCount": 21, 337 | "unpackedSize": 32384, 338 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJearSzCRA9TVsSAnZWagAAdXQP/37gxveGMGnYPLHEtnLE\nxGoFFRWU82yPya40/uMK45UZl5S48tCIJF+QgifQl4dnRJ0pvPBobZtovjQ3\n9kD9E9my/JJEvytLoM3rIhyaMpfpr68zABVYA8MUVUHsDOIjn6HVr1UR6B7L\nj8AfsLdtmO6Sy6nb8t5TfBuDprsLbGCKqXyUXWOMOJc/9fQN5PEKMJ9bZa2d\nT5gcJ3wxWKJYRt4Wiv6ddQ5xN+4Tnc7pA9yqxz73bn6aaRgLq3c2vam0leGE\nxjVC9OX8riWiPVMJNUqGwC7qa8u59QFos1TZQ8HpBiJLP9L33UdHcYbDNAoZ\nRSSBw3gM3H+eRWIN369POOO8AFrdbAGovSmWcS9RFLDjSm8rWmB+Jx22Z1Na\nc54umOQDXuuAS8TbWsVyLvjKd+UwxHRc0h8e/gMQO2AGFQzTlJAgarCanTrz\nZBAcjJNsE6MKANRqWHobiE38viQOOGqVnCgQ5L4CwcMI7BMH7MFc4ntMLVIO\nxZPbBXHzYi5dfWucd8C11ZTN5Id4D+mdRZbUe8jePZ0WsD/xjABokZuhZziW\n90inv759CQn4JjnDKPQrZlBt+/6L3a+QQHhmNf+WWFKKPdDIpSZ395Kxmqov\nVLunRv2RigRd0r7GQGthStV+sxjCmy/ZgV56PpeFj1j1d/GvUu6/jYwG+J8X\nMbJT\r\n=myam\r\n-----END PGP SIGNATURE-----\r\n" 339 | } 340 | } 341 | }, 342 | "modified": "2020-06-16T04:12:40.399Z", 343 | "_cached": true, 344 | "_contentLength": 0 345 | } 346 | -------------------------------------------------------------------------------- /test/fixtures/packuments/mkdirp.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mkdirp", 3 | "dist-tags": { 4 | "latest": "1.0.4", 5 | "legacy": "0.5.5" 6 | }, 7 | "versions": { 8 | "0.0.1": { 9 | "name": "mkdirp", 10 | "version": "0.0.1", 11 | "dist": { 12 | "shasum": "3fbd9f4711a5234233dc6c9d7a052d4b9f83b416", 13 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.1.tgz" 14 | }, 15 | "engines": { 16 | "node": "*" 17 | }, 18 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 19 | }, 20 | "0.0.2": { 21 | "name": "mkdirp", 22 | "version": "0.0.2", 23 | "dist": { 24 | "shasum": "d9438082daac12691c71d64076706c8a5c3511b6", 25 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.2.tgz" 26 | }, 27 | "engines": { 28 | "node": "*" 29 | }, 30 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 31 | }, 32 | "0.0.3": { 33 | "name": "mkdirp", 34 | "version": "0.0.3", 35 | "devDependencies": { 36 | "expresso": "0.7.x" 37 | }, 38 | "dist": { 39 | "shasum": "5a7d88a26857023759ffee7fe4c0b28b0f0066b9", 40 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.3.tgz" 41 | }, 42 | "engines": { 43 | "node": "*" 44 | }, 45 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 46 | }, 47 | "0.0.4": { 48 | "name": "mkdirp", 49 | "version": "0.0.4", 50 | "devDependencies": { 51 | "expresso": "0.7.x" 52 | }, 53 | "dist": { 54 | "shasum": "fbb491deec0b9b00869f52582e5f431b3681d2f5", 55 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.4.tgz" 56 | }, 57 | "engines": { 58 | "node": "*" 59 | }, 60 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 61 | }, 62 | "0.0.5": { 63 | "name": "mkdirp", 64 | "version": "0.0.5", 65 | "devDependencies": { 66 | "expresso": "0.7.x" 67 | }, 68 | "dist": { 69 | "shasum": "375facfa634b17dcdf734c56f59ddae5102811c8", 70 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.5.tgz" 71 | }, 72 | "engines": { 73 | "node": "*" 74 | }, 75 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 76 | }, 77 | "0.0.6": { 78 | "name": "mkdirp", 79 | "version": "0.0.6", 80 | "devDependencies": { 81 | "expresso": "0.7.x" 82 | }, 83 | "dist": { 84 | "shasum": "0965de71060cf5e237ffa795243cb5d9a78d335b", 85 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.6.tgz" 86 | }, 87 | "engines": { 88 | "node": "*" 89 | }, 90 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 91 | }, 92 | "0.0.7": { 93 | "name": "mkdirp", 94 | "version": "0.0.7", 95 | "devDependencies": { 96 | "tap": "0.0.x" 97 | }, 98 | "dist": { 99 | "shasum": "d89b4f0e4c3e5e5ca54235931675e094fe1a5072", 100 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.0.7.tgz" 101 | }, 102 | "engines": { 103 | "node": "*" 104 | }, 105 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 106 | }, 107 | "0.1.0": { 108 | "name": "mkdirp", 109 | "version": "0.1.0", 110 | "devDependencies": { 111 | "tap": "0.0.x" 112 | }, 113 | "dist": { 114 | "shasum": "53212930f7bd75f187b6c8688eb0a5fd69b7d118", 115 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.1.0.tgz" 116 | }, 117 | "engines": { 118 | "node": "*" 119 | }, 120 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 121 | }, 122 | "0.2.0": { 123 | "name": "mkdirp", 124 | "version": "0.2.0", 125 | "devDependencies": { 126 | "tap": "0.0.x" 127 | }, 128 | "dist": { 129 | "shasum": "29dd87f198880b568d1efce0980e7231b048f3aa", 130 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.2.0.tgz" 131 | }, 132 | "engines": { 133 | "node": "*" 134 | }, 135 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 136 | }, 137 | "0.2.1": { 138 | "name": "mkdirp", 139 | "version": "0.2.1", 140 | "devDependencies": { 141 | "tap": "0.0.x" 142 | }, 143 | "dist": { 144 | "shasum": "2ef920435c8511e135137a33f18a9e40cf9dd166", 145 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.2.1.tgz" 146 | }, 147 | "engines": { 148 | "node": "*" 149 | }, 150 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 151 | }, 152 | "0.2.2": { 153 | "name": "mkdirp", 154 | "version": "0.2.2", 155 | "devDependencies": { 156 | "tap": "0.0.x" 157 | }, 158 | "dist": { 159 | "shasum": "7235f2a2062aaf3619189b9f4772114c30944498", 160 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.2.2.tgz" 161 | }, 162 | "engines": { 163 | "node": "*" 164 | }, 165 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 166 | }, 167 | "0.3.0": { 168 | "name": "mkdirp", 169 | "version": "0.3.0", 170 | "devDependencies": { 171 | "tap": "0.0.x" 172 | }, 173 | "dist": { 174 | "shasum": "1bbf5ab1ba827af23575143490426455f481fe1e", 175 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz" 176 | }, 177 | "engines": { 178 | "node": "*" 179 | }, 180 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 181 | }, 182 | "0.3.1": { 183 | "name": "mkdirp", 184 | "version": "0.3.1", 185 | "devDependencies": { 186 | "tap": "~0.2.4" 187 | }, 188 | "dist": { 189 | "shasum": "bee3db22a2aa1c81d4b4c0db39c7da9888799593", 190 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.1.tgz" 191 | }, 192 | "engines": { 193 | "node": "*" 194 | }, 195 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 196 | }, 197 | "0.3.2": { 198 | "name": "mkdirp", 199 | "version": "0.3.2", 200 | "devDependencies": { 201 | "tap": "~0.2.4" 202 | }, 203 | "dist": { 204 | "shasum": "4bfb891e9c48b93d6b567f2c3cf2dd3f56bcdef8", 205 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.2.tgz" 206 | }, 207 | "engines": { 208 | "node": "*" 209 | }, 210 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 211 | }, 212 | "0.3.3": { 213 | "name": "mkdirp", 214 | "version": "0.3.3", 215 | "devDependencies": { 216 | "tap": "~0.2.4" 217 | }, 218 | "dist": { 219 | "shasum": "595e251c1370c3a68bab2136d0e348b8105adf13", 220 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.3.tgz" 221 | }, 222 | "engines": { 223 | "node": "*" 224 | }, 225 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 226 | }, 227 | "0.3.4": { 228 | "name": "mkdirp", 229 | "version": "0.3.4", 230 | "devDependencies": { 231 | "tap": "~0.2.4" 232 | }, 233 | "dist": { 234 | "shasum": "f8c81d213b7299a031f193a57d752a17d2f6c7d8", 235 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.4.tgz" 236 | }, 237 | "engines": { 238 | "node": "*" 239 | }, 240 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 241 | }, 242 | "0.3.5": { 243 | "name": "mkdirp", 244 | "version": "0.3.5", 245 | "devDependencies": { 246 | "tap": "~0.4.0" 247 | }, 248 | "dist": { 249 | "shasum": "de3e5f8961c88c787ee1368df849ac4413eca8d7", 250 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz" 251 | }, 252 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 253 | }, 254 | "0.4.0": { 255 | "name": "mkdirp", 256 | "version": "0.4.0", 257 | "devDependencies": { 258 | "tap": "~0.4.0" 259 | }, 260 | "bin": { 261 | "mkdirp": "bin/cmd.js" 262 | }, 263 | "dist": { 264 | "shasum": "291ac2a2d43a19c478662577b5be846fe83b5923", 265 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.4.0.tgz" 266 | }, 267 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 268 | }, 269 | "0.4.1": { 270 | "name": "mkdirp", 271 | "version": "0.4.1", 272 | "dependencies": { 273 | "minimist": "0.0.8" 274 | }, 275 | "devDependencies": { 276 | "tap": "~0.4.0" 277 | }, 278 | "bin": { 279 | "mkdirp": "bin/cmd.js" 280 | }, 281 | "dist": { 282 | "shasum": "4d467afabfdf8ae460c2da656eae8f7b21af4558", 283 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.4.1.tgz" 284 | }, 285 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 286 | }, 287 | "0.4.2": { 288 | "name": "mkdirp", 289 | "version": "0.4.2", 290 | "dependencies": { 291 | "minimist": "0.0.8" 292 | }, 293 | "devDependencies": { 294 | "tap": "~0.4.0" 295 | }, 296 | "bin": { 297 | "mkdirp": "bin/cmd.js" 298 | }, 299 | "dist": { 300 | "shasum": "427c8c18ece398b932f6f666f4e1e5b7740e78c8", 301 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.4.2.tgz" 302 | }, 303 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 304 | }, 305 | "0.5.0": { 306 | "name": "mkdirp", 307 | "version": "0.5.0", 308 | "dependencies": { 309 | "minimist": "0.0.8" 310 | }, 311 | "devDependencies": { 312 | "tap": "~0.4.0", 313 | "mock-fs": "~2.2.0" 314 | }, 315 | "bin": { 316 | "mkdirp": "bin/cmd.js" 317 | }, 318 | "dist": { 319 | "shasum": "1d73076a6df986cd9344e15e71fcc05a4c9abf12", 320 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz" 321 | }, 322 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 323 | }, 324 | "0.5.1": { 325 | "name": "mkdirp", 326 | "version": "0.5.1", 327 | "dependencies": { 328 | "minimist": "0.0.8" 329 | }, 330 | "devDependencies": { 331 | "tap": "1", 332 | "mock-fs": "2 >=2.7.0" 333 | }, 334 | "bin": { 335 | "mkdirp": "bin/cmd.js" 336 | }, 337 | "dist": { 338 | "shasum": "30057438eac6cf7f8c4767f38648d6697d75c903", 339 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" 340 | }, 341 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 342 | }, 343 | "1.0.0": { 344 | "name": "mkdirp", 345 | "version": "1.0.0", 346 | "devDependencies": { 347 | "require-inject": "^1.4.4", 348 | "tap": "^14.10.6" 349 | }, 350 | "bin": { 351 | "mkdirp": "bin/cmd.js" 352 | }, 353 | "dist": { 354 | "integrity": "sha512-4Pb+8NJ5DdvaWD797hKOM28wMXsObb4HppQdIwKUHFiB69ICZ4wktOE+qsGGBy7GtwgYNizp0R9KEy4zKYBLMg==", 355 | "shasum": "8487b07699b70c9b06fce47b3ce28d8176c13c75", 356 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.0.tgz", 357 | "fileCount": 39, 358 | "unpackedSize": 51158, 359 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeK1EjCRA9TVsSAnZWagAA3dIP/isRDRiVFfvRTok8YZa2\nU7DHcBjKLouQm0ZXy0WqfodlzqwPGRCZwMgeWNsb4S01UPTewUC1jd/zmMtt\nV1YoyhCDCojNCQY0X2mfijSeGk9LkrnQsYbcIsg+IP4QVmx4y3KC0yCvP9YM\nay0uE9YQLe253erLqObHvzbHJl3V+pDi1rGZ8xufoOCzf1tyBpVzkwjnQlLa\nahZ8mrXB+kS1Du/yCQYHHuVuNkxBIdPKy/02SNQXfao79i/BXM9dzFmv/rq8\nA6mVat4YHQiLZ1XMk+aqCXuIrVeR6P5Slwwqf6nhIIXa6hocRGOHBkLPxOuB\n1rG3ycUb/tnPabEObRIf5V48L43/OoMiSMOVCK3LjqHQ1GgOe5A7gWfLgsW4\nsn6EEhI3nwO/dAnpBzaF0p/h6Tnkj5AqCWR7zI6Tftl2UX+KSekkt6AaEr4G\n04/GSnb+FYDZ9teauzx2Dg9dBC4KCTZnW49TfViDXjXyQO3OxW6uqWKg83fd\nyB306YdF6Lj3O2tL+qqxU6uzG9nugKO2/cshpF169+1dT6OFBx5XLDWj8pPV\nUCJhReoSd/LXcGWzZ9AfUsPbQK9yFt/Ub0+mdpkYeiTg78+FXlSKSdoaEBtR\nPutFdd1usyYhXXOqg0rwmhrBe4AZ5sHz56BZCvhg9Gpq/Guu5i8Nv+EdARxm\nHLKG\r\n=CCXU\r\n-----END PGP SIGNATURE-----\r\n" 360 | }, 361 | "engines": { 362 | "node": ">=10" 363 | } 364 | }, 365 | "1.0.1": { 366 | "name": "mkdirp", 367 | "version": "1.0.1", 368 | "devDependencies": { 369 | "require-inject": "^1.4.4", 370 | "tap": "^14.10.6" 371 | }, 372 | "bin": { 373 | "mkdirp": "bin/cmd.js" 374 | }, 375 | "dist": { 376 | "integrity": "sha512-epSaAwmT1sXxKNHFtzxrNEepE0qneey1uVReE15tN3Zvba3ifzx01eStvvvyb8dQeI50CVuGZ5qnSk2wMtsysg==", 377 | "shasum": "57828231711628a8a303f455bb7d79b32d9c9d4f", 378 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.1.tgz", 379 | "fileCount": 12, 380 | "unpackedSize": 19083, 381 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeK1GOCRA9TVsSAnZWagAAxVcP/iQv0DL9YSzDmkGXwOmX\nVw7YJsamnCnqqs1yLdbZVFfWKpc96DP1lckMUuENiTzoDHeoj3aen3Bs7Dh/\n+pSWoUxKIHkqqt1N++9cOLUk9z15pv8/TdNM8iMwcB6LN0IS//4PuQTKYqiC\nDJoNo0npu55ONZGSBLM6A+/tu0pN1l6sVNSk3IKXlR9CbL1ndjns2NP9UlTa\n9O/mpr8kdX4+2sv8SJU9f2FQsVBg99SeLyqsC2YvB1Bfws45SQo7DO8KBpKX\nQGJiQus3yB4/1JGX1O2XwUuwrGZn7/ZFksCKnqt0u4A7GPC2NGH8EhTSilFB\nopNzIMlhIvRbv4jfqqm7XsaIbhM52IiDvENignnUioz0VUiFjhSHvnxT2w2O\n051BmMSEPqc/yKTHVx640W4h78XMiDIDMaIR1yOSGYI/zrd15mzSDyTdkouL\nWkUy6RLrYKFrLVQNjj4Vt5KbM1W4KiDYNOrTsrPEOkAmB8XDmYlXPeVCb0AX\nkAjdbaCy7HRlOUKpYlUbkNA3y3Jzjkbt/wmfB5ZsA72L/QJk7PR+Q3a/+prU\n58c57mVQxvkOJnPgQPGSjWvpm0QM43DDahSeYvjN2G9PxMj5JiyNwYU8eyNU\nn+drSIcdvHIYPeL1ry1CAvFQEeXDCXmgI92m0diswZOYvENH7N33qxXWUUWa\nWyyZ\r\n=F+br\r\n-----END PGP SIGNATURE-----\r\n" 382 | }, 383 | "engines": { 384 | "node": ">=10" 385 | } 386 | }, 387 | "1.0.2": { 388 | "name": "mkdirp", 389 | "version": "1.0.2", 390 | "devDependencies": { 391 | "require-inject": "^1.4.4", 392 | "tap": "^14.10.6" 393 | }, 394 | "bin": { 395 | "mkdirp": "bin/cmd.js" 396 | }, 397 | "dist": { 398 | "integrity": "sha512-N2REVrJ/X/jGPfit2d7zea2J1pf7EAR5chIUcfHffAZ7gmlam5U65sAm76+o4ntQbSRdTjYf7qZz3chuHlwXEA==", 399 | "shasum": "5ccd93437619ca7050b538573fc918327eba98fb", 400 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.2.tgz", 401 | "fileCount": 12, 402 | "unpackedSize": 19084, 403 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeK1IaCRA9TVsSAnZWagAAC80P/jvpXscoPfndU9oDDNIe\n9YoGhXYq4zrl1Qb3dwqW7h6Q/UmYqFR53OfSl3crPbhf+CLsTVZh9xSdquMs\nJvyjAbIygAk+ciDnclQpJArR18YYd+k3NvLToUXNdgXqgpeiCpUrOLF8zpy0\nDfM4ud39hMImrgv73wklj0Oc2YV0H/XOZE5BT7WfAYuqJ8U8T689ywFp2MUf\niivson0GBpX2CGLwp+L98K8IDz7T1x1WnYWYaL8VeFt4l/XQnGRRh0WchjOp\nikV/Mv3pdfOS6sAE7Ie+D624ZYxd+utUPWwP33Q10h/PFnnbhxQreUb0tOTC\nVkUJPPQV/sssZ9s9qNgH5Nb3sY6eGWXChhHw2lZ+Nlrf1oyrDnV2dwYI3wdn\nOkMHo72jiwlKZ7i74VII0BXVrko72VPXoJ10/JH7n0IXaEuzkrfbsHeiVFDw\nTAYnI1Kbk0f/UJUQzP2mtCQDKLnHw/z67oz33diaP99xsxmiXh3rRR54HmX4\njkZ6wjQ6LUOBZ1V5HusG4Yd84NeYETQ9dRY7waxblxHVHr30QW/03AFlySQQ\nt8eDYvz/tC+G5/21pHtLCnx2F/vwDGgPaQDytfPKKRz2tOZp3yDe0gAXo5N7\nf8jrUkDuptHrAODS7KNnkUbk5SswL0rsfnItPFpt4XWf1FDw1sT+2xHONpui\nHmnO\r\n=5E1g\r\n-----END PGP SIGNATURE-----\r\n" 404 | }, 405 | "engines": { 406 | "node": ">=10" 407 | } 408 | }, 409 | "1.0.3": { 410 | "name": "mkdirp", 411 | "version": "1.0.3", 412 | "devDependencies": { 413 | "require-inject": "^1.4.4", 414 | "tap": "^14.10.6" 415 | }, 416 | "bin": { 417 | "mkdirp": "bin/cmd.js" 418 | }, 419 | "dist": { 420 | "integrity": "sha512-6uCP4Qc0sWsgMLy1EOqqS/3rjDHOEnsStVr/4vtAIK2Y5i2kA7lFFejYrpIyiN9w0pYf4ckeCYT9f1r1P9KX5g==", 421 | "shasum": "4cf2e30ad45959dddea53ad97d518b6c8205e1ea", 422 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.3.tgz", 423 | "fileCount": 12, 424 | "unpackedSize": 19130, 425 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeK1sGCRA9TVsSAnZWagAA7F4P/0xsz0O16oRicTgxQ8Ef\nRfrdeJeagGaI6qfo1gE5UL7dYWK2eL8/XNWKfRL/k9tN9JX/mTkEKcCdBkhk\n4l0tpOY1pZJPu5O6tW/Swl5i+swANNxs1Tdqdotts3dCzAfOULbemde/YIJj\nVobppuFiCjOZ1WoxFfhfHWChFR4a2coMzu8VXOJCJq57gM//nHiUg5yUtAxd\nBF5Ycm2d5guzQdhs37hHGfukiBv9XaO+8FuraDEMMVtFCjQnJhhYVKRq5u0y\nW3rx0IA+5pdaJzKab01bBz3fVMT+eTBFj201+3jky3J9vFXHi2azDSALFn3W\n/je5Qn6o/69tVqOt+GFOkSmL3PNDKgXbnlB//cjf19ziNFXYl5/ExhVmCHLc\nKDrDfRFzjkjchoiTYLFVH/WX79WgKKsM//2N0J69zljvidmGFqdYOzT57/Lk\nDBxG8JLKHFnKSmOZLVHxmfT3RnFY5phvtKWuVIT6G8QV2lczEe5d8GZAgzUR\nPyNhHPJPhpqeZpKmQ/smwV+imuzKn+GU95qf4ck/dQ6uCtUdaLOiHLwfegMf\n4qvmh7HJpkfk2fYAYV5x4Y+2OfNXLZNOvSMGID/MgJ8WBF5Qyz8shrGCzNat\ndIY0hQpeEvdzpeV4Hq6/+xaeJ4x+L1jA09Tk2Txw6/cOcserohhurvpUJ5s6\n0SEi\r\n=aA1p\r\n-----END PGP SIGNATURE-----\r\n" 426 | }, 427 | "engines": { 428 | "node": ">=10" 429 | } 430 | }, 431 | "0.5.2": { 432 | "name": "mkdirp", 433 | "version": "0.5.2", 434 | "dependencies": { 435 | "minimist": "^1.2.5" 436 | }, 437 | "devDependencies": { 438 | "mock-fs": "^3.7.0", 439 | "tap": "^5.4.2" 440 | }, 441 | "bin": { 442 | "mkdirp": "bin/cmd.js" 443 | }, 444 | "dist": { 445 | "integrity": "sha512-jczt4BrifxW743wRHJ05AnqIF52sDrHCAjTO66cFQStG1/jHMLFSGdAa3Rec21jdEObaPugcXfbh6Ammt2VQsw==", 446 | "shasum": "2e7138d794dfbd097d74c84c410c3edd9eec479f", 447 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.2.tgz", 448 | "fileCount": 84, 449 | "unpackedSize": 238925, 450 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJecPpxCRA9TVsSAnZWagAAuEUP/3W7GzvkixthBZxxTNDW\nQTOv8W2CmOR+bwXVHEW95OKsbh8V4vcnZ6sDyFTqP4tZ/Br+TDSROnlXxu6n\npVhhaDQ/t5j4qmSUP5Jn1KwjvAb0zWWIJHMJ+KDegchVb1xKn+aKTPbvQPbz\nWbU8upX40rbl0zqd+ZhJKx1am2xcEflaRe0bmAXnQO38C5Uu7xLi00VTmHLm\nJj1/KFdGTgr8ba2TGZvB8xhSh/WjAmLlm0XFO1OTbEtfpwJcab2k+G+dZKk6\nBFfNItE2N08ppwCG0iY2DGi3G9Emqh/XnLBgYrkxBxy05JDUi/v+bz8NuDkA\nIHkKhzRLFY5ZEYklX9giIuyNPxEULhBY13Skgq8hB24tZi3yPADUE8jzOljz\nnyBJARbpunpOnmpCM8smeTBx01vE5cFACgddKPe5HeM2+MaqNWYUjO5N9GVD\nAvkZv3i/4Ap7836XxuiWDErMDK0WMyclaVXeRvsBxDMq3Rd4D1leTgz+1FKs\n849emi7Ojmfa7Vo6nRf6zty/yYVMck+/VNcKSITjoSEP7xyYdH+AuR7oT4C3\nvX84Twg+j+OSApgU/Za2IfO9NlA9Wvy3o/ibY9Ez/I9GtC9S6dV0aIlylsPO\n+f0dgujGVb9XWGSuEnWwfF0zBCOKdzu0P1LsG0AIlNFLNKQENBopIX5zOu/L\n+12b\r\n=hDKo\r\n-----END PGP SIGNATURE-----\r\n" 451 | }, 452 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 453 | }, 454 | "0.5.3": { 455 | "name": "mkdirp", 456 | "version": "0.5.3", 457 | "dependencies": { 458 | "minimist": "^1.2.5" 459 | }, 460 | "devDependencies": { 461 | "mock-fs": "^3.7.0", 462 | "tap": "^5.4.2" 463 | }, 464 | "bin": { 465 | "mkdirp": "bin/cmd.js" 466 | }, 467 | "dist": { 468 | "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", 469 | "shasum": "5a514b7179259287952881e94410ec5465659f8c", 470 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", 471 | "fileCount": 6, 472 | "unpackedSize": 7559, 473 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJecPqyCRA9TVsSAnZWagAATvcQAJGnQf0PNBHPrlJoeNpR\n/fL18ar6/fwQ38/2W4IwITUclQb0xaV6r7lfIkOzveDx3DXOZI/EaTUBdVY9\nVuqozPcHgSVAxLrS+ScXBSp15lDXnqQwgYsE1AjG6S4EFepvTKjYQto9ZORg\nw4LVxupl5Yqw1+8bDTycXZ8DT9VZz0DB6/h75is27yRfbhpKIHyoH9e3lyOu\nR6pIa9A6cMK4ezM8otrr9V6OPej6h1vIMXW0nPCh4K5/7r6Ajqi83CH1BK5s\nBvNoto7cNOHUZQmKDrTsfxYo/zHXxbeY7oA6pwcg2ZKm124UtA7Vrds9HScO\nlqf5Uv7k80KdBJ7cNsoKJBBX0/wDbTD1YUY0gTcojsmFSwn6m22V2HOnpsd9\nyInBFo6/Rp9NSTH7xR/CVpSycnyCLy71KuEUfi5Rypvj46c5zapqMAxQ0b3c\nHKxlzVLTH36xpBLOq8MWh2ddWMZfL0XJA7iEPOHKfwSgO/WkT5/N6Qk2LZ8M\nqWk1L8p/g5tAVCmYCU4P/CXe0ka8XZ9ubqg76rD3XazDCULS0EStBnxY/XUE\ntzBXBXhVv9eeudtZgjWIBxNNPoEPrNsW3/1reX1ab10qdbz8BsIGNzYcZVSg\nEyIq8E5WtZ94Mr0b/Y99Ni9isCT/RySvYQ1BTnGLsvqaF4IH8YbHP+UDMYAQ\nKCkY\r\n=l2Wn\r\n-----END PGP SIGNATURE-----\r\n" 474 | }, 475 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 476 | }, 477 | "0.5.4": { 478 | "name": "mkdirp", 479 | "version": "0.5.4", 480 | "dependencies": { 481 | "minimist": "^1.2.5" 482 | }, 483 | "devDependencies": { 484 | "mock-fs": "^3.7.0", 485 | "tap": "^5.4.2" 486 | }, 487 | "bin": { 488 | "mkdirp": "bin/cmd.js" 489 | }, 490 | "dist": { 491 | "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", 492 | "shasum": "fd01504a6797ec5c9be81ff43d204961ed64a512", 493 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", 494 | "fileCount": 6, 495 | "unpackedSize": 7617, 496 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeePd/CRA9TVsSAnZWagAAF9sQAKIMYrwLQQPHflBZSXKm\nkbTBW+owq0NpOT4vxuh65C0A2u4Q5+5cwB4h3e+qk9tEcboaABaP32qTYKy7\nsqILBVbDGsHE94Kmx+uuDjjEhWG3sQCuWNDzbu66SusRMyaBunhuW2y/t6GV\nuLgyF2qOdOLP9QJ8pKuaAuTux5Zv5F9LolfXWBrKHpqV2nqHVwRFzd/yq7zE\nWnDXT/MAEkRHH5xfIzNL0EQsi73WDfwkGNVXw5i8OvLwfvbZQo7zvdrbcwSP\nuD0OKRj2pmPmMGcuZsqKOWmxYZA4WDSD37aDS7Rw/hnCF55T4UU76ggbfr1M\n33w2aSipHgfpMCrUlYfpSRqWFENMzKmk3tPLb1DPkjmWvaKst0BR5UvC1Vqq\nHxhw+J9rVjojqgtMIo2Ihs/dMT/kuHv1BjlQ1zzucz1WtgCVvLRWs1W4RWdo\ni9tNml6YN2Cr0IqxpBRVHtAm26zHw8dQFUFG0Bmj671RNhWrL5igXDnVXk95\n/eSYtDoiaeIcwmTDrMHPmRIeNP+C8s362Th9FHofSd3qZ2wUlPL6LfEfIxs3\nudzrwdMtapwjMgJ8xoAfdJuIo/UecCvfXvRe/qjLDSe3ZcMh9EmYoO86inb8\nnSAzbBpCkzF4OzKwdL8v1HzywzXDA48acYwqFsDFHmYv8AHmQkRiIxBnXGsy\nM2UD\r\n=v0+5\r\n-----END PGP SIGNATURE-----\r\n" 497 | }, 498 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)" 499 | }, 500 | "1.0.4": { 501 | "name": "mkdirp", 502 | "version": "1.0.4", 503 | "devDependencies": { 504 | "require-inject": "^1.4.4", 505 | "tap": "^14.10.7" 506 | }, 507 | "bin": { 508 | "mkdirp": "bin/cmd.js" 509 | }, 510 | "dist": { 511 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 512 | "shasum": "3eb5ed62622756d79a5f0e2a221dfebad75c2f7e", 513 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 514 | "fileCount": 12, 515 | "unpackedSize": 19088, 516 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeh2xNCRA9TVsSAnZWagAAFiMQAIeSoosHZP0eVV6P64NS\nnXRn6I82OfqeOhU9DmWKLO4csIwPiBfLE+ZL4C0bHxeWllyD1HJYB755v38Y\nG8g+GgdkCKeUDSGsL5kcVWXclMydN14FooYF5vjiXig+WNFevum7Ul8Sduvy\np+/o95aKK4Q2xu9JGEVFmmhRF7dKUsb1Q6npcDrdmrDODWHfKQ6t6DXDW3M/\nXYZ/UblSBLOQkiqDKpilB+WWbLe95KOk7pPX8v1dZR94h9RTVoVkDXXX3TLQ\nJAPJ1FrYcIWfBZS3OgbbWo3Baer97ysxBc9zDpXJC3gHbrRMDJnbhnvcxnt/\n41l7zwnPF9JX0gV2Ol8FUSvAfE7FWDQzlpqxfn13g+gPmU42RbKBh4fuhw63\nMTPcvxwjTrDkLecTcbvaNv9WtZN8mJyFhxzjvYPr24ewwsdxj0q7fTMHWMhX\n2edEhaQ1sJVQV49rSPwJwE7+mZK/2D1accQIPa4PdePLTC6LSzviAROJz8eB\ntrcRY5kS3rTJA+Kdw8vByeWV1/eavBMNJL69gdYBgKfX6E0WiXb0YdFSX9DB\njT4CoKYPPKjUrsQdO4NcPPRgmnAlHS9U5nIPbxAhn40eb9hTs9GgN3ZnZcgL\nr4LkQaku79npoNqW2vmxJSJ9axe2qUYE6OycAlKbGiHqP36k3e9GNcK7c9aU\npS9s\r\n=RHg1\r\n-----END PGP SIGNATURE-----\r\n" 517 | }, 518 | "engines": { 519 | "node": ">=10" 520 | } 521 | }, 522 | "0.5.5": { 523 | "name": "mkdirp", 524 | "version": "0.5.5", 525 | "dependencies": { 526 | "minimist": "^1.2.5" 527 | }, 528 | "devDependencies": { 529 | "mock-fs": "^3.7.0", 530 | "tap": "^5.4.2" 531 | }, 532 | "bin": { 533 | "mkdirp": "bin/cmd.js" 534 | }, 535 | "dist": { 536 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", 537 | "shasum": "d91cefd62d1436ca0f41620e251288d420099def", 538 | "tarball": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", 539 | "fileCount": 6, 540 | "unpackedSize": 7531, 541 | "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJeh256CRA9TVsSAnZWagAAlzoP/iLuWMWGqLMB70xGqJjf\nCnzJKbwcC2F1ek5f5gL7VLncqA5AoOER5pGmiLw9AhDFU/CK40uutHnujivh\nBNuO+uoweFrBsgS0bKCUBdSWzVABItvv3eJ2l6j7s9mbhiZgmu7weOT0WMcs\nZi1nmsMDeyqxCZUIwz2FJFOlU3QdaiuaXCnW8L/VaxdwUgy7rlLbu4qCKYlP\n8IdkkSUpXksaw6KE7QfFOmc+lM62CaNxIzto/B03ovHaUAUQrAeow8Ly/n8j\nhg+CiTScKeBvjKwMskoeYdV56cDsMQLyLLivlQt+AMN2KcHcE0qqvsvgxGwe\nBXTfvW9hpVsYLj6rICcMkU9yHSRlRkQ4fAInXSwvpzzzIPfavd9zSuFbyMsF\nzcZBEgZu0gbilehWhwwvTZ3Bd8WwxliKnkfgVLN+cOO69JSXJCXwIA7n3N06\nGQVKrGqDkLfKD6nSHy8krDknY0MvOhCDcwy2fNh1IdWMFnmF80x5Huhmxr55\npTkwxQr3IGOyePRqu5B2pXIW7HscnAerXAkccyX/FzmUlw3UIaPlPjNnUJDR\nopPRUYOAMHkg0LtrtVnPQCK0YU+siUzIDSjXnYV/fR0416r+RQXhemdgO6Ey\nqx8tIHVSvdKBLOaCzZYuqkDOIJgWC/qwCudv3Hxbj1gwEcCb3X5IvoK/1ok3\nt18G\r\n=XnRj\r\n-----END PGP SIGNATURE-----\r\n" 542 | } 543 | } 544 | }, 545 | "modified": "2020-04-22T06:03:23.719Z", 546 | "_cached": true, 547 | "_contentLength": 0 548 | } 549 | -------------------------------------------------------------------------------- /test/get-dep-spec.js: -------------------------------------------------------------------------------- 1 | const t = require('tap') 2 | const getDepSpec = require('../lib/get-dep-spec.js') 3 | 4 | t.equal(getDepSpec({ 5 | dependencies: { dep: '1' }, 6 | }, 'dep'), '1') 7 | 8 | t.equal(getDepSpec({ 9 | optionalDependencies: { dep: '1' }, 10 | }, 'dep'), '1') 11 | 12 | t.equal(getDepSpec({ 13 | dependencies: null, 14 | optionalDependencies: { dep: '1' }, 15 | }, 'dep'), '1', 'allows dependencies to be null') 16 | 17 | t.equal(getDepSpec({ 18 | peerDependencies: { dep: '1' }, 19 | }, 'dep'), '1') 20 | 21 | t.equal(getDepSpec({ 22 | dependencies: null, 23 | optioanlDependencies: null, 24 | peerDependencies: { dep: '1' }, 25 | }, 'dep'), '1', 'allows optionalDependencies to be null') 26 | 27 | t.equal(getDepSpec({ 28 | dependencies: { dep: '1' }, 29 | optionalDependencies: { dep: '2' }, 30 | }, 'dep'), '1', 'prefer prod deps over optional') 31 | 32 | t.equal(getDepSpec({ 33 | dependencies: { dep: '1' }, 34 | peerDependencies: { dep: '2' }, 35 | }, 'dep'), '1', 'prefer prod deps over peer') 36 | 37 | t.equal(getDepSpec({ 38 | optionalDependencies: { dep: '1' }, 39 | peerDependencies: { dep: '2' }, 40 | }, 'dep'), '1', 'prefer optional deps over peer') 41 | 42 | t.equal(getDepSpec({ 43 | devDependencies: { dep: '1' }, 44 | }, 'dep'), null, 'ignore dev') 45 | -------------------------------------------------------------------------------- /test/hash.js: -------------------------------------------------------------------------------- 1 | const t = require('tap') 2 | const hash = require('../lib/hash.js') 3 | 4 | t.equal( 5 | hash({ name: 'name', source: 'string' }), 6 | 'H5g4PQXxK9hZai4+htDRkAmOHMHwtnFkWPhtKeqzj6+4h04Ydn8BTmtdGdlHNQYC1lJalvPtlsCpZU+iJyNATQ==') 7 | t.equal( 8 | hash({ name: 'number', source: 123 }), 9 | 'ogXIWvAyjuIKtDbQmQD29hgUJackr36y6oWohHLx2hLqnhzNSQBhq/a5sjH6myWL/s5Yu2IlUcowvNZjciFwoQ==') 10 | 11 | t.equal(hash({ name: 'x', source: 'y' }), hash({ source: 'y', name: 'x' }), 12 | 'not order dependent') 13 | t.equal(hash({ name: 'x', source: 123 }), hash({ source: 123, name: 'x' }), 14 | 'not order dependent') 15 | 16 | t.not(hash({ name: 'x', source: 123 }), hash({ name: 'x', source: '123' }), 17 | 'different hashes for different source types') 18 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const t = require('tap') 2 | const requireInject = require('require-inject') 3 | const packuments = require('./fixtures/packuments/index.js') 4 | const advisories = require('./fixtures/advisories/index.js') 5 | const pacote = { 6 | packument: async (name) => { 7 | if (packuments[name]) { 8 | return packuments[name] 9 | } 10 | throw Object.assign(new Error('not found'), { 11 | code: 'E404', 12 | }) 13 | }, 14 | } 15 | const cacache = require('cacache') 16 | const Advisory = require('../lib/advisory.js') 17 | 18 | const cache = t.testdir() 19 | 20 | const Calculator = requireInject('../lib/index.js', { pacote }) 21 | 22 | t.test('basic instantiation', t => { 23 | const defaults = new Calculator() 24 | t.equal(defaults.cache, require('os').homedir() + '/.npm/_cacache') 25 | t.strictSame(defaults.options, { cache: defaults.cache }) 26 | const calc = new Calculator({ cache }) 27 | t.strictSame(calc.options, { cache }) 28 | t.equal(calc.cache, cache) 29 | t.end() 30 | }) 31 | 32 | t.test('calculate fresh', async t => { 33 | const calc = new Calculator({ cache }) 34 | const minimistFresh = await calc.calculate('minimist', advisories.minimist) 35 | t.match(minimistFresh, { 36 | constructor: Advisory, 37 | updated: true, 38 | source: 1179, 39 | name: 'minimist', 40 | dependency: 'minimist', 41 | type: 'advisory', 42 | range: '<0.2.1 || >=1.0.0 <1.2.3', 43 | id: '8MDgP3O3yM8t8dcQHSMUtmH4UKJrKhWmsmV44L4YChIzoahEo+G6j24b+4BPItZck5h5zQFPFD39kOC/789lfA==', 44 | }) 45 | // calculate another one for same package to hit the packument memoizing 46 | const otherMinimistAdvisory = { 47 | ...advisories.minimist, 48 | id: 123456, 49 | } 50 | const otherMinimistVuln = await calc.calculate('minimist', otherMinimistAdvisory) 51 | t.match(otherMinimistVuln, { 52 | constructor: Advisory, 53 | source: 123456, 54 | name: 'minimist', 55 | dependency: 'minimist', 56 | type: 'advisory', 57 | range: '<0.2.1 || >=1.0.0 <1.2.3', 58 | id: 'WNi+Ammra045Ltb3M04AEe31yaYdjqUffX/iwhuagBKRTyZCzaNihh0prxpc4kVhVK6wXV1XDSXTGEqt1JusCA==', 59 | updated: true, 60 | }) 61 | 62 | const mkdirpFresh = await calc.calculate('mkdirp', minimistFresh) 63 | t.match(mkdirpFresh, { 64 | constructor: Advisory, 65 | type: 'metavuln', 66 | source: minimistFresh.id, 67 | name: 'mkdirp', 68 | dependency: 'minimist', 69 | title: 'Depends on vulnerable versions of minimist', 70 | url: null, 71 | severity: minimistFresh.severity, 72 | range: '0.4.1 - 0.5.1', 73 | id: 'dOqvv9Jcyhu8PueSJZB+eZ0G/JI7mVomMmOBSku5SA7OScjvKmHq9jcLVFKmH1wsW2LcZATEOArlMxt/fa5LmA==', 74 | updated: true, 75 | }) 76 | }) 77 | 78 | t.test('handle cache failures', async t => { 79 | const { get, put } = cacache 80 | t.teardown(() => Object.assign(cacache, { get, put })) 81 | cacache.get = async () => { 82 | throw new Error('nope') 83 | } 84 | cacache.put = async () => { 85 | throw new Error('nope') 86 | } 87 | 88 | const calc = new Calculator({ cache }) 89 | const minimistFresh = await calc.calculate('minimist', advisories.minimist) 90 | t.match(minimistFresh, { 91 | constructor: Advisory, 92 | source: 1179, 93 | name: 'minimist', 94 | dependency: 'minimist', 95 | type: 'advisory', 96 | range: '<0.2.1 || >=1.0.0 <1.2.3', 97 | id: '8MDgP3O3yM8t8dcQHSMUtmH4UKJrKhWmsmV44L4YChIzoahEo+G6j24b+4BPItZck5h5zQFPFD39kOC/789lfA==', 98 | updated: true, // <-- "updated" because cache read failed 99 | }) 100 | await calc.calculate('mkdirp', minimistFresh) 101 | }) 102 | 103 | t.test('calculate from cache', async t => { 104 | const calc = new Calculator({ cache }) 105 | const minimistCached = await calc.calculate('minimist', advisories.minimist) 106 | t.match(minimistCached, { 107 | constructor: Advisory, 108 | updated: false, 109 | source: 1179, 110 | name: 'minimist', 111 | dependency: 'minimist', 112 | type: 'advisory', 113 | range: '<0.2.1 || >=1.0.0 <1.2.3', 114 | id: '8MDgP3O3yM8t8dcQHSMUtmH4UKJrKhWmsmV44L4YChIzoahEo+G6j24b+4BPItZck5h5zQFPFD39kOC/789lfA==', 115 | }) 116 | const mkdirpCached = await calc.calculate('mkdirp', minimistCached) 117 | t.match(mkdirpCached, { 118 | constructor: Advisory, 119 | type: 'metavuln', 120 | source: minimistCached.id, 121 | name: 'mkdirp', 122 | dependency: 'minimist', 123 | title: 'Depends on vulnerable versions of minimist', 124 | url: null, 125 | severity: minimistCached.severity, 126 | range: '0.4.1 - 0.5.1', 127 | id: 'dOqvv9Jcyhu8PueSJZB+eZ0G/JI7mVomMmOBSku5SA7OScjvKmHq9jcLVFKmH1wsW2LcZATEOArlMxt/fa5LmA==', 128 | updated: false, 129 | }) 130 | const mkdirpCached2 = await calc.calculate('mkdirp', minimistCached) 131 | t.equal(mkdirpCached.packument, mkdirpCached2.packument, 132 | 'reuse packument rather than make an extra request') 133 | }) 134 | 135 | t.test('packument not found', async t => { 136 | const calc = new Calculator({ cache }) 137 | const notSemver = await calc.calculate('not-semver', advisories.semver) 138 | t.match(notSemver, { 139 | source: 31, 140 | name: 'not-semver', 141 | dependency: 'not-semver', 142 | title: 'Regular Expression Denial of Service', 143 | url: 'https://npmjs.com/advisories/31', 144 | severity: 'moderate', 145 | versions: [], 146 | vulnerableVersions: [], 147 | range: '<4.3.2', 148 | id: 'TEayTAF88mYJ/wy04iwwifoEUL/+mmrrYoE4EbGSe7s9nbZ8+zQQVqwnhh1TwzEFwV/DoaVAjHTdt+GXvT04lg==', 149 | }) 150 | }) 151 | --------------------------------------------------------------------------------