├── .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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------