diff --git a/.eslintrc.js b/.eslintrc.js
index 0e8ad007..5db9f815 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,5 +1,7 @@
/* This file is automatically added by @npmcli/template-oss. Do not edit. */
+'use strict'
+
const { readdirSync: readdir } = require('fs')
const localConfigs = readdir(__dirname)
diff --git a/.eslintrc.local.js b/.eslintrc.local.js
new file mode 100644
index 00000000..5b7c98ea
--- /dev/null
+++ b/.eslintrc.local.js
@@ -0,0 +1,18 @@
+'use strict'
+
+module.exports = {
+ overrides: [
+ {
+ files: ['bin/**', 'classes/**', 'functions/**', 'internal/**', 'ranges/**'],
+ rules: {
+ 'import/no-extraneous-dependencies': [
+ 'error',
+ {
+ devDependencies: false,
+ },
+ ],
+ 'import/no-nodejs-modules': ['error'],
+ },
+ },
+ ],
+}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 96d8eafb..8da2a452 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,7 +4,7 @@ version: 2
updates:
- package-ecosystem: npm
- directory: "/"
+ directory: /
schedule:
interval: daily
allow:
diff --git a/.github/matchers/tap.json b/.github/matchers/tap.json
new file mode 100644
index 00000000..2c81ea98
--- /dev/null
+++ b/.github/matchers/tap.json
@@ -0,0 +1,32 @@
+{
+ "//@npmcli/template-oss": "This file is automatically added by @npmcli/template-oss. Do not edit.",
+ "problemMatcher": [
+ {
+ "owner": "tap",
+ "pattern": [
+ {
+ "regexp": "^\\s*not ok \\d+ - (.*)",
+ "message": 1
+ },
+ {
+ "regexp": "^\\s*---"
+ },
+ {
+ "regexp": "^\\s*at:"
+ },
+ {
+ "regexp": "^\\s*line:\\s*(\\d+)",
+ "line": 1
+ },
+ {
+ "regexp": "^\\s*column:\\s*(\\d+)",
+ "column": 1
+ },
+ {
+ "regexp": "^\\s*file:\\s*(.*)",
+ "file": 1
+ }
+ ]
+ }
+ ]
+}
diff --git a/.github/settings.yml b/.github/settings.yml
index 1019e26f..adbef7e6 100644
--- a/.github/settings.yml
+++ b/.github/settings.yml
@@ -1,2 +1,52 @@
----
-_extends: '.github:npm-cli/settings.yml'
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+repository:
+ allow_merge_commit: false
+ allow_rebase_merge: true
+ allow_squash_merge: true
+ squash_merge_commit_title: PR_TITLE
+ squash_merge_commit_message: PR_BODY
+ delete_branch_on_merge: true
+ enable_automated_security_fixes: true
+ enable_vulnerability_alerts: true
+
+branches:
+ - name: main
+ protection:
+ required_status_checks: null
+ enforce_admins: true
+ required_pull_request_reviews:
+ required_approving_review_count: 1
+ require_code_owner_reviews: true
+ require_last_push_approval: true
+ dismiss_stale_reviews: true
+ restrictions:
+ apps: []
+ users: []
+ teams: [ "cli-team" ]
+ - name: latest
+ protection:
+ required_status_checks: null
+ enforce_admins: true
+ required_pull_request_reviews:
+ required_approving_review_count: 1
+ require_code_owner_reviews: true
+ require_last_push_approval: true
+ dismiss_stale_reviews: true
+ restrictions:
+ apps: []
+ users: []
+ teams: [ "cli-team" ]
+ - name: release/v*
+ protection:
+ required_status_checks: null
+ enforce_admins: true
+ required_pull_request_reviews:
+ required_approving_review_count: 1
+ require_code_owner_reviews: true
+ require_last_push_approval: true
+ dismiss_stale_reviews: true
+ restrictions:
+ apps: []
+ users: []
+ teams: [ "cli-team" ]
diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml
index 03dcd937..8b8f3748 100644
--- a/.github/workflows/audit.yml
+++ b/.github/workflows/audit.yml
@@ -5,23 +5,35 @@ name: Audit
on:
workflow_dispatch:
schedule:
- # "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1
- - cron: "0 1 * * 1"
+ # "At 08:00 UTC (01:00 PT) on Monday" https://crontab.guru/#0_8_*_*_1
+ - cron: "0 8 * * 1"
jobs:
audit:
+ name: Audit Dependencies
+ if: github.repository_owner == 'npm'
runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
steps:
- - uses: actions/checkout@v3
- - name: Setup git user
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Git User
run: |
- git config --global user.email "ops+npm-cli@npmjs.com"
- git config --global user.name "npm cli ops bot"
- - uses: actions/setup-node@v3
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
with:
- node-version: 16.x
- - name: Update npm to latest
- run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- - run: npm -v
- - run: npm i --ignore-scripts --no-audit --no-fund --package-lock
- - run: npm audit
+ node-version: 18.x
+ - name: Install npm@8
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund --package-lock
+ - name: Run Production Audit
+ run: npm audit --omit=dev
+ - name: Run Full Audit
+ run: npm audit --audit-level=none
diff --git a/.github/workflows/ci-release.yml b/.github/workflows/ci-release.yml
new file mode 100644
index 00000000..98b70866
--- /dev/null
+++ b/.github/workflows/ci-release.yml
@@ -0,0 +1,216 @@
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: CI - Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ ref:
+ required: true
+ type: string
+ default: main
+ workflow_call:
+ inputs:
+ ref:
+ required: true
+ type: string
+ check-sha:
+ required: true
+ type: string
+
+jobs:
+ lint-all:
+ name: Lint All
+ if: github.repository_owner == 'npm'
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Get Workflow Job
+ uses: actions/github-script@v6
+ if: inputs.check-sha
+ id: check-output
+ env:
+ JOB_NAME: "Lint All"
+ MATRIX_NAME: ""
+ with:
+ script: |
+ const { owner, repo } = context.repo
+
+ const { data } = await github.rest.actions.listJobsForWorkflowRun({
+ owner,
+ repo,
+ run_id: context.runId,
+ per_page: 100
+ })
+
+ const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME
+ const job = data.jobs.find(j => j.name.endsWith(jobName))
+ const jobUrl = job?.html_url
+
+ const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ inputs.check-sha }}`
+
+ let summary = `This check is assosciated with ${shaUrl}\n\n`
+
+ if (jobUrl) {
+ summary += `For run logs, click here: ${jobUrl}`
+ } else {
+ summary += `Run logs could not be found for a job with name: "${jobName}"`
+ }
+
+ return { summary }
+ - name: Create Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ id: check
+ if: inputs.check-sha
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ status: in_progress
+ name: Lint All
+ sha: ${{ inputs.check-sha }}
+ output: ${{ steps.check-output.outputs.result }}
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ inputs.ref }}
+ - name: Setup Git User
+ run: |
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+ - name: Install npm@8
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Lint
+ run: npm run lint --ignore-scripts
+ - name: Post Lint
+ run: npm run postlint --ignore-scripts
+ - name: Conclude Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ if: steps.check.outputs.check_id && always()
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ conclusion: ${{ job.status }}
+ check_id: ${{ steps.check.outputs.check_id }}
+
+ test-all:
+ name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
+ if: github.repository_owner == 'npm'
+ strategy:
+ fail-fast: false
+ matrix:
+ platform:
+ - name: Linux
+ os: ubuntu-latest
+ shell: bash
+ - name: macOS
+ os: macos-latest
+ shell: bash
+ - name: Windows
+ os: windows-latest
+ shell: cmd
+ node-version:
+ - 10.0.0
+ - 10.x
+ - 12.x
+ - 14.x
+ - 16.x
+ - 18.x
+ runs-on: ${{ matrix.platform.os }}
+ defaults:
+ run:
+ shell: ${{ matrix.platform.shell }}
+ steps:
+ - name: Get Workflow Job
+ uses: actions/github-script@v6
+ if: inputs.check-sha
+ id: check-output
+ env:
+ JOB_NAME: "Test All"
+ MATRIX_NAME: " - ${{ matrix.platform.name }} - ${{ matrix.node-version }}"
+ with:
+ script: |
+ const { owner, repo } = context.repo
+
+ const { data } = await github.rest.actions.listJobsForWorkflowRun({
+ owner,
+ repo,
+ run_id: context.runId,
+ per_page: 100
+ })
+
+ const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME
+ const job = data.jobs.find(j => j.name.endsWith(jobName))
+ const jobUrl = job?.html_url
+
+ const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ inputs.check-sha }}`
+
+ let summary = `This check is assosciated with ${shaUrl}\n\n`
+
+ if (jobUrl) {
+ summary += `For run logs, click here: ${jobUrl}`
+ } else {
+ summary += `Run logs could not be found for a job with name: "${jobName}"`
+ }
+
+ return { summary }
+ - name: Create Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ id: check
+ if: inputs.check-sha
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ status: in_progress
+ name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
+ sha: ${{ inputs.check-sha }}
+ output: ${{ steps.check-output.outputs.result }}
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ inputs.ref }}
+ - name: Setup Git User
+ run: |
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: ${{ matrix.node-version }}
+ - name: Update Windows npm
+ # node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
+ if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12.') || startsWith(matrix.node-version, '14.'))
+ run: |
+ curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
+ tar xf npm-7.5.4.tgz
+ cd package
+ node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
+ cd ..
+ rmdir /s /q package
+ - name: Install npm@7
+ if: startsWith(matrix.node-version, '10.')
+ run: npm i --prefer-online --no-fund --no-audit -g npm@7
+ - name: Install npm@8
+ if: ${{ !startsWith(matrix.node-version, '10.') }}
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Add Problem Matcher
+ run: echo "::add-matcher::.github/matchers/tap.json"
+ - name: Test
+ run: npm test --ignore-scripts
+ - name: Conclude Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ if: steps.check.outputs.check_id && always()
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ conclusion: ${{ job.status }}
+ check_id: ${{ steps.check.outputs.check_id }}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 538e46a0..bb473086 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,65 +5,84 @@ name: CI
on:
workflow_dispatch:
pull_request:
- branches:
- - '*'
push:
branches:
- main
- latest
+ - release/v*
schedule:
- # "At 02:00 on Monday" https://crontab.guru/#0_2_*_*_1
- - cron: "0 2 * * 1"
+ # "At 09:00 UTC (02:00 PT) on Monday" https://crontab.guru/#0_9_*_*_1
+ - cron: "0 9 * * 1"
jobs:
lint:
+ name: Lint
+ if: github.repository_owner == 'npm'
runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
steps:
- - uses: actions/checkout@v3
- - name: Setup git user
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Git User
run: |
- git config --global user.email "ops+npm-cli@npmjs.com"
- git config --global user.name "npm cli ops bot"
- - uses: actions/setup-node@v3
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
with:
- node-version: 16.x
- - name: Update npm to latest
- run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- - run: npm -v
- - run: npm i --ignore-scripts --no-audit --no-fund
- - run: npm run lint
+ node-version: 18.x
+ - name: Install npm@8
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Lint
+ run: npm run lint --ignore-scripts
+ - name: Post Lint
+ run: npm run postlint --ignore-scripts
test:
+ name: Test - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
+ if: github.repository_owner == 'npm'
strategy:
fail-fast: false
matrix:
+ platform:
+ - name: Linux
+ os: ubuntu-latest
+ shell: bash
+ - name: macOS
+ os: macos-latest
+ shell: bash
+ - name: Windows
+ os: windows-latest
+ shell: cmd
node-version:
- 10.0.0
- 10.x
- 12.x
- 14.x
- 16.x
- platform:
- - os: ubuntu-latest
- shell: bash
- - os: macos-latest
- shell: bash
- - os: windows-latest
- shell: cmd
+ - 18.x
runs-on: ${{ matrix.platform.os }}
defaults:
run:
shell: ${{ matrix.platform.shell }}
steps:
- - uses: actions/checkout@v3
- - name: Setup git user
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Git User
run: |
- git config --global user.email "ops+npm-cli@npmjs.com"
- git config --global user.name "npm cli ops bot"
- - uses: actions/setup-node@v3
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- - name: Update to workable npm (windows)
+ - name: Update Windows npm
# node 12 and 14 ship with npm@6, which is known to fail when updating itself in windows
if: matrix.platform.os == 'windows-latest' && (startsWith(matrix.node-version, '12.') || startsWith(matrix.node-version, '14.'))
run: |
@@ -73,13 +92,17 @@ jobs:
node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
cd ..
rmdir /s /q package
- - name: Update npm to 7
- # If we do test on npm 10 it needs npm7
+ - name: Install npm@7
if: startsWith(matrix.node-version, '10.')
run: npm i --prefer-online --no-fund --no-audit -g npm@7
- - name: Update npm to latest
+ - name: Install npm@8
if: ${{ !startsWith(matrix.node-version, '10.') }}
- run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- - run: npm -v
- - run: npm i --ignore-scripts --no-audit --no-fund
- - run: npm test --ignore-scripts
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Add Problem Matcher
+ run: echo "::add-matcher::.github/matchers/tap.json"
+ - name: Test
+ run: npm test --ignore-scripts
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 56cd7b9c..21244879 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -1,20 +1,21 @@
# This file is automatically added by @npmcli/template-oss. Do not edit.
-name: "CodeQL"
+name: CodeQL
on:
push:
branches:
- main
- latest
+ - release/v*
pull_request:
- # The branches below must be a subset of the branches above
branches:
- main
- latest
+ - release/v*
schedule:
- # "At 03:00 on Monday" https://crontab.guru/#0_3_*_*_1
- - cron: "0 3 * * 1"
+ # "At 10:00 UTC (03:00 PT) on Monday" https://crontab.guru/#0_10_*_*_1
+ - cron: "0 10 * * 1"
jobs:
analyze:
@@ -24,21 +25,16 @@ jobs:
actions: read
contents: read
security-events: write
-
- strategy:
- fail-fast: false
- matrix:
- language: [ javascript ]
-
steps:
- - uses: actions/checkout@v3
- - name: Setup git user
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Git User
run: |
- git config --global user.email "ops+npm-cli@npmjs.com"
- git config --global user.name "npm cli ops bot"
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
with:
- languages: ${{ matrix.language }}
+ languages: javascript
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/post-dependabot.yml b/.github/workflows/post-dependabot.yml
index 0372a7a9..03c85681 100644
--- a/.github/workflows/post-dependabot.yml
+++ b/.github/workflows/post-dependabot.yml
@@ -1,43 +1,121 @@
# This file is automatically added by @npmcli/template-oss. Do not edit.
-name: Post Dependabot Actions
+name: Post Dependabot
on: pull_request
-# https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
contents: write
jobs:
- template-oss-apply:
+ template-oss:
+ name: template-oss
+ if: github.repository_owner == 'npm' && github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
- if: github.actor == 'dependabot[bot]'
+ defaults:
+ run:
+ shell: bash
steps:
- - uses: actions/checkout@v3
- - name: Setup git user
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ github.event.pull_request.head.ref }}
+ - name: Setup Git User
run: |
- git config --global user.email "ops+npm-cli@npmjs.com"
- git config --global user.name "npm cli ops bot"
- - uses: actions/setup-node@v3
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
with:
- node-version: 16.x
- - name: Update npm to latest
- run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- - run: npm -v
- - name: Dependabot metadata
+ node-version: 18.x
+ - name: Install npm@8
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Fetch Dependabot Metadata
id: metadata
- uses: dependabot/fetch-metadata@v1.1.1
+ uses: dependabot/fetch-metadata@v1
with:
- github-token: "${{ secrets.GITHUB_TOKEN }}"
- - name: npm install and commit
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ # Dependabot can update multiple directories so we output which directory
+ # it is acting on so we can run the command for the correct root or workspace
+ - name: Get Dependabot Directory
if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
+ id: flags
+ run: |
+ dependabot_dir="${{ steps.metadata.outputs.directory }}"
+ if [[ "$dependabot_dir" == "/" ]]; then
+ echo "workspace=-iwr" >> $GITHUB_OUTPUT
+ else
+ # strip leading slash from directory so it works as a
+ # a path to the workspace flag
+ echo "workspace=-w ${dependabot_dir#/}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Apply Changes
+ if: steps.flags.outputs.workspace
+ id: apply
+ run: |
+ npm run template-oss-apply ${{ steps.flags.outputs.workspace }}
+ if [[ `git status --porcelain` ]]; then
+ echo "changes=true" >> $GITHUB_OUTPUT
+ fi
+ # This only sets the conventional commit prefix. This workflow can't reliably determine
+ # what the breaking change is though. If a BREAKING CHANGE message is required then
+ # this PR check will fail and the commit will be amended with stafftools
+ if [[ "${{ steps.metadata.outputs.update-type }}" == "version-update:semver-major" ]]; then
+ prefix='feat!'
+ else
+ prefix='chore'
+ fi
+ echo "message=$prefix: postinstall for dependabot template-oss PR" >> $GITHUB_OUTPUT
+
+ # This step will fail if template-oss has made any workflow updates. It is impossible
+ # for a workflow to update other workflows. In the case it does fail, we continue
+ # and then try to apply only a portion of the changes in the next step
+ - name: Push All Changes
+ if: steps.apply.outputs.changes
+ id: push
+ continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- gh pr checkout ${{ github.event.pull_request.number }}
- npm install --ignore-scripts --no-audit --no-fund
- npm run template-oss-apply
- git add .
- git commit -am "chore: postinstall for dependabot template-oss PR"
+ git commit -am "${{ steps.apply.outputs.message }}"
git push
- npm run lint
+
+ # If the previous step failed, then reset the commit and remove any workflow changes
+ # and attempt to commit and push again. This is helpful because we will have a commit
+ # with the correct prefix that we can then --amend with @npmcli/stafftools later.
+ - name: Push All Changes Except Workflows
+ if: steps.apply.outputs.changes && steps.push.outcome == 'failure'
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ git reset HEAD~
+ git checkout HEAD -- .github/workflows/
+ git clean -fd .github/workflows/
+ git commit -am "${{ steps.apply.outputs.message }}"
+ git push
+
+ # Check if all the necessary template-oss changes were applied. Since we continued
+ # on errors in one of the previous steps, this check will fail if our follow up
+ # only applied a portion of the changes and we need to followup manually.
+ #
+ # Note that this used to run `lint` and `postlint` but that will fail this action
+ # if we've also shipped any linting changes separate from template-oss. We do
+ # linting in another action, so we want to fail this one only if there are
+ # template-oss changes that could not be applied.
+ - name: Check Changes
+ if: steps.apply.outputs.changes
+ run: |
+ npm exec --offline ${{ steps.flags.outputs.workspace }} -- template-oss-check
+
+ - name: Fail on Breaking Change
+ if: steps.apply.outputs.changes && startsWith(steps.apply.outputs.message, 'feat!')
+ run: |
+ echo "This PR has a breaking change. Run 'npx -p @npmcli/stafftools gh template-oss-fix'"
+ echo "for more information on how to fix this with a BREAKING CHANGE footer."
+ exit 1
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 93a5c3c9..da5779df 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -1,6 +1,6 @@
# This file is automatically added by @npmcli/template-oss. Do not edit.
-name: Pull Request Linting
+name: Pull Request
on:
pull_request:
@@ -11,28 +11,40 @@ on:
- synchronize
jobs:
- check:
- name: Check PR Title or Commits
+ commitlint:
+ name: Lint Commits
+ if: github.repository_owner == 'npm'
runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
steps:
- - uses: actions/checkout@v3
+ - name: Checkout
+ uses: actions/checkout@v3
with:
fetch-depth: 0
- - name: Setup git user
+ - name: Setup Git User
run: |
- git config --global user.email "ops+npm-cli@npmjs.com"
- git config --global user.name "npm cli ops bot"
- - uses: actions/setup-node@v3
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
with:
- node-version: 16.x
- - name: Update npm to latest
- run: npm i --prefer-online --no-fund --no-audit -g npm@latest
- - run: npm -v
- - name: Install deps
- run: npm i -D @commitlint/cli @commitlint/config-conventional
- - name: Check commits OR PR title
+ node-version: 18.x
+ - name: Install npm@8
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Run Commitlint on Commits
+ id: commit
+ continue-on-error: true
+ run: |
+ npx --offline commitlint -V --from 'origin/${{ github.base_ref }}' --to ${{ github.event.pull_request.head.sha }}
+ - name: Run Commitlint on PR Title
+ if: steps.commit.outcome == 'failure'
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
- npx --offline commitlint -V --from origin/main --to ${{ github.event.pull_request.head.sha }} \
- || echo $PR_TITLE | npx --offline commitlint -V
+ echo '$PR_TITLE' | npx --offline commitlint -V
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
deleted file mode 100644
index ab3a9105..00000000
--- a/.github/workflows/release-please.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-# This file is automatically added by @npmcli/template-oss. Do not edit.
-
-name: Release Please
-
-on:
- push:
- branches:
- - main
- - latest
-
-jobs:
- release-please:
- runs-on: ubuntu-latest
- steps:
- - uses: google-github-actions/release-please-action@v3
- id: release
- with:
- release-type: node
- changelog-types: >
- [
- {"type":"feat","section":"Features","hidden":false},
- {"type":"fix","section":"Bug Fixes","hidden":false},
- {"type":"docs","section":"Documentation","hidden":false},
- {"type":"deps","section":"Dependencies","hidden":false},
- {"type":"chore","hidden":true}
- ]
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..b086b0a5
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,399 @@
+# This file is automatically added by @npmcli/template-oss. Do not edit.
+
+name: Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ release-pr:
+ description: a release PR number to rerun release jobs on
+ type: string
+ push:
+ branches:
+ - main
+ - latest
+ - release/v*
+
+permissions:
+ contents: write
+ pull-requests: write
+ checks: write
+
+jobs:
+ release:
+ outputs:
+ pr: ${{ steps.release.outputs.pr }}
+ release: ${{ steps.release.outputs.release }}
+ releases: ${{ steps.release.outputs.releases }}
+ branch: ${{ steps.release.outputs.pr-branch }}
+ pr-number: ${{ steps.release.outputs.pr-number }}
+ comment-id: ${{ steps.pr-comment.outputs.result }}
+ check-id: ${{ steps.check.outputs.check_id }}
+ name: Release
+ if: github.repository_owner == 'npm'
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup Git User
+ run: |
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+ - name: Install npm@8
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Release Please
+ id: release
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ npx --offline template-oss-release-please "${{ github.ref_name }}" "${{ inputs.release-pr }}"
+ - name: Post Pull Request Comment
+ if: steps.release.outputs.pr-number
+ uses: actions/github-script@v6
+ id: pr-comment
+ env:
+ PR_NUMBER: ${{ steps.release.outputs.pr-number }}
+ REF_NAME: ${{ github.ref_name }}
+ with:
+ script: |
+ const { REF_NAME, PR_NUMBER: issue_number } = process.env
+ const { runId, repo: { owner, repo } } = context
+
+ const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId })
+
+ let body = '## Release Manager\n\n'
+
+ const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number })
+ let commentId = comments.find(c => c.user.login === 'github-actions[bot]' && c.body.startsWith(body))?.id
+
+ body += `Release workflow run: ${workflow.html_url}\n\n#### Force CI to Update This Release\n\n`
+ body += `This PR will be updated and CI will run for every non-\`chore:\` commit that is pushed to \`${REF_NAME}\`. `
+ body += `To force CI to update this PR, run this command:\n\n`
+ body += `\`\`\`\ngh workflow run release.yml -r ${REF_NAME} -R ${owner}/${repo} -f release-pr=${issue_number}\n\`\`\``
+
+ if (commentId) {
+ await github.rest.issues.updateComment({ owner, repo, comment_id: commentId, body })
+ } else {
+ const { data: comment } = await github.rest.issues.createComment({ owner, repo, issue_number, body })
+ commentId = comment?.id
+ }
+
+ return commentId
+ - name: Get Workflow Job
+ uses: actions/github-script@v6
+ if: steps.release.outputs.pr-sha
+ id: check-output
+ env:
+ JOB_NAME: "Release"
+ MATRIX_NAME: ""
+ with:
+ script: |
+ const { owner, repo } = context.repo
+
+ const { data } = await github.rest.actions.listJobsForWorkflowRun({
+ owner,
+ repo,
+ run_id: context.runId,
+ per_page: 100
+ })
+
+ const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME
+ const job = data.jobs.find(j => j.name.endsWith(jobName))
+ const jobUrl = job?.html_url
+
+ const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ steps.release.outputs.pr-sha }}`
+
+ let summary = `This check is assosciated with ${shaUrl}\n\n`
+
+ if (jobUrl) {
+ summary += `For run logs, click here: ${jobUrl}`
+ } else {
+ summary += `Run logs could not be found for a job with name: "${jobName}"`
+ }
+
+ return { summary }
+ - name: Create Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ id: check
+ if: steps.release.outputs.pr-sha
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ status: in_progress
+ name: Release
+ sha: ${{ steps.release.outputs.pr-sha }}
+ output: ${{ steps.check-output.outputs.result }}
+
+ update:
+ needs: release
+ outputs:
+ sha: ${{ steps.commit.outputs.sha }}
+ check-id: ${{ steps.check.outputs.check_id }}
+ name: Update - Release
+ if: github.repository_owner == 'npm' && needs.release.outputs.pr
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ ref: ${{ needs.release.outputs.branch }}
+ - name: Setup Git User
+ run: |
+ git config --global user.email "npm-cli+bot@github.com"
+ git config --global user.name "npm CLI robot"
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+ - name: Install npm@8
+ run: npm i --prefer-online --no-fund --no-audit -g npm@8
+ - name: npm Version
+ run: npm -v
+ - name: Install Dependencies
+ run: npm i --ignore-scripts --no-audit --no-fund
+ - name: Run Post Pull Request Actions
+ env:
+ RELEASE_PR_NUMBER: ${{ needs.release.outputs.pr-number }}
+ RELEASE_COMMENT_ID: ${{ needs.release.outputs.comment-id }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ npm exec --offline -- template-oss-release-manager --lockfile=false --publish=true
+ npm run rp-pull-request --ignore-scripts --if-present
+ - name: Commit
+ id: commit
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ git commit --all --amend --no-edit || true
+ git push --force-with-lease
+ echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
+ - name: Get Workflow Job
+ uses: actions/github-script@v6
+ if: steps.commit.outputs.sha
+ id: check-output
+ env:
+ JOB_NAME: "Update - Release"
+ MATRIX_NAME: ""
+ with:
+ script: |
+ const { owner, repo } = context.repo
+
+ const { data } = await github.rest.actions.listJobsForWorkflowRun({
+ owner,
+ repo,
+ run_id: context.runId,
+ per_page: 100
+ })
+
+ const jobName = process.env.JOB_NAME + process.env.MATRIX_NAME
+ const job = data.jobs.find(j => j.name.endsWith(jobName))
+ const jobUrl = job?.html_url
+
+ const shaUrl = `${context.serverUrl}/${owner}/${repo}/commit/${{ steps.commit.outputs.sha }}`
+
+ let summary = `This check is assosciated with ${shaUrl}\n\n`
+
+ if (jobUrl) {
+ summary += `For run logs, click here: ${jobUrl}`
+ } else {
+ summary += `Run logs could not be found for a job with name: "${jobName}"`
+ }
+
+ return { summary }
+ - name: Create Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ id: check
+ if: steps.commit.outputs.sha
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ status: in_progress
+ name: Release
+ sha: ${{ steps.commit.outputs.sha }}
+ output: ${{ steps.check-output.outputs.result }}
+ - name: Conclude Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ if: needs.release.outputs.check-id && always()
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ conclusion: ${{ job.status }}
+ check_id: ${{ needs.release.outputs.check-id }}
+
+ ci:
+ name: CI - Release
+ needs: [ release, update ]
+ if: needs.release.outputs.pr
+ uses: ./.github/workflows/ci-release.yml
+ with:
+ ref: ${{ needs.release.outputs.branch }}
+ check-sha: ${{ needs.update.outputs.sha }}
+
+ post-ci:
+ needs: [ release, update, ci ]
+ name: Post CI - Release
+ if: github.repository_owner == 'npm' && needs.release.outputs.pr && always()
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Get Needs Result
+ id: needs-result
+ run: |
+ result=""
+ if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
+ result="failure"
+ elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
+ result="cancelled"
+ else
+ result="success"
+ fi
+ echo "result=$result" >> $GITHUB_OUTPUT
+ - name: Conclude Check
+ uses: LouisBrunner/checks-action@v1.6.0
+ if: needs.update.outputs.check-id && always()
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ conclusion: ${{ steps.needs-result.outputs.result }}
+ check_id: ${{ needs.update.outputs.check-id }}
+
+ post-release:
+ needs: release
+ name: Post Release - Release
+ if: github.repository_owner == 'npm' && needs.release.outputs.releases
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Create Release PR Comment
+ uses: actions/github-script@v6
+ env:
+ RELEASES: ${{ needs.release.outputs.releases }}
+ with:
+ script: |
+ const releases = JSON.parse(process.env.RELEASES)
+ const { runId, repo: { owner, repo } } = context
+ const issue_number = releases[0].prNumber
+
+ let body = '## Release Workflow\n\n'
+ for (const { pkgName, version, url } of releases) {
+ body += `- \`${pkgName}@${version}\` ${url}\n`
+ }
+
+ const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number })
+ .then(cs => cs.map(c => ({ id: c.id, login: c.user.login, body: c.body })))
+ console.log(`Found comments: ${JSON.stringify(comments, null, 2)}`)
+ const releaseComments = comments.filter(c => c.login === 'github-actions[bot]' && c.body.includes('Release is at'))
+
+ for (const comment of releaseComments) {
+ console.log(`Release comment: ${JSON.stringify(comment, null, 2)}`)
+ await github.rest.issues.deleteComment({ owner, repo, comment_id: comment.id })
+ }
+
+ const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number,
+ body: `${body}- Workflow run: :arrows_counterclockwise: ${runUrl}`,
+ })
+
+ release-integration:
+ needs: release
+ name: Release Integration
+ if: needs.release.outputs.release
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ permissions:
+ deployments: write
+ id-token: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ ref: ${{ fromJSON(needs.release.outputs.release).tagName }}
+ - name: Setup Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18.x
+ - name: Install npm@latest
+ run: |
+ npm i --prefer-online --no-fund --no-audit -g npm@latest
+ npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN}
+ - name: Publish
+ env:
+ PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
+ run: npm publish --provenance
+
+ post-release-integration:
+ needs: [ release, release-integration ]
+ name: Post Release Integration - Release
+ if: github.repository_owner == 'npm' && needs.release.outputs.release && always()
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ shell: bash
+ steps:
+ - name: Get Needs Result
+ id: needs-result
+ run: |
+ if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
+ result="x"
+ elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
+ result="heavy_multiplication_x"
+ else
+ result="white_check_mark"
+ fi
+ echo "result=$result" >> $GITHUB_OUTPUT
+ - name: Update Release PR Comment
+ uses: actions/github-script@v6
+ env:
+ PR_NUMBER: ${{ fromJSON(needs.release.outputs.release).prNumber }}
+ RESULT: ${{ steps.needs-result.outputs.result }}
+ with:
+ script: |
+ const { PR_NUMBER: issue_number, RESULT } = process.env
+ const { runId, repo: { owner, repo } } = context
+
+ const comments = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number })
+ const updateComment = comments.find(c =>
+ c.user.login === 'github-actions[bot]' &&
+ c.body.startsWith('## Release Workflow\n\n') &&
+ c.body.includes(runId)
+ )
+
+ if (updateComment) {
+ console.log('Found comment to update:', JSON.stringify(updateComment, null, 2))
+ let body = updateComment.body.replace(/Workflow run: :[a-z_]+:/, `Workflow run: :${RESULT}:`)
+ const tagCodeowner = RESULT !== 'white_check_mark'
+ if (tagCodeowner) {
+ body += `\n\n:rotating_light:`
+ body += ` @npm/cli-team: The post-release workflow failed for this release.`
+ body += ` Manual steps may need to be taken after examining the workflow output`
+ body += ` from the above workflow run. :rotating_light:`
+ }
+ await github.rest.issues.updateComment({
+ owner,
+ repo,
+ body,
+ comment_id: updateComment.id,
+ })
+ } else {
+ console.log('No matching comments found:', JSON.stringify(comments, null, 2))
+ }
diff --git a/.gitignore b/.gitignore
index dd3ceb93..00bdaf2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,28 +4,33 @@
/*
# keep these
-!/.eslintrc.local.*
!**/.gitignore
-!/docs/
-!/tap-snapshots/
-!/test/
-!/map.js
-!/scripts/
-!/README*
-!/LICENSE*
-!/CHANGELOG*
!/.commitlintrc.js
!/.eslintrc.js
+!/.eslintrc.local.*
!/.github/
!/.gitignore
!/.npmrc
-!/SECURITY.md
+!/.release-please-manifest.json
!/bin/
+!/CHANGELOG*
!/classes/
+!/CODE_OF_CONDUCT.md
+!/CONTRIBUTING.md
+!/docs/
!/functions/
!/index.js
!/internal/
+!/lib/
+!/LICENSE*
+!/map.js
!/package.json
!/preload.js
!/range.bnf
!/ranges/
+!/README*
+!/release-please-config.json
+!/scripts/
+!/SECURITY.md
+!/tap-snapshots/
+!/test/
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 00000000..92661b3b
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "7.5.4"
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04ad0067..232b63ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,79 @@
# Changelog
+## [7.5.4](https://github.com/npm/node-semver/compare/v7.5.3...v7.5.4) (2023-07-07)
+
+### Bug Fixes
+
+* [`cc6fde2`](https://github.com/npm/node-semver/commit/cc6fde2d34b95cb600d126649d926901bd2a9703) [#588](https://github.com/npm/node-semver/pull/588) trim each range set before parsing (@lukekarrys)
+* [`99d8287`](https://github.com/npm/node-semver/commit/99d8287516a1d2abf0286033e2e26eca6b69c09f) [#583](https://github.com/npm/node-semver/pull/583) correctly parse long build ids as valid (#583) (@lukekarrys)
+
+## [7.5.3](https://github.com/npm/node-semver/compare/v7.5.2...v7.5.3) (2023-06-22)
+
+### Bug Fixes
+
+* [`abdd93d`](https://github.com/npm/node-semver/commit/abdd93d55496d22e3c15a454a5cf13f101e48bce) [#571](https://github.com/npm/node-semver/pull/571) set max lengths in regex for numeric and build identifiers (#571) (@lukekarrys)
+
+### Documentation
+
+* [`bf53dd8`](https://github.com/npm/node-semver/commit/bf53dd8da15a17eb6b8111115d0d8ef341fea5db) [#569](https://github.com/npm/node-semver/pull/569) add example for `>` comparator (#569) (@mbtools)
+
+## [7.5.2](https://github.com/npm/node-semver/compare/v7.5.1...v7.5.2) (2023-06-15)
+
+### Bug Fixes
+
+* [`58c791f`](https://github.com/npm/node-semver/commit/58c791f40ba8cf4be35a5ca6644353ecd6249edc) [#566](https://github.com/npm/node-semver/pull/566) diff when detecting major change from prerelease (#566) (@lukekarrys)
+* [`5c8efbc`](https://github.com/npm/node-semver/commit/5c8efbcb3c6c125af10746d054faff13e8c33fbd) [#565](https://github.com/npm/node-semver/pull/565) preserve build in raw after inc (#565) (@lukekarrys)
+* [`717534e`](https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441) [#564](https://github.com/npm/node-semver/pull/564) better handling of whitespace (#564) (@lukekarrys)
+
+## [7.5.1](https://github.com/npm/node-semver/compare/v7.5.0...v7.5.1) (2023-05-12)
+
+### Bug Fixes
+
+* [`d30d25a`](https://github.com/npm/node-semver/commit/d30d25a5c1fb963c3cc9178cb1769fe45e4a3cab) [#559](https://github.com/npm/node-semver/pull/559) show type on invalid semver error (#559) (@tjenkinson)
+
+## [7.5.0](https://github.com/npm/node-semver/compare/v7.4.0...v7.5.0) (2023-04-17)
+
+### Features
+
+* [`503a4e5`](https://github.com/npm/node-semver/commit/503a4e52fe2b1c6ed1400d33149f7733c8361eed) [#548](https://github.com/npm/node-semver/pull/548) allow identifierBase to be false (#548) (@lsvalina)
+
+### Bug Fixes
+
+* [`e219bb4`](https://github.com/npm/node-semver/commit/e219bb454036a0c23e34407591f921c8edb688e7) [#552](https://github.com/npm/node-semver/pull/552) throw on bad version with correct error message (#552) (@wraithgar)
+* [`fc2f3df`](https://github.com/npm/node-semver/commit/fc2f3df0b5d25253b3580607e111a9a280d888ca) [#546](https://github.com/npm/node-semver/pull/546) incorrect results from diff sometimes with prerelease versions (#546) (@tjenkinson)
+* [`2781767`](https://github.com/npm/node-semver/commit/27817677794f592b592bf6181a80a4824ff762b2) [#547](https://github.com/npm/node-semver/pull/547) avoid re-instantiating SemVer during diff compare (#547) (@macno)
+
+## [7.4.0](https://github.com/npm/node-semver/compare/v7.3.8...v7.4.0) (2023-04-10)
+
+### Features
+
+* [`113f513`](https://github.com/npm/node-semver/commit/113f51312a1a6b6aa50d4f9486b4fde21782c1f5) [#532](https://github.com/npm/node-semver/pull/532) identifierBase parameter for .inc (#532) (@wraithgar, @b-bly)
+* [`48d8f8f`](https://github.com/npm/node-semver/commit/48d8f8fa63bf6e35db70ff840b6da1a51596a5a8) [#530](https://github.com/npm/node-semver/pull/530) export new RELEASE_TYPES constant (@hcharley)
+
+### Bug Fixes
+
+* [`940723d`](https://github.com/npm/node-semver/commit/940723d22bca824993627c45ac30dd3d2854b8cd) [#538](https://github.com/npm/node-semver/pull/538) intersects with v0.0.0 and v0.0.0-0 (#538) (@wraithgar)
+* [`aa516b5`](https://github.com/npm/node-semver/commit/aa516b50b32f5a144017d8fc1b9efe0540963c91) [#535](https://github.com/npm/node-semver/pull/535) faster parse options (#535) (@H4ad)
+* [`61e6ea1`](https://github.com/npm/node-semver/commit/61e6ea1e9b7af01baf19ab0c0a63c8e3ebfac97c) [#536](https://github.com/npm/node-semver/pull/536) faster cache key factory for range (#536) (@H4ad)
+* [`f8b8b61`](https://github.com/npm/node-semver/commit/f8b8b619e71746a47852a9d301f3087ab311444f) [#541](https://github.com/npm/node-semver/pull/541) optimistic parse (#541) (@H4ad)
+* [`796cbe2`](https://github.com/npm/node-semver/commit/796cbe29b06d102e1b16f3ed78eaba210ece951e) [#533](https://github.com/npm/node-semver/pull/533) semver.diff prerelease to release recognition (#533) (@wraithgar, @dominique-blockchain)
+* [`3f222b1`](https://github.com/npm/node-semver/commit/3f222b144033525ca9f8a2ce5bc6e02f0401881f) [#537](https://github.com/npm/node-semver/pull/537) reuse comparators on subset (#537) (@H4ad)
+* [`f66cc45`](https://github.com/npm/node-semver/commit/f66cc45c6e82eebb4b5b51af73e7b8dcaeda7e21) [#539](https://github.com/npm/node-semver/pull/539) faster diff (#539) (@H4ad)
+
+### Documentation
+
+* [`c5d29df`](https://github.com/npm/node-semver/commit/c5d29df6f75741fea27fffe3b88c9c3b28e3ca73) [#530](https://github.com/npm/node-semver/pull/530) Add "Constants" section to README (@hcharley)
+
+## [7.3.8](https://github.com/npm/node-semver/compare/v7.3.7...v7.3.8) (2022-10-04)
+
+### Bug Fixes
+
+* [`d8ef32c`](https://github.com/npm/node-semver/commit/d8ef32cee7a7e34310838f32451c9bcf52956b64) [#383](https://github.com/npm/node-semver/pull/383) add support for node.js esm auto exports (#383) (@MylesBorins)
+
+### Documentation
+
+* [`7209b14`](https://github.com/npm/node-semver/commit/7209b14ccd7ca35b9a1077a0b67d9ce884fe6d00) [#477](https://github.com/npm/node-semver/pull/477) update range.js comments to clarify the caret ranges examples (#477) (@amitse)
+
### [7.3.7](https://github.com/npm/node-semver/compare/v7.3.6...v7.3.7) (2022-04-11)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 00000000..167043c2
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,7 @@
+
+
+All interactions in this repo are covered by the [npm Code of
+Conduct](https://docs.npmjs.com/policies/conduct)
+
+The npm cli team may, at its own discretion, moderate, remove, or edit
+any interactions such as pull requests, issues, and comments.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..69e88788
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,50 @@
+
+
+# Contributing
+
+## Code of Conduct
+
+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).
+
+## Reporting Bugs
+
+Before submitting a new bug report please search for an existing or similar report.
+
+Use one of our existing issue templates if you believe you've come across a unique problem.
+
+Duplicate issues, or issues that don't use one of our templates may get closed without a response.
+
+## Pull Request Conventions
+
+### Commits
+
+We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
+
+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:
+
+ - `feat`: For when introducing a new feature. The result will be a new semver minor version of the package when it is next published.
+ - `fix`: For bug fixes. The result will be a new semver patch version of the package when it is next published.
+ - `docs`: For documentation updates. The result will be a new semver patch version of the package when it is next published.
+ - `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).
+
+### Test Coverage
+
+Pull requests made against this repo will run `npm test` automatically. Please make sure tests pass locally before submitting a PR.
+
+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.
+
+### Linting
+
+Linting is also done automatically once tests pass. `npm run lintfix` will fix most linting errors automatically.
+
+Please make sure linting passes before submitting a PR.
+
+## What _not_ to contribute?
+
+### Dependencies
+
+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.
+
+### Tools/Automation
+
+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.
diff --git a/README.md b/README.md
index df54e7a0..53ea9b52 100644
--- a/README.md
+++ b/README.md
@@ -110,6 +110,9 @@ Options:
-l --loose
Interpret versions and ranges loosely
+-n <0|1>
+ This is the base to be used for the prerelease identifier.
+
-p --include-prerelease
Always include prerelease versions in range matching
@@ -156,7 +159,9 @@ of primitive `operators` is:
For example, the comparator `>=1.2.7` would match the versions
`1.2.7`, `1.2.8`, `2.5.3`, and `1.3.9`, but not the versions `1.2.6`
-or `1.1.0`.
+or `1.1.0`. The comparator `>1` is equivalent to `>=2.0.0` and
+would match the versions `2.0.0` and `3.1.0`, but not the versions
+`1.0.1` or `1.1.0`.
Comparators can be joined by whitespace to form a `comparator set`,
which is satisfied by the **intersection** of all of the comparators
@@ -232,6 +237,35 @@ $ semver 1.2.4-beta.0 -i prerelease
1.2.4-beta.1
```
+#### Prerelease Identifier Base
+
+The method `.inc` takes an optional parameter 'identifierBase' string
+that will let you let your prerelease number as zero-based or one-based.
+Set to `false` to omit the prerelease number altogether.
+If you do not specify this parameter, it will default to zero-based.
+
+```javascript
+semver.inc('1.2.3', 'prerelease', 'beta', '1')
+// '1.2.4-beta.1'
+```
+
+```javascript
+semver.inc('1.2.3', 'prerelease', 'beta', false)
+// '1.2.4-beta'
+```
+
+command-line example:
+
+```bash
+$ semver 1.2.3 -i prerelease --preid beta -n 1
+1.2.4-beta.1
+```
+
+```bash
+$ semver 1.2.3 -i prerelease --preid beta -n false
+1.2.4-beta
+```
+
### Advanced Range Syntax
Advanced range syntax desugars to primitive comparators in
@@ -513,6 +547,40 @@ ex.
* `s.clean(' 2.1.5 ')`: `'2.1.5'`
* `s.clean('~1.0.0')`: `null`
+## Constants
+
+As a convenience, helper constants are exported to provide information about what `node-semver` supports:
+
+### `RELEASE_TYPES`
+
+- major
+- premajor
+- minor
+- preminor
+- patch
+- prepatch
+- prerelease
+
+```
+const semver = require('semver');
+
+if (semver.RELEASE_TYPES.includes(arbitraryUserInput)) {
+ console.log('This is a valid release type!');
+} else {
+ console.warn('This is NOT a valid release type!');
+}
+```
+
+### `SEMVER_SPEC_VERSION`
+
+2.0.0
+
+```
+const semver = require('semver');
+
+console.log('We are currently using the semver specification version:', semver.SEMVER_SPEC_VERSION);
+```
+
## Exported Modules
-Please send vulnerability reports through [hackerone](https://hackerone.com/github).
+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).
+
+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.
+
+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).
+
+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.
+
+**Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
+
+Thanks for helping make GitHub safe for everyone.
diff --git a/bin/semver.js b/bin/semver.js
index 8d1b5572..242b7ade 100755
--- a/bin/semver.js
+++ b/bin/semver.js
@@ -23,7 +23,10 @@ let rtl = false
let identifier
+let identifierBase
+
const semver = require('../')
+const parseOptions = require('../internal/parse-options')
let reverse = false
@@ -71,6 +74,12 @@ const main = () => {
case '-r': case '--range':
range.push(argv.shift())
break
+ case '-n':
+ identifierBase = argv.shift()
+ if (identifierBase === 'false') {
+ identifierBase = false
+ }
+ break
case '-c': case '--coerce':
coerce = true
break
@@ -88,7 +97,7 @@ const main = () => {
}
}
- options = { loose: loose, includePrerelease: includePrerelease, rtl: rtl }
+ options = parseOptions({ loose, includePrerelease, rtl })
versions = versions.map((v) => {
return coerce ? (semver.coerce(v, options) || { version: v }).version : v
@@ -127,7 +136,7 @@ const success = () => {
}).map((v) => {
return semver.clean(v, options)
}).map((v) => {
- return inc ? semver.inc(v, inc, options, identifier) : v
+ return inc ? semver.inc(v, inc, options, identifier, identifierBase) : v
}).forEach((v, i, _) => {
console.log(v)
})
@@ -172,6 +181,11 @@ Options:
--ltr
Coerce version strings left to right (default)
+-n
+ Base number to be used for the prerelease identifier.
+ Can be either 0 or 1, or false to omit the number altogether.
+ Defaults to 0.
+
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
diff --git a/classes/comparator.js b/classes/comparator.js
index 62cd204d..3d39c0ee 100644
--- a/classes/comparator.js
+++ b/classes/comparator.js
@@ -16,6 +16,7 @@ class Comparator {
}
}
+ comp = comp.trim().split(/\s+/).join(' ')
debug('comparator', comp, options)
this.options = options
this.loose = !!options.loose
@@ -78,13 +79,6 @@ class Comparator {
throw new TypeError('a Comparator is required')
}
- if (!options || typeof options !== 'object') {
- options = {
- loose: !!options,
- includePrerelease: false,
- }
- }
-
if (this.operator === '') {
if (this.value === '') {
return true
@@ -97,39 +91,50 @@ class Comparator {
return new Range(this.value, options).test(comp.semver)
}
- const sameDirectionIncreasing =
- (this.operator === '>=' || this.operator === '>') &&
- (comp.operator === '>=' || comp.operator === '>')
- const sameDirectionDecreasing =
- (this.operator === '<=' || this.operator === '<') &&
- (comp.operator === '<=' || comp.operator === '<')
- const sameSemVer = this.semver.version === comp.semver.version
- const differentDirectionsInclusive =
- (this.operator === '>=' || this.operator === '<=') &&
- (comp.operator === '>=' || comp.operator === '<=')
- const oppositeDirectionsLessThan =
- cmp(this.semver, '<', comp.semver, options) &&
- (this.operator === '>=' || this.operator === '>') &&
- (comp.operator === '<=' || comp.operator === '<')
- const oppositeDirectionsGreaterThan =
- cmp(this.semver, '>', comp.semver, options) &&
- (this.operator === '<=' || this.operator === '<') &&
- (comp.operator === '>=' || comp.operator === '>')
-
- return (
- sameDirectionIncreasing ||
- sameDirectionDecreasing ||
- (sameSemVer && differentDirectionsInclusive) ||
- oppositeDirectionsLessThan ||
- oppositeDirectionsGreaterThan
- )
+ options = parseOptions(options)
+
+ // Special cases where nothing can possibly be lower
+ if (options.includePrerelease &&
+ (this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) {
+ return false
+ }
+ if (!options.includePrerelease &&
+ (this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) {
+ return false
+ }
+
+ // Same direction increasing (> or >=)
+ if (this.operator.startsWith('>') && comp.operator.startsWith('>')) {
+ return true
+ }
+ // Same direction decreasing (< or <=)
+ if (this.operator.startsWith('<') && comp.operator.startsWith('<')) {
+ return true
+ }
+ // same SemVer and both sides are inclusive (<= or >=)
+ if (
+ (this.semver.version === comp.semver.version) &&
+ this.operator.includes('=') && comp.operator.includes('=')) {
+ return true
+ }
+ // opposite directions less than
+ if (cmp(this.semver, '<', comp.semver, options) &&
+ this.operator.startsWith('>') && comp.operator.startsWith('<')) {
+ return true
+ }
+ // opposite directions greater than
+ if (cmp(this.semver, '>', comp.semver, options) &&
+ this.operator.startsWith('<') && comp.operator.startsWith('>')) {
+ return true
+ }
+ return false
}
}
module.exports = Comparator
const parseOptions = require('../internal/parse-options')
-const { re, t } = require('../internal/re')
+const { safeRe: re, t } = require('../internal/re')
const cmp = require('../functions/cmp')
const debug = require('../internal/debug')
const SemVer = require('./semver')
diff --git a/classes/range.js b/classes/range.js
index 7dc24bc7..7e7c4141 100644
--- a/classes/range.js
+++ b/classes/range.js
@@ -26,9 +26,16 @@ class Range {
this.loose = !!options.loose
this.includePrerelease = !!options.includePrerelease
- // First, split based on boolean or ||
+ // First reduce all whitespace as much as possible so we do not have to rely
+ // on potentially slow regexes like \s*. This is then stored and used for
+ // future error messages as well.
this.raw = range
- this.set = range
+ .trim()
+ .split(/\s+/)
+ .join(' ')
+
+ // First, split on ||
+ this.set = this.raw
.split('||')
// map the range to a 2d array of comparators
.map(r => this.parseRange(r.trim()))
@@ -38,7 +45,7 @@ class Range {
.filter(c => c.length)
if (!this.set.length) {
- throw new TypeError(`Invalid SemVer Range: ${range}`)
+ throw new TypeError(`Invalid SemVer Range: ${this.raw}`)
}
// if we have any that are not the null set, throw out null sets.
@@ -64,9 +71,7 @@ class Range {
format () {
this.range = this.set
- .map((comps) => {
- return comps.join(' ').trim()
- })
+ .map((comps) => comps.join(' ').trim())
.join('||')
.trim()
return this.range
@@ -77,12 +82,12 @@ class Range {
}
parseRange (range) {
- range = range.trim()
-
// memoize range parsing for performance.
// this is a very hot path, and fully deterministic.
- const memoOpts = Object.keys(this.options).join(',')
- const memoKey = `parseRange:${memoOpts}:${range}`
+ const memoOpts =
+ (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) |
+ (this.options.loose && FLAG_LOOSE)
+ const memoKey = memoOpts + ':' + range
const cached = cache.get(memoKey)
if (cached) {
return cached
@@ -93,18 +98,18 @@ class Range {
const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE]
range = range.replace(hr, hyphenReplace(this.options.includePrerelease))
debug('hyphen replace', range)
+
// `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5`
range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace)
debug('comparator trim', range)
// `~ 1.2.3` => `~1.2.3`
range = range.replace(re[t.TILDETRIM], tildeTrimReplace)
+ debug('tilde trim', range)
// `^ 1.2.3` => `^1.2.3`
range = range.replace(re[t.CARETTRIM], caretTrimReplace)
-
- // normalize spaces
- range = range.split(/\s+/).join(' ')
+ debug('caret trim', range)
// At this point, the range is completely trimmed and
// ready to be split into comparators.
@@ -190,6 +195,7 @@ class Range {
return false
}
}
+
module.exports = Range
const LRU = require('lru-cache')
@@ -200,12 +206,13 @@ const Comparator = require('./comparator')
const debug = require('../internal/debug')
const SemVer = require('./semver')
const {
- re,
+ safeRe: re,
t,
comparatorTrimReplace,
tildeTrimReplace,
caretTrimReplace,
} = require('../internal/re')
+const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = require('../internal/constants')
const isNullSet = c => c.value === '<0.0.0-0'
const isAny = c => c.value === ''
@@ -252,10 +259,14 @@ const isX = id => !id || id.toLowerCase() === 'x' || id === '*'
// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0
// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0
// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0
-const replaceTildes = (comp, options) =>
- comp.trim().split(/\s+/).map((c) => {
- return replaceTilde(c, options)
- }).join(' ')
+// ~0.0.1 --> >=0.0.1 <0.1.0-0
+const replaceTildes = (comp, options) => {
+ return comp
+ .trim()
+ .split(/\s+/)
+ .map((c) => replaceTilde(c, options))
+ .join(' ')
+}
const replaceTilde = (comp, options) => {
const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
@@ -291,10 +302,15 @@ const replaceTilde = (comp, options) => {
// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0
// ^1.2.3 --> >=1.2.3 <2.0.0-0
// ^1.2.0 --> >=1.2.0 <2.0.0-0
-const replaceCarets = (comp, options) =>
- comp.trim().split(/\s+/).map((c) => {
- return replaceCaret(c, options)
- }).join(' ')
+// ^0.0.1 --> >=0.0.1 <0.0.2-0
+// ^0.1.0 --> >=0.1.0 <0.2.0-0
+const replaceCarets = (comp, options) => {
+ return comp
+ .trim()
+ .split(/\s+/)
+ .map((c) => replaceCaret(c, options))
+ .join(' ')
+}
const replaceCaret = (comp, options) => {
debug('caret', comp, options)
@@ -351,9 +367,10 @@ const replaceCaret = (comp, options) => {
const replaceXRanges = (comp, options) => {
debug('replaceXRanges', comp, options)
- return comp.split(/\s+/).map((c) => {
- return replaceXRange(c, options)
- }).join(' ')
+ return comp
+ .split(/\s+/)
+ .map((c) => replaceXRange(c, options))
+ .join(' ')
}
const replaceXRange = (comp, options) => {
@@ -436,12 +453,15 @@ const replaceXRange = (comp, options) => {
const replaceStars = (comp, options) => {
debug('replaceStars', comp, options)
// Looseness is ignored here. star is always as loose as it gets!
- return comp.trim().replace(re[t.STAR], '')
+ return comp
+ .trim()
+ .replace(re[t.STAR], '')
}
const replaceGTE0 = (comp, options) => {
debug('replaceGTE0', comp, options)
- return comp.trim()
+ return comp
+ .trim()
.replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '')
}
@@ -479,7 +499,7 @@ const hyphenReplace = incPr => ($0,
to = `<=${to}`
}
- return (`${from} ${to}`).trim()
+ return `${from} ${to}`.trim()
}
const testSet = (set, version, options) => {
diff --git a/classes/semver.js b/classes/semver.js
index af629551..84e84590 100644
--- a/classes/semver.js
+++ b/classes/semver.js
@@ -1,6 +1,6 @@
const debug = require('../internal/debug')
const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants')
-const { re, t } = require('../internal/re')
+const { safeRe: re, t } = require('../internal/re')
const parseOptions = require('../internal/parse-options')
const { compareIdentifiers } = require('../internal/identifiers')
@@ -16,7 +16,7 @@ class SemVer {
version = version.version
}
} else if (typeof version !== 'string') {
- throw new TypeError(`Invalid Version: ${version}`)
+ throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`)
}
if (version.length > MAX_LENGTH) {
@@ -175,36 +175,36 @@ class SemVer {
// preminor will bump the version up to the next minor release, and immediately
// down to pre-release. premajor and prepatch work the same way.
- inc (release, identifier) {
+ inc (release, identifier, identifierBase) {
switch (release) {
case 'premajor':
this.prerelease.length = 0
this.patch = 0
this.minor = 0
this.major++
- this.inc('pre', identifier)
+ this.inc('pre', identifier, identifierBase)
break
case 'preminor':
this.prerelease.length = 0
this.patch = 0
this.minor++
- this.inc('pre', identifier)
+ this.inc('pre', identifier, identifierBase)
break
case 'prepatch':
// If this is already a prerelease, it will bump to the next version
// drop any prereleases that might already exist, since they are not
// relevant at this point.
this.prerelease.length = 0
- this.inc('patch', identifier)
- this.inc('pre', identifier)
+ this.inc('patch', identifier, identifierBase)
+ this.inc('pre', identifier, identifierBase)
break
// If the input is a non-prerelease version, this acts the same as
// prepatch.
case 'prerelease':
if (this.prerelease.length === 0) {
- this.inc('patch', identifier)
+ this.inc('patch', identifier, identifierBase)
}
- this.inc('pre', identifier)
+ this.inc('pre', identifier, identifierBase)
break
case 'major':
@@ -246,9 +246,15 @@ class SemVer {
break
// This probably shouldn't be used publicly.
// 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction.
- case 'pre':
+ case 'pre': {
+ const base = Number(identifierBase) ? 1 : 0
+
+ if (!identifier && identifierBase === false) {
+ throw new Error('invalid increment argument: identifier is empty')
+ }
+
if (this.prerelease.length === 0) {
- this.prerelease = [0]
+ this.prerelease = [base]
} else {
let i = this.prerelease.length
while (--i >= 0) {
@@ -259,27 +265,36 @@ class SemVer {
}
if (i === -1) {
// didn't increment anything
- this.prerelease.push(0)
+ if (identifier === this.prerelease.join('.') && identifierBase === false) {
+ throw new Error('invalid increment argument: identifier already exists')
+ }
+ this.prerelease.push(base)
}
}
if (identifier) {
// 1.2.0-beta.1 bumps to 1.2.0-beta.2,
// 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0
+ let prerelease = [identifier, base]
+ if (identifierBase === false) {
+ prerelease = [identifier]
+ }
if (compareIdentifiers(this.prerelease[0], identifier) === 0) {
if (isNaN(this.prerelease[1])) {
- this.prerelease = [identifier, 0]
+ this.prerelease = prerelease
}
} else {
- this.prerelease = [identifier, 0]
+ this.prerelease = prerelease
}
}
break
-
+ }
default:
throw new Error(`invalid increment argument: ${release}`)
}
- this.format()
- this.raw = this.version
+ this.raw = this.format()
+ if (this.build.length) {
+ this.raw += `+${this.build.join('.')}`
+ }
return this
}
}
diff --git a/functions/coerce.js b/functions/coerce.js
index 2e01452f..febbff9c 100644
--- a/functions/coerce.js
+++ b/functions/coerce.js
@@ -1,6 +1,6 @@
const SemVer = require('../classes/semver')
const parse = require('./parse')
-const { re, t } = require('../internal/re')
+const { safeRe: re, t } = require('../internal/re')
const coerce = (version, options) => {
if (version instanceof SemVer) {
diff --git a/functions/diff.js b/functions/diff.js
index 87200ef3..fc224e30 100644
--- a/functions/diff.js
+++ b/functions/diff.js
@@ -1,23 +1,65 @@
-const parse = require('./parse')
-const eq = require('./eq')
+const parse = require('./parse.js')
const diff = (version1, version2) => {
- if (eq(version1, version2)) {
+ const v1 = parse(version1, null, true)
+ const v2 = parse(version2, null, true)
+ const comparison = v1.compare(v2)
+
+ if (comparison === 0) {
return null
- } else {
- const v1 = parse(version1)
- const v2 = parse(version2)
- const hasPre = v1.prerelease.length || v2.prerelease.length
- const prefix = hasPre ? 'pre' : ''
- const defaultResult = hasPre ? 'prerelease' : ''
- for (const key in v1) {
- if (key === 'major' || key === 'minor' || key === 'patch') {
- if (v1[key] !== v2[key]) {
- return prefix + key
- }
- }
+ }
+
+ const v1Higher = comparison > 0
+ const highVersion = v1Higher ? v1 : v2
+ const lowVersion = v1Higher ? v2 : v1
+ const highHasPre = !!highVersion.prerelease.length
+ const lowHasPre = !!lowVersion.prerelease.length
+
+ if (lowHasPre && !highHasPre) {
+ // Going from prerelease -> no prerelease requires some special casing
+
+ // If the low version has only a major, then it will always be a major
+ // Some examples:
+ // 1.0.0-1 -> 1.0.0
+ // 1.0.0-1 -> 1.1.1
+ // 1.0.0-1 -> 2.0.0
+ if (!lowVersion.patch && !lowVersion.minor) {
+ return 'major'
+ }
+
+ // Otherwise it can be determined by checking the high version
+
+ if (highVersion.patch) {
+ // anything higher than a patch bump would result in the wrong version
+ return 'patch'
+ }
+
+ if (highVersion.minor) {
+ // anything higher than a minor bump would result in the wrong version
+ return 'minor'
}
- return defaultResult // may be undefined
+
+ // bumping major/minor/patch all have same result
+ return 'major'
+ }
+
+ // add the `pre` prefix if we are going to a prerelease version
+ const prefix = highHasPre ? 'pre' : ''
+
+ if (v1.major !== v2.major) {
+ return prefix + 'major'
+ }
+
+ if (v1.minor !== v2.minor) {
+ return prefix + 'minor'
+ }
+
+ if (v1.patch !== v2.patch) {
+ return prefix + 'patch'
}
+
+ // high and low are preleases
+ return 'prerelease'
}
+
module.exports = diff
diff --git a/functions/inc.js b/functions/inc.js
index 62d1da2c..7670b1be 100644
--- a/functions/inc.js
+++ b/functions/inc.js
@@ -1,7 +1,8 @@
const SemVer = require('../classes/semver')
-const inc = (version, release, options, identifier) => {
+const inc = (version, release, options, identifier, identifierBase) => {
if (typeof (options) === 'string') {
+ identifierBase = identifier
identifier = options
options = undefined
}
@@ -10,7 +11,7 @@ const inc = (version, release, options, identifier) => {
return new SemVer(
version instanceof SemVer ? version.version : version,
options
- ).inc(release, identifier).version
+ ).inc(release, identifier, identifierBase).version
} catch (er) {
return null
}
diff --git a/functions/parse.js b/functions/parse.js
index a66663aa..459b3b17 100644
--- a/functions/parse.js
+++ b/functions/parse.js
@@ -1,32 +1,15 @@
-const { MAX_LENGTH } = require('../internal/constants')
-const { re, t } = require('../internal/re')
const SemVer = require('../classes/semver')
-
-const parseOptions = require('../internal/parse-options')
-const parse = (version, options) => {
- options = parseOptions(options)
-
+const parse = (version, options, throwErrors = false) => {
if (version instanceof SemVer) {
return version
}
-
- if (typeof version !== 'string') {
- return null
- }
-
- if (version.length > MAX_LENGTH) {
- return null
- }
-
- const r = options.loose ? re[t.LOOSE] : re[t.FULL]
- if (!r.test(version)) {
- return null
- }
-
try {
return new SemVer(version, options)
} catch (er) {
- return null
+ if (!throwErrors) {
+ return null
+ }
+ throw er
}
}
diff --git a/index.js b/index.js
index 57e2ae64..86d42ac1 100644
--- a/index.js
+++ b/index.js
@@ -1,48 +1,89 @@
// just pre-load all the stuff that index.js lazily exports
const internalRe = require('./internal/re')
+const constants = require('./internal/constants')
+const SemVer = require('./classes/semver')
+const identifiers = require('./internal/identifiers')
+const parse = require('./functions/parse')
+const valid = require('./functions/valid')
+const clean = require('./functions/clean')
+const inc = require('./functions/inc')
+const diff = require('./functions/diff')
+const major = require('./functions/major')
+const minor = require('./functions/minor')
+const patch = require('./functions/patch')
+const prerelease = require('./functions/prerelease')
+const compare = require('./functions/compare')
+const rcompare = require('./functions/rcompare')
+const compareLoose = require('./functions/compare-loose')
+const compareBuild = require('./functions/compare-build')
+const sort = require('./functions/sort')
+const rsort = require('./functions/rsort')
+const gt = require('./functions/gt')
+const lt = require('./functions/lt')
+const eq = require('./functions/eq')
+const neq = require('./functions/neq')
+const gte = require('./functions/gte')
+const lte = require('./functions/lte')
+const cmp = require('./functions/cmp')
+const coerce = require('./functions/coerce')
+const Comparator = require('./classes/comparator')
+const Range = require('./classes/range')
+const satisfies = require('./functions/satisfies')
+const toComparators = require('./ranges/to-comparators')
+const maxSatisfying = require('./ranges/max-satisfying')
+const minSatisfying = require('./ranges/min-satisfying')
+const minVersion = require('./ranges/min-version')
+const validRange = require('./ranges/valid')
+const outside = require('./ranges/outside')
+const gtr = require('./ranges/gtr')
+const ltr = require('./ranges/ltr')
+const intersects = require('./ranges/intersects')
+const simplifyRange = require('./ranges/simplify')
+const subset = require('./ranges/subset')
module.exports = {
+ parse,
+ valid,
+ clean,
+ inc,
+ diff,
+ major,
+ minor,
+ patch,
+ prerelease,
+ compare,
+ rcompare,
+ compareLoose,
+ compareBuild,
+ sort,
+ rsort,
+ gt,
+ lt,
+ eq,
+ neq,
+ gte,
+ lte,
+ cmp,
+ coerce,
+ Comparator,
+ Range,
+ satisfies,
+ toComparators,
+ maxSatisfying,
+ minSatisfying,
+ minVersion,
+ validRange,
+ outside,
+ gtr,
+ ltr,
+ intersects,
+ simplifyRange,
+ subset,
+ SemVer,
re: internalRe.re,
src: internalRe.src,
tokens: internalRe.t,
- SEMVER_SPEC_VERSION: require('./internal/constants').SEMVER_SPEC_VERSION,
- SemVer: require('./classes/semver'),
- compareIdentifiers: require('./internal/identifiers').compareIdentifiers,
- rcompareIdentifiers: require('./internal/identifiers').rcompareIdentifiers,
- parse: require('./functions/parse'),
- valid: require('./functions/valid'),
- clean: require('./functions/clean'),
- inc: require('./functions/inc'),
- diff: require('./functions/diff'),
- major: require('./functions/major'),
- minor: require('./functions/minor'),
- patch: require('./functions/patch'),
- prerelease: require('./functions/prerelease'),
- compare: require('./functions/compare'),
- rcompare: require('./functions/rcompare'),
- compareLoose: require('./functions/compare-loose'),
- compareBuild: require('./functions/compare-build'),
- sort: require('./functions/sort'),
- rsort: require('./functions/rsort'),
- gt: require('./functions/gt'),
- lt: require('./functions/lt'),
- eq: require('./functions/eq'),
- neq: require('./functions/neq'),
- gte: require('./functions/gte'),
- lte: require('./functions/lte'),
- cmp: require('./functions/cmp'),
- coerce: require('./functions/coerce'),
- Comparator: require('./classes/comparator'),
- Range: require('./classes/range'),
- satisfies: require('./functions/satisfies'),
- toComparators: require('./ranges/to-comparators'),
- maxSatisfying: require('./ranges/max-satisfying'),
- minSatisfying: require('./ranges/min-satisfying'),
- minVersion: require('./ranges/min-version'),
- validRange: require('./ranges/valid'),
- outside: require('./ranges/outside'),
- gtr: require('./ranges/gtr'),
- ltr: require('./ranges/ltr'),
- intersects: require('./ranges/intersects'),
- simplifyRange: require('./ranges/simplify'),
- subset: require('./ranges/subset'),
+ SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION,
+ RELEASE_TYPES: constants.RELEASE_TYPES,
+ compareIdentifiers: identifiers.compareIdentifiers,
+ rcompareIdentifiers: identifiers.rcompareIdentifiers,
}
diff --git a/internal/constants.js b/internal/constants.js
index 4f0de59b..94be1c57 100644
--- a/internal/constants.js
+++ b/internal/constants.js
@@ -9,9 +9,27 @@ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER ||
// Max safe segment length for coercion.
const MAX_SAFE_COMPONENT_LENGTH = 16
+// Max safe length for a build identifier. The max length minus 6 characters for
+// the shortest version with a build 0.0.0+BUILD.
+const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6
+
+const RELEASE_TYPES = [
+ 'major',
+ 'premajor',
+ 'minor',
+ 'preminor',
+ 'patch',
+ 'prepatch',
+ 'prerelease',
+]
+
module.exports = {
- SEMVER_SPEC_VERSION,
MAX_LENGTH,
- MAX_SAFE_INTEGER,
MAX_SAFE_COMPONENT_LENGTH,
+ MAX_SAFE_BUILD_LENGTH,
+ MAX_SAFE_INTEGER,
+ RELEASE_TYPES,
+ SEMVER_SPEC_VERSION,
+ FLAG_INCLUDE_PRERELEASE: 0b001,
+ FLAG_LOOSE: 0b010,
}
diff --git a/internal/parse-options.js b/internal/parse-options.js
index bbd9ec77..10d64ce0 100644
--- a/internal/parse-options.js
+++ b/internal/parse-options.js
@@ -1,11 +1,15 @@
-// parse out just the options we care about so we always get a consistent
-// obj with keys in a consistent order.
-const opts = ['includePrerelease', 'loose', 'rtl']
-const parseOptions = options =>
- !options ? {}
- : typeof options !== 'object' ? { loose: true }
- : opts.filter(k => options[k]).reduce((o, k) => {
- o[k] = true
- return o
- }, {})
+// parse out just the options we care about
+const looseOption = Object.freeze({ loose: true })
+const emptyOpts = Object.freeze({ })
+const parseOptions = options => {
+ if (!options) {
+ return emptyOpts
+ }
+
+ if (typeof options !== 'object') {
+ return looseOption
+ }
+
+ return options
+}
module.exports = parseOptions
diff --git a/internal/re.js b/internal/re.js
index ed88398a..21150b3e 100644
--- a/internal/re.js
+++ b/internal/re.js
@@ -1,19 +1,49 @@
-const { MAX_SAFE_COMPONENT_LENGTH } = require('./constants')
+const {
+ MAX_SAFE_COMPONENT_LENGTH,
+ MAX_SAFE_BUILD_LENGTH,
+ MAX_LENGTH,
+} = require('./constants')
const debug = require('./debug')
exports = module.exports = {}
// The actual regexps go on exports.re
const re = exports.re = []
+const safeRe = exports.safeRe = []
const src = exports.src = []
const t = exports.t = {}
let R = 0
+const LETTERDASHNUMBER = '[a-zA-Z0-9-]'
+
+// Replace some greedy regex tokens to prevent regex dos issues. These regex are
+// used internally via the safeRe object since all inputs in this library get
+// normalized first to trim and collapse all extra whitespace. The original
+// regexes are exported for userland consumption and lower level usage. A
+// future breaking change could export the safer regex only with a note that
+// all input should have extra whitespace removed.
+const safeRegexReplacements = [
+ ['\\s', 1],
+ ['\\d', MAX_LENGTH],
+ [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH],
+]
+
+const makeSafeRegex = (value) => {
+ for (const [token, max] of safeRegexReplacements) {
+ value = value
+ .split(`${token}*`).join(`${token}{0,${max}}`)
+ .split(`${token}+`).join(`${token}{1,${max}}`)
+ }
+ return value
+}
+
const createToken = (name, value, isGlobal) => {
+ const safe = makeSafeRegex(value)
const index = R++
debug(name, index, value)
t[name] = index
src[index] = value
re[index] = new RegExp(value, isGlobal ? 'g' : undefined)
+ safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined)
}
// The following Regular Expressions can be used for tokenizing,
@@ -23,13 +53,13 @@ const createToken = (name, value, isGlobal) => {
// A single `0`, or a non-zero digit followed by zero or more digits.
createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*')
-createToken('NUMERICIDENTIFIERLOOSE', '[0-9]+')
+createToken('NUMERICIDENTIFIERLOOSE', '\\d+')
// ## Non-numeric Identifier
// Zero or more digits, followed by a letter or hyphen, and then zero or
// more letters, digits, or hyphens.
-createToken('NONNUMERICIDENTIFIER', '\\d*[a-zA-Z-][a-zA-Z0-9-]*')
+createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`)
// ## Main Version
// Three dot-separated numeric identifiers.
@@ -64,7 +94,7 @@ createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE]
// ## Build Metadata Identifier
// Any combination of digits, letters, or hyphens.
-createToken('BUILDIDENTIFIER', '[0-9A-Za-z-]+')
+createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`)
// ## Build Metadata
// Plus sign, followed by one or more period-separated build metadata
diff --git a/package.json b/package.json
index 7898f590..c145eca2 100644
--- a/package.json
+++ b/package.json
@@ -1,24 +1,20 @@
{
"name": "semver",
- "version": "7.3.7",
+ "version": "7.5.4",
"description": "The semantic version parser used by npm.",
"main": "index.js",
"scripts": {
"test": "tap",
"snap": "tap",
- "preversion": "npm test",
- "postversion": "npm publish",
- "postpublish": "git push origin --follow-tags",
"lint": "eslint \"**/*.js\"",
"postlint": "template-oss-check",
"lintfix": "npm run lint -- --fix",
- "prepublishOnly": "git push origin --follow-tags",
"posttest": "npm run lint",
"template-oss-apply": "template-oss-apply --force"
},
"devDependencies": {
- "@npmcli/eslint-config": "^3.0.1",
- "@npmcli/template-oss": "3.3.2",
+ "@npmcli/eslint-config": "^4.0.0",
+ "@npmcli/template-oss": "4.17.0",
"tap": "^16.0.0"
},
"license": "ISC",
@@ -31,6 +27,7 @@
},
"files": [
"bin/",
+ "lib/",
"classes/",
"functions/",
"internal/",
@@ -40,8 +37,12 @@
"range.bnf"
],
"tap": {
- "check-coverage": true,
- "coverage-map": "map.js"
+ "timeout": 30,
+ "coverage-map": "map.js",
+ "nyc-arg": [
+ "--exclude",
+ "tap-snapshots/**"
+ ]
},
"engines": {
"node": ">=10"
@@ -52,17 +53,18 @@
"author": "GitHub Inc.",
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
- "version": "3.3.2",
+ "version": "4.17.0",
"engines": ">=10",
"ciVersions": [
"10.0.0",
"10.x",
"12.x",
"14.x",
- "16.x"
+ "16.x",
+ "18.x"
],
+ "npmSpec": "8",
"distPaths": [
- "bin/",
"classes/",
"functions/",
"internal/",
@@ -70,6 +72,16 @@
"index.js",
"preload.js",
"range.bnf"
- ]
+ ],
+ "allowPaths": [
+ "/classes/",
+ "/functions/",
+ "/internal/",
+ "/ranges/",
+ "/index.js",
+ "/preload.js",
+ "/range.bnf"
+ ],
+ "publish": "true"
}
}
diff --git a/ranges/intersects.js b/ranges/intersects.js
index 3d1a6f31..e0e9b7ce 100644
--- a/ranges/intersects.js
+++ b/ranges/intersects.js
@@ -2,6 +2,6 @@ const Range = require('../classes/range')
const intersects = (r1, r2, options) => {
r1 = new Range(r1, options)
r2 = new Range(r2, options)
- return r1.intersects(r2)
+ return r1.intersects(r2, options)
}
module.exports = intersects
diff --git a/ranges/subset.js b/ranges/subset.js
index e0dea43c..1e5c2683 100644
--- a/ranges/subset.js
+++ b/ranges/subset.js
@@ -68,6 +68,9 @@ const subset = (sub, dom, options = {}) => {
return true
}
+const minimumVersionWithPreRelease = [new Comparator('>=0.0.0-0')]
+const minimumVersion = [new Comparator('>=0.0.0')]
+
const simpleSubset = (sub, dom, options) => {
if (sub === dom) {
return true
@@ -77,9 +80,9 @@ const simpleSubset = (sub, dom, options) => {
if (dom.length === 1 && dom[0].semver === ANY) {
return true
} else if (options.includePrerelease) {
- sub = [new Comparator('>=0.0.0-0')]
+ sub = minimumVersionWithPreRelease
} else {
- sub = [new Comparator('>=0.0.0')]
+ sub = minimumVersion
}
}
@@ -87,7 +90,7 @@ const simpleSubset = (sub, dom, options) => {
if (options.includePrerelease) {
return true
} else {
- dom = [new Comparator('>=0.0.0')]
+ dom = minimumVersion
}
}
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 00000000..73d1e353
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,36 @@
+{
+ "exclude-packages-from-root": true,
+ "group-pull-request-title-pattern": "chore: release ${version}",
+ "pull-request-title-pattern": "chore: release${component} ${version}",
+ "changelog-sections": [
+ {
+ "type": "feat",
+ "section": "Features",
+ "hidden": false
+ },
+ {
+ "type": "fix",
+ "section": "Bug Fixes",
+ "hidden": false
+ },
+ {
+ "type": "docs",
+ "section": "Documentation",
+ "hidden": false
+ },
+ {
+ "type": "deps",
+ "section": "Dependencies",
+ "hidden": false
+ },
+ {
+ "type": "chore",
+ "hidden": true
+ }
+ ],
+ "packages": {
+ ".": {
+ "package-name": ""
+ }
+ }
+}
diff --git a/tap-snapshots/test/bin/semver.js.test.cjs b/tap-snapshots/test/bin/semver.js.test.cjs
index 4093fdab..e820ca47 100644
--- a/tap-snapshots/test/bin/semver.js.test.cjs
+++ b/tap-snapshots/test/bin/semver.js.test.cjs
@@ -93,6 +93,11 @@ Object {
--ltr
Coerce version strings left to right (default)
+ -n
+ Base number to be used for the prerelease identifier.
+ Can be either 0 or 1, or false to omit the number altogether.
+ Defaults to 0.
+
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
@@ -149,6 +154,11 @@ Object {
--ltr
Coerce version strings left to right (default)
+ -n
+ Base number to be used for the prerelease identifier.
+ Can be either 0 or 1, or false to omit the number altogether.
+ Defaults to 0.
+
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
@@ -205,6 +215,11 @@ Object {
--ltr
Coerce version strings left to right (default)
+ -n
+ Base number to be used for the prerelease identifier.
+ Can be either 0 or 1, or false to omit the number altogether.
+ Defaults to 0.
+
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
@@ -261,6 +276,11 @@ Object {
--ltr
Coerce version strings left to right (default)
+ -n
+ Base number to be used for the prerelease identifier.
+ Can be either 0 or 1, or false to omit the number altogether.
+ Defaults to 0.
+
Program exits successfully if any valid version satisfies
all supplied ranges, and prints all satisfying versions.
@@ -301,6 +321,24 @@ Object {
}
`
+exports[`test/bin/semver.js TAP inc tests > -i premajor 1.0.0 --preid=beta -n 1 1`] = `
+Object {
+ "code": 0,
+ "err": "",
+ "out": "2.0.0-beta.1\\n",
+ "signal": null,
+}
+`
+
+exports[`test/bin/semver.js TAP inc tests > -i premajor 1.0.0 --preid=beta -n false 1`] = `
+Object {
+ "code": 0,
+ "err": "",
+ "out": "2.0.0-beta\\n",
+ "signal": null,
+}
+`
+
exports[`test/bin/semver.js TAP inc tests > -i premajor 1.0.0 --preid=beta 1`] = `
Object {
"code": 0,
diff --git a/test/bin/semver.js b/test/bin/semver.js
index 04333fd5..262ca380 100644
--- a/test/bin/semver.js
+++ b/test/bin/semver.js
@@ -29,6 +29,8 @@ t.test('inc tests', t => Promise.all([
['-i', 'major', '1.0.0'],
['-i', 'major', '1.0.0', '1.0.1'],
['-i', 'premajor', '1.0.0', '--preid=beta'],
+ ['-i', 'premajor', '1.0.0', '--preid=beta', '-n', '1'],
+ ['-i', 'premajor', '1.0.0', '--preid=beta', '-n', 'false'],
['-i', '1.2.3'],
].map(args => t.resolveMatchSnapshot(run(args), args.join(' ')))))
diff --git a/test/classes/comparator.js b/test/classes/comparator.js
index 6c2e1215..209a024b 100644
--- a/test/classes/comparator.js
+++ b/test/classes/comparator.js
@@ -22,17 +22,18 @@ test('tostrings', (t) => {
test('intersect comparators', (t) => {
t.plan(comparatorIntersection.length)
- comparatorIntersection.forEach(([c0, c1, expect]) => t.test(`${c0} ${c1} ${expect}`, t => {
- const comp0 = new Comparator(c0)
- const comp1 = new Comparator(c1)
-
- t.equal(comp0.intersects(comp1, false), expect,
- `${c0} intersects ${c1}`)
-
- t.equal(comp1.intersects(comp0, { loose: false }), expect,
- `${c1} intersects ${c0}`)
- t.end()
- }))
+ comparatorIntersection.forEach(([c0, c1, expect, includePrerelease]) =>
+ t.test(`${c0} ${c1} ${expect}`, t => {
+ const comp0 = new Comparator(c0)
+ const comp1 = new Comparator(c1)
+
+ t.equal(comp0.intersects(comp1, { includePrerelease }), expect,
+ `${c0} intersects ${c1}`)
+
+ t.equal(comp1.intersects(comp0, { includePrerelease }), expect,
+ `${c1} intersects ${c0}`)
+ t.end()
+ }))
})
test('intersect demands another comparator', t => {
diff --git a/test/classes/semver.js b/test/classes/semver.js
index 50619c0f..85a0ec31 100644
--- a/test/classes/semver.js
+++ b/test/classes/semver.js
@@ -62,15 +62,19 @@ test('really big numeric prerelease value', (t) => {
})
test('invalid version numbers', (t) => {
- ['1.2.3.4',
- 'NOT VALID',
- 1.2,
- null,
- 'Infinity.NaN.Infinity',
- ].forEach((v) => {
- t.throws(() => {
- new SemVer(v) // eslint-disable-line no-new
- }, { name: 'TypeError', message: `Invalid Version: ${v}` })
+ ['1.2.3.4', 'NOT VALID', 1.2, null, 'Infinity.NaN.Infinity'].forEach((v) => {
+ t.throws(
+ () => {
+ new SemVer(v) // eslint-disable-line no-new
+ },
+ {
+ name: 'TypeError',
+ message:
+ typeof v === 'string'
+ ? `Invalid Version: ${v}`
+ : `Invalid version. Must be a string. Got type "${typeof v}".`,
+ }
+ )
})
t.end()
@@ -84,12 +88,20 @@ test('incrementing', t => {
expect,
options,
id,
+ base,
]) => t.test(`${version} ${inc} ${id || ''}`.trim(), t => {
- t.plan(1)
if (expect === null) {
- t.throws(() => new SemVer(version, options).inc(inc, id))
+ t.plan(1)
+ t.throws(() => new SemVer(version, options).inc(inc, id, base))
} else {
- t.equal(new SemVer(version, options).inc(inc, id).version, expect)
+ t.plan(2)
+ const incremented = new SemVer(version, options).inc(inc, id, base)
+ t.equal(incremented.version, expect)
+ if (incremented.build.length) {
+ t.equal(incremented.raw, `${expect}+${incremented.build.join('.')}`)
+ } else {
+ t.equal(incremented.raw, expect)
+ }
}
}))
})
@@ -111,21 +123,6 @@ test('compare main vs pre', (t) => {
t.end()
})
-test('invalid version numbers', (t) => {
- ['1.2.3.4',
- 'NOT VALID',
- 1.2,
- null,
- 'Infinity.NaN.Infinity',
- ].forEach((v) => {
- t.throws(() => {
- new SemVer(v) // eslint-disable-line no-new
- }, { name: 'TypeError', message: `Invalid Version: ${v}` })
- })
-
- t.end()
-})
-
test('compareBuild', (t) => {
const noBuild = new SemVer('1.0.0')
const build0 = new SemVer('1.0.0+0')
diff --git a/test/fixtures/comparator-intersection.js b/test/fixtures/comparator-intersection.js
index 5f24acce..1d777d38 100644
--- a/test/fixtures/comparator-intersection.js
+++ b/test/fixtures/comparator-intersection.js
@@ -1,4 +1,4 @@
-// c0, c1, expected intersection
+// c0, c1, expected intersection, includePrerelease
module.exports = [
// One is a Version
['1.3.0', '>=1.3.0', true],
@@ -33,4 +33,10 @@ module.exports = [
['', '', true],
['', '>1.0.0', true],
['<=2.0.0', '', true],
+ ['<0.0.0', '<0.1.0', false],
+ ['<0.1.0', '<0.0.0', false],
+ ['<0.0.0-0', '<0.1.0', false],
+ ['<0.1.0', '<0.0.0-0', false],
+ ['<0.0.0-0', '<0.1.0', false, true],
+ ['<0.1.0', '<0.0.0-0', false, true],
]
diff --git a/test/fixtures/increments.js b/test/fixtures/increments.js
index 6a998b5f..65e9530b 100644
--- a/test/fixtures/increments.js
+++ b/test/fixtures/increments.js
@@ -1,5 +1,5 @@
-// [version, inc, result, options, identifier]
-// inc(version, inc) -> result
+// [version, inc, result, options, identifier, identifierBase]
+// inc(version, inc, options, identifier, identifierBase) -> result
module.exports = [
['1.2.3', 'major', '2.0.0'],
['1.2.3', 'minor', '1.3.0'],
@@ -79,12 +79,49 @@ module.exports = [
['1.2.3-1', 'preminor', '1.3.0-dev.0', false, 'dev'],
['1.2.0', 'premajor', '2.0.0-dev.0', false, 'dev'],
['1.2.3-1', 'premajor', '2.0.0-dev.0', false, 'dev'],
+ ['1.2.3-1', 'premajor', '2.0.0-dev.1', false, 'dev', 1],
['1.2.0-1', 'minor', '1.2.0', false, 'dev'],
['1.0.0-1', 'major', '1.0.0', 'dev'],
['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.0', false, 'dev'],
-
['1.2.3-0', 'prerelease', '1.2.3-1.0', false, '1'],
['1.2.3-1.0', 'prerelease', '1.2.3-1.1', false, '1'],
['1.2.3-1.1', 'prerelease', '1.2.3-1.2', false, '1'],
['1.2.3-1.1', 'prerelease', '1.2.3-2.0', false, '2'],
+
+ // [version, inc, result, identifierIndex, loose, identifier]
+ ['1.2.0-1', 'prerelease', '1.2.0-alpha.0', false, 'alpha', '0'],
+ ['1.2.1', 'prerelease', '1.2.2-alpha.0', false, 'alpha', '0'],
+ ['0.2.0', 'prerelease', '0.2.1-alpha.0', false, 'alpha', '0'],
+ ['1.2.2', 'prerelease', '1.2.3-alpha.1', false, 'alpha', '1'],
+ ['1.2.3', 'prerelease', '1.2.4-alpha.1', false, 'alpha', '1'],
+ ['1.2.4', 'prerelease', '1.2.5-alpha.1', false, 'alpha', '1'],
+ ['1.2.0', 'prepatch', '1.2.1-dev.1', false, 'dev', '1'],
+ ['1.2.0-1', 'prepatch', '1.2.1-dev.1', false, 'dev', '1'],
+ ['1.2.0', 'premajor', '2.0.0-dev.0', false, 'dev', '0'],
+ ['1.2.3-1', 'premajor', '2.0.0-dev.0', false, 'dev', '0'],
+ ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.0', false, 'dev', '0'],
+ ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.1', false, 'dev', '1'],
+ ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.bar.0', false, '', '0'],
+ ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.bar.1', false, '', '1'],
+ ['1.2.0', 'preminor', '1.3.0-dev.1', false, 'dev', '1'],
+ ['1.2.3-1', 'preminor', '1.3.0-dev.0', false, 'dev'],
+ ['1.2.0', 'prerelease', '1.2.1-1', false, '', '1'],
+
+ ['1.2.0-1', 'prerelease', '1.2.0-alpha', false, 'alpha', false],
+ ['1.2.1', 'prerelease', '1.2.2-alpha', false, 'alpha', false],
+ ['1.2.2', 'prerelease', '1.2.3-alpha', false, 'alpha', false],
+ ['1.2.0', 'prepatch', '1.2.1-dev', false, 'dev', false],
+ ['1.2.0-1', 'prepatch', '1.2.1-dev', false, 'dev', false],
+ ['1.2.0', 'premajor', '2.0.0-dev', false, 'dev', false],
+ ['1.2.3-1', 'premajor', '2.0.0-dev', false, 'dev', false],
+ ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev', false, 'dev', false],
+ ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.baz', false, 'dev.baz', false],
+ ['1.2.0', 'preminor', '1.3.0-dev', false, 'dev', false],
+ ['1.2.3-1', 'preminor', '1.3.0-dev', false, 'dev', false],
+ ['1.2.3-dev', 'prerelease', null, false, 'dev', false],
+ ['1.2.0-dev', 'premajor', '2.0.0-dev', false, 'dev', false],
+ ['1.2.0-dev', 'preminor', '1.3.0-beta', false, 'beta', false],
+ ['1.2.0-dev', 'prepatch', '1.2.1-dev', false, 'dev', false],
+ ['1.2.0', 'prerelease', null, false, '', false],
+ ['1.0.0-rc.1+build.4', 'prerelease', '1.0.0-rc.2', 'rc', false],
]
diff --git a/test/fixtures/range-exclude.js b/test/fixtures/range-exclude.js
index 9e829445..2789148a 100644
--- a/test/fixtures/range-exclude.js
+++ b/test/fixtures/range-exclude.js
@@ -80,7 +80,6 @@ module.exports = [
['^1.0.0', '1.0.0-rc1', { includePrerelease: true }],
['^1.0.0', '2.0.0-rc1', { includePrerelease: true }],
['^1.2.3-rc2', '2.0.0', { includePrerelease: true }],
- ['^1.0.0', '2.0.0-rc1', { includePrerelease: true }],
['^1.0.0', '2.0.0-rc1'],
['1 - 2', '3.0.0-pre', { includePrerelease: true }],
@@ -103,4 +102,6 @@ module.exports = [
['>=1.0.0 <1.1.0', '1.1.0', { includePrerelease: true }],
['>=1.0.0 <1.1.0', '1.1.0-pre'],
['>=1.0.0 <1.1.0-pre', '1.1.0-pre'],
+
+ ['== 1.0.0 || foo', '2.0.0', { loose: true }],
]
diff --git a/test/fixtures/range-include.js b/test/fixtures/range-include.js
index da20f6ce..cdb7034b 100644
--- a/test/fixtures/range-include.js
+++ b/test/fixtures/range-include.js
@@ -84,7 +84,6 @@ module.exports = [
['~1.2.1 1.2.3', '1.2.3'],
['~1.2.1 >=1.2.3 1.2.3', '1.2.3'],
['~1.2.1 1.2.3 >=1.2.3', '1.2.3'],
- ['~1.2.1 1.2.3', '1.2.3'],
['>=1.2.1 1.2.3', '1.2.3'],
['1.2.3 >=1.2.1', '1.2.3'],
['>=1.2.3 >=1.2.1', '1.2.3'],
diff --git a/test/fixtures/range-parse.js b/test/fixtures/range-parse.js
index 83adaff8..dcafc6b5 100644
--- a/test/fixtures/range-parse.js
+++ b/test/fixtures/range-parse.js
@@ -14,14 +14,10 @@ module.exports = [
['>=*', '*'],
['', '*'],
['*', '*'],
- ['*', '*'],
['>=1.0.0', '>=1.0.0'],
['>1.0.0', '>1.0.0'],
['<=2.0.0', '<=2.0.0'],
['1', '>=1.0.0 <2.0.0-0'],
- ['<=2.0.0', '<=2.0.0'],
- ['<=2.0.0', '<=2.0.0'],
- ['<2.0.0', '<2.0.0'],
['<2.0.0', '<2.0.0'],
['>= 1.0.0', '>=1.0.0'],
['>= 1.0.0', '>=1.0.0'],
@@ -34,25 +30,19 @@ module.exports = [
['< 2.0.0', '<2.0.0'],
['<\t2.0.0', '<2.0.0'],
['>=0.1.97', '>=0.1.97'],
- ['>=0.1.97', '>=0.1.97'],
['0.1.20 || 1.2.4', '0.1.20||1.2.4'],
['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1'],
- ['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1'],
- ['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1'],
['||', '*'],
['2.x.x', '>=2.0.0 <3.0.0-0'],
['1.2.x', '>=1.2.0 <1.3.0-0'],
['1.2.x || 2.x', '>=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0'],
- ['1.2.x || 2.x', '>=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0'],
['x', '*'],
['2.*.*', '>=2.0.0 <3.0.0-0'],
['1.2.*', '>=1.2.0 <1.3.0-0'],
['1.2.* || 2.*', '>=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0'],
- ['*', '*'],
['2', '>=2.0.0 <3.0.0-0'],
['2.3', '>=2.3.0 <2.4.0-0'],
['~2.4', '>=2.4.0 <2.5.0-0'],
- ['~2.4', '>=2.4.0 <2.5.0-0'],
['~>3.2.1', '>=3.2.1 <3.3.0-0'],
['~1', '>=1.0.0 <2.0.0-0'],
['~>1', '>=1.0.0 <2.0.0-0'],
@@ -75,7 +65,6 @@ module.exports = [
['>= 1', '>=1.0.0'],
['<1.2', '<1.2.0-0'],
['< 1.2', '<1.2.0-0'],
- ['1', '>=1.0.0 <2.0.0-0'],
['>01.02.03', '>1.2.3', true],
['>01.02.03', null],
['~1.2.3beta', '>=1.2.3-beta <1.3.0-0', { loose: true }],
diff --git a/test/fixtures/version-gt-range.js b/test/fixtures/version-gt-range.js
index c30055e0..51049924 100644
--- a/test/fixtures/version-gt-range.js
+++ b/test/fixtures/version-gt-range.js
@@ -34,7 +34,6 @@ module.exports = [
['=0.7.x', '0.8.0'],
['=0.7.x', '0.8.0-asdf'],
['<0.7.x', '0.7.0'],
- ['~1.2.2', '1.3.0'],
['1.0.0 - 2.0.0', '2.2.3'],
['1.0.0', '1.0.1'],
['<=2.0.0', '3.0.0'],
@@ -46,17 +45,13 @@ module.exports = [
['1.2.x', '1.3.3'],
['1.2.x || 2.x', '3.1.3'],
['2.*.*', '3.1.3'],
- ['1.2.*', '1.3.3'],
['1.2.* || 2.*', '3.1.3'],
['2', '3.1.2'],
['2.3', '2.4.1'],
- ['~2.4', '2.5.0'], // >=2.4.0 <2.5.0
['~>3.2.1', '3.3.2'], // >=3.2.1 <3.3.0
- ['~1', '2.2.3'], // >=1.0.0 <2.0.0
['~>1', '2.2.3'],
['~1.0', '1.1.0'], // >=1.0.0 <1.1.0
['<1', '1.0.0'],
- ['1', '2.0.0beta', true],
['<1', '1.0.0beta', true],
['< 1', '1.0.0beta', true],
['=0.7.x', '0.8.2'],
diff --git a/test/fixtures/version-lt-range.js b/test/fixtures/version-lt-range.js
index 5482dbf2..55fe5c17 100644
--- a/test/fixtures/version-lt-range.js
+++ b/test/fixtures/version-lt-range.js
@@ -30,29 +30,22 @@ module.exports = [
['> 1.2', '1.2.1'],
['1', '0.0.0beta', true],
['~v0.5.4-pre', '0.5.4-alpha'],
- ['~v0.5.4-pre', '0.5.4-alpha'],
['=0.7.x', '0.6.0'],
['=0.7.x', '0.6.0-asdf'],
['>=0.7.x', '0.6.0'],
- ['~1.2.2', '1.2.1'],
['1.0.0 - 2.0.0', '0.2.3'],
['1.0.0', '0.0.1'],
['>=2.0.0', '1.0.0'],
['>=2.0.0', '1.9999.9999'],
- ['>=2.0.0', '1.2.9'],
- ['>2.0.0', '2.0.0'],
['>2.0.0', '1.2.9'],
['2.x.x', '1.1.3'],
['1.2.x', '1.1.3'],
['1.2.x || 2.x', '1.1.3'],
['2.*.*', '1.1.3'],
- ['1.2.*', '1.1.3'],
['1.2.* || 2.*', '1.1.3'],
['2', '1.9999.9999'],
['2.3', '2.2.1'],
- ['~2.4', '2.3.0'], // >=2.4.0 <2.5.0
['~>3.2.1', '2.3.2'], // >=3.2.1 <3.3.0
- ['~1', '0.2.3'], // >=1.0.0 <2.0.0
['~>1', '0.2.3'],
['~1.0', '0.0.0'], // >=1.0.0 <1.1.0
['>1', '1.0.0'],
diff --git a/test/fixtures/version-not-gt-range.js b/test/fixtures/version-not-gt-range.js
index 15e7b6af..1f40738e 100644
--- a/test/fixtures/version-not-gt-range.js
+++ b/test/fixtures/version-not-gt-range.js
@@ -48,8 +48,6 @@ module.exports = [
['1.2.*', '1.2.3'],
['1.2.* || 2.*', '2.1.3'],
['1.2.* || 2.*', '1.2.3'],
- ['1.2.* || 2.*', '1.2.3'],
- ['*', '1.2.3'],
['2', '2.1.2'],
['2.3', '2.3.1'],
['~2.4', '2.4.0'], // >=2.4.0 <2.5.0
diff --git a/test/fixtures/version-not-lt-range.js b/test/fixtures/version-not-lt-range.js
index 35954f7a..aac7c254 100644
--- a/test/fixtures/version-not-lt-range.js
+++ b/test/fixtures/version-not-lt-range.js
@@ -48,8 +48,6 @@ module.exports = [
['1.2.*', '1.2.3'],
['1.2.* || 2.*', '2.1.3'],
['1.2.* || 2.*', '1.2.3'],
- ['1.2.* || 2.*', '1.2.3'],
- ['*', '1.2.3'],
['2', '2.1.2'],
['2.3', '2.3.1'],
['~2.4', '2.4.0'], // >=2.4.0 <2.5.0
diff --git a/test/functions/diff.js b/test/functions/diff.js
index 2369c98f..720e159b 100644
--- a/test/functions/diff.js
+++ b/test/functions/diff.js
@@ -4,19 +4,36 @@ const diff = require('../../functions/diff')
test('diff versions test', (t) => {
// [version1, version2, result]
// diff(version1, version2) -> result
- [['1.2.3', '0.2.3', 'major'],
+ [
+ ['1.2.3', '0.2.3', 'major'],
+ ['0.2.3', '1.2.3', 'major'],
['1.4.5', '0.2.3', 'major'],
['1.2.3', '2.0.0-pre', 'premajor'],
+ ['2.0.0-pre', '1.2.3', 'premajor'],
['1.2.3', '1.3.3', 'minor'],
['1.0.1', '1.1.0-pre', 'preminor'],
['1.2.3', '1.2.4', 'patch'],
['1.2.3', '1.2.4-pre', 'prepatch'],
- ['0.0.1', '0.0.1-pre', 'prerelease'],
- ['0.0.1', '0.0.1-pre-2', 'prerelease'],
- ['1.1.0', '1.1.0-pre', 'prerelease'],
+ ['0.0.1', '0.0.1-pre', 'patch'],
+ ['0.0.1', '0.0.1-pre-2', 'patch'],
+ ['1.1.0', '1.1.0-pre', 'minor'],
['1.1.0-pre-1', '1.1.0-pre-2', 'prerelease'],
['1.0.0', '1.0.0', null],
-
+ ['1.0.0-1', '1.0.0-1', null],
+ ['0.0.2-1', '0.0.2', 'patch'],
+ ['0.0.2-1', '0.0.3', 'patch'],
+ ['0.0.2-1', '0.1.0', 'minor'],
+ ['0.0.2-1', '1.0.0', 'major'],
+ ['0.1.0-1', '0.1.0', 'minor'],
+ ['1.0.0-1', '1.0.0', 'major'],
+ ['1.0.0-1', '1.1.1', 'major'],
+ ['1.0.0-1', '2.1.1', 'major'],
+ ['1.0.1-1', '1.0.1', 'patch'],
+ ['0.0.0-1', '0.0.0', 'major'],
+ ['1.0.0-1', '2.0.0', 'major'],
+ ['1.0.0-1', '2.0.0-1', 'premajor'],
+ ['1.0.0-1', '1.1.0-1', 'preminor'],
+ ['1.0.0-1', '1.0.1-1', 'prepatch'],
].forEach((v) => {
const version1 = v[0]
const version2 = v[1]
@@ -28,3 +45,13 @@ test('diff versions test', (t) => {
t.end()
})
+
+test('throws on bad version', (t) => {
+ t.throws(() => {
+ diff('bad', '1.2.3')
+ }, {
+ message: 'Invalid Version: bad',
+ name: 'TypeError',
+ })
+ t.end()
+})
diff --git a/test/functions/inc.js b/test/functions/inc.js
index 909debdf..2f6f9bb4 100644
--- a/test/functions/inc.js
+++ b/test/functions/inc.js
@@ -4,20 +4,28 @@ const parse = require('../../functions/parse')
const increments = require('../fixtures/increments.js')
test('increment versions test', (t) => {
- increments.forEach(([pre, what, wanted, options, id]) => {
- const found = inc(pre, what, options, id)
- const cmd = `inc(${pre}, ${what}, ${id})`
+ increments.forEach(([pre, what, wanted, options, id, base]) => {
+ const found = inc(pre, what, options, id, base)
+ const cmd = `inc(${pre}, ${what}, ${id}, ${base})`
t.equal(found, wanted, `${cmd} === ${wanted}`)
const parsed = parse(pre, options)
const parsedAsInput = parse(pre, options)
if (wanted) {
- parsed.inc(what, id)
+ parsed.inc(what, id, base)
t.equal(parsed.version, wanted, `${cmd} object version updated`)
- t.equal(parsed.raw, wanted, `${cmd} object raw field updated`)
+ if (parsed.build.length) {
+ t.equal(
+ parsed.raw,
+ `${wanted}+${parsed.build.join('.')}`,
+ `${cmd} object raw field updated with build`
+ )
+ } else {
+ t.equal(parsed.raw, wanted, `${cmd} object raw field updated`)
+ }
const preIncObject = JSON.stringify(parsedAsInput)
- inc(parsedAsInput, what, options, id)
+ inc(parsedAsInput, what, options, id, base)
const postIncObject = JSON.stringify(parsedAsInput)
t.equal(
postIncObject,
@@ -26,7 +34,7 @@ test('increment versions test', (t) => {
)
} else if (parsed) {
t.throws(() => {
- parsed.inc(what, id)
+ parsed.inc(what, id, base)
})
} else {
t.equal(parsed, null)
diff --git a/test/functions/parse.js b/test/functions/parse.js
index 16183dc0..dd091e94 100644
--- a/test/functions/parse.js
+++ b/test/functions/parse.js
@@ -9,6 +9,22 @@ t.test('returns null instead of throwing when presented with garbage', t => {
t.equal(parse(v, opts), null, msg))
})
+t.test('throw errors if asked to', t => {
+ t.throws(() => {
+ parse('bad', null, true)
+ }, {
+ name: 'TypeError',
+ message: 'Invalid Version: bad',
+ })
+ t.throws(() => {
+ parse([], null, true)
+ }, {
+ name: 'TypeError',
+ message: 'Invalid version. Must be a string. Got type "object".',
+ })
+ t.end()
+})
+
t.test('parse a version into a SemVer object', t => {
t.match(parse('1.2.3'), new SemVer('1.2.3'))
const s = new SemVer('4.5.6')
diff --git a/test/functions/valid.js b/test/functions/valid.js
index ab51fed3..33399ed7 100644
--- a/test/functions/valid.js
+++ b/test/functions/valid.js
@@ -2,6 +2,7 @@ const t = require('tap')
const valid = require('../../functions/valid')
const SemVer = require('../../classes/semver')
const invalidVersions = require('../fixtures/invalid-versions')
+const { MAX_SAFE_INTEGER } = require('../../internal/constants')
t.test('returns null instead of throwing when presented with garbage', t => {
t.plan(invalidVersions.length)
@@ -17,3 +18,12 @@ t.test('validate a version into a SemVer object', t => {
t.equal(valid('4.2.0foo', { loose: true }), '4.2.0-foo', 'looseness as an option')
t.end()
})
+
+t.test('long build id', t => {
+ const longBuild = '-928490632884417731e7af463c92b034d6a78268fc993bcb88a57944'
+ const shortVersion = '1.1.1'
+ const longVersion = `${MAX_SAFE_INTEGER}.${MAX_SAFE_INTEGER}.${MAX_SAFE_INTEGER}`
+ t.equal(valid(shortVersion + longBuild), shortVersion + longBuild)
+ t.equal(valid(longVersion + longBuild), longVersion + longBuild)
+ t.end()
+})
diff --git a/test/integration/whitespace.js b/test/integration/whitespace.js
new file mode 100644
index 00000000..a3541325
--- /dev/null
+++ b/test/integration/whitespace.js
@@ -0,0 +1,49 @@
+const { test } = require('tap')
+const Range = require('../../classes/range')
+const SemVer = require('../../classes/semver')
+const Comparator = require('../../classes/comparator')
+const validRange = require('../../ranges/valid')
+const minVersion = require('../../ranges/min-version')
+const minSatisfying = require('../../ranges/min-satisfying')
+const maxSatisfying = require('../../ranges/max-satisfying')
+
+const wsMedium = ' '.repeat(125)
+const wsLarge = ' '.repeat(500000)
+const zeroLarge = '0'.repeat(500000)
+
+test('range with whitespace', (t) => {
+ // a range with these extra characters would take a few minutes to process if
+ // any redos susceptible regexes were used. there is a global tap timeout per
+ // file set in the package.json that will error if this test takes too long.
+ const r = `1.2.3 ${wsLarge} <1.3.0`
+ t.equal(new Range(r).range, '1.2.3 <1.3.0')
+ t.equal(validRange(r), '1.2.3 <1.3.0')
+ t.equal(minVersion(r).version, '1.2.3')
+ t.equal(minSatisfying(['1.2.3'], r), '1.2.3')
+ t.equal(maxSatisfying(['1.2.3'], r), '1.2.3')
+ t.end()
+})
+
+test('range with 0', (t) => {
+ const r = `1.2.3 ${zeroLarge} <1.3.0`
+ t.throws(() => new Range(r).range)
+ t.equal(validRange(r), null)
+ t.throws(() => minVersion(r).version)
+ t.equal(minSatisfying(['1.2.3'], r), null)
+ t.equal(maxSatisfying(['1.2.3'], r), null)
+ t.end()
+})
+
+test('semver version', (t) => {
+ const v = `${wsMedium}1.2.3${wsMedium}`
+ const tooLong = `${wsLarge}1.2.3${wsLarge}`
+ t.equal(new SemVer(v).version, '1.2.3')
+ t.throws(() => new SemVer(tooLong))
+ t.end()
+})
+
+test('comparator', (t) => {
+ const comparator = `${wsLarge}<${wsLarge}1.2.3${wsLarge}`
+ t.equal(new Comparator(comparator).value, '<1.2.3')
+ t.end()
+})
diff --git a/test/internal/constants.js b/test/internal/constants.js
index 1b72d870..a8f6ab2d 100644
--- a/test/internal/constants.js
+++ b/test/internal/constants.js
@@ -2,8 +2,9 @@ const t = require('tap')
const constants = require('../../internal/constants')
t.match(constants, {
- SEMVER_SPEC_VERSION: String,
MAX_LENGTH: Number,
- MAX_SAFE_INTEGER: Number,
MAX_SAFE_COMPONENT_LENGTH: Number,
-}, 'got some numbers exported')
+ MAX_SAFE_INTEGER: Number,
+ RELEASE_TYPES: Array,
+ SEMVER_SPEC_VERSION: String,
+}, 'got appropriate data types exported')
diff --git a/test/internal/parse-options.js b/test/internal/parse-options.js
index 6213423d..2400537d 100644
--- a/test/internal/parse-options.js
+++ b/test/internal/parse-options.js
@@ -18,12 +18,24 @@ t.test('truthy non-objects always loose mode, for backwards comp', t => {
t.end()
})
-t.test('objects only include truthy flags we know about, set to true', t => {
- t.strictSame(parseOptions(/asdf/), {})
- t.strictSame(parseOptions(new Error('hello')), {})
- t.strictSame(parseOptions({ loose: true, a: 1, rtl: false }), { loose: true })
+t.test('any object passed is returned', t => {
+ t.strictSame(parseOptions(/asdf/), /asdf/)
+ t.strictSame(parseOptions(new Error('hello')), new Error('hello'))
+ t.strictSame(parseOptions({ loose: true, a: 1, rtl: false }), { loose: true, a: 1, rtl: false })
t.strictSame(parseOptions({ loose: 1, rtl: 2, includePrerelease: 10 }), {
+ loose: 1,
+ rtl: 2,
+ includePrerelease: 10,
+ })
+ t.strictSame(parseOptions({ loose: true }), { loose: true })
+ t.strictSame(parseOptions({ rtl: true }), { rtl: true })
+ t.strictSame(parseOptions({ includePrerelease: true }), { includePrerelease: true })
+ t.strictSame(parseOptions({ loose: true, rtl: true }), { loose: true, rtl: true })
+ t.strictSame(parseOptions({ loose: true, includePrerelease: true }), {
loose: true,
+ includePrerelease: true,
+ })
+ t.strictSame(parseOptions({ rtl: true, includePrerelease: true }), {
rtl: true,
includePrerelease: true,
})
diff --git a/test/internal/re.js b/test/internal/re.js
index 1aad22ba..2851b325 100644
--- a/test/internal/re.js
+++ b/test/internal/re.js
@@ -1,5 +1,5 @@
const { test } = require('tap')
-const { src, re } = require('../../internal/re')
+const { src, re, safeRe } = require('../../internal/re')
const semver = require('../../')
test('has a list of src, re, and tokens', (t) => {
@@ -13,5 +13,11 @@ test('has a list of src, re, and tokens', (t) => {
for (const i in semver.tokens) {
t.match(semver.tokens[i], Number, 'tokens are numbers')
}
+
+ safeRe.forEach(r => {
+ t.notMatch(r.source, '\\s+', 'safe regex do not contain greedy whitespace')
+ t.notMatch(r.source, '\\s*', 'safe regex do not contain greedy whitespace')
+ })
+
t.end()
})
diff --git a/test/map.js b/test/map.js
index aded2454..5c36eb7d 100644
--- a/test/map.js
+++ b/test/map.js
@@ -1,48 +1,46 @@
const t = require('tap')
+const { resolve, join, relative, extname, dirname, basename } = require('path')
+const { statSync, readdirSync } = require('fs')
+const map = require('../map.js')
+const pkg = require('../package.json')
-// ensure that the coverage map maps all coverage
-const ignore = [
- '.git',
- '.github',
- '.commitlintrc.js',
- '.eslintrc.js',
- 'node_modules',
- 'coverage',
- 'tap-snapshots',
- 'test',
- 'fixtures',
-]
+const ROOT = resolve(__dirname, '..')
+const TEST = join(ROOT, 'test')
+const IGNORE_DIRS = ['fixtures', 'integration']
-const { statSync, readdirSync } = require('fs')
-const find = (folder, set = [], root = true) => {
- const ent = readdirSync(folder)
- set.push(...ent.filter(f => !ignore.includes(f) && /\.m?js$/.test(f)).map(f => folder + '/' + f))
- for (const e of ent.filter(f => !ignore.includes(f) && !/\.m?js$/.test(f))) {
- if (statSync(folder + '/' + e).isDirectory()) {
- find(folder + '/' + e, set, false)
+const getFile = (f) => {
+ try {
+ if (statSync(f).isFile()) {
+ return extname(f) === '.js' ? [f] : []
}
+ } catch {
+ return []
}
- if (!root) {
- return
- }
- return set.map(f => f.slice(folder.length + 1)
- .replace(/\\/g, '/'))
- .sort((a, b) => a.localeCompare(b))
}
-const { resolve } = require('path')
-const root = resolve(__dirname, '..')
+const walk = (item, res = []) => getFile(item) || readdirSync(item)
+ .map(f => join(item, f))
+ .reduce((acc, f) => acc.concat(statSync(f).isDirectory() ? walk(f, res) : getFile(f)), [])
+ .filter(Boolean)
-const sut = find(root)
-const tests = find(root + '/test')
-t.strictSame(sut, tests, 'test files should match system files')
-const map = require('../map.js')
+const walkAll = (items, relativeTo) => items
+ .reduce((acc, f) => acc.concat(walk(join(ROOT, f))), [])
+ .map((f) => relative(relativeTo, f))
+ .sort()
-for (const testFile of tests) {
- t.test(testFile, t => {
- t.plan(1)
- // cast to an array, since map() can return a string or array
- const systemFiles = [].concat(map(testFile))
- t.ok(systemFiles.some(sys => sut.includes(sys)), 'test covers a file')
- })
-}
+t.test('tests match system', t => {
+ const sut = walkAll([pkg.tap['coverage-map'], ...pkg.files], ROOT)
+ const tests = walkAll([basename(TEST)], TEST)
+ .filter(f => !IGNORE_DIRS.includes(dirname(f)))
+
+ t.strictSame(sut, tests, 'test files should match system files')
+
+ for (const f of tests) {
+ t.test(f, t => {
+ t.plan(1)
+ t.ok(sut.includes(map(f)), 'test covers a file')
+ })
+ }
+
+ t.end()
+})
diff --git a/test/ranges/intersects.js b/test/ranges/intersects.js
index e93492b7..b23ad03d 100644
--- a/test/ranges/intersects.js
+++ b/test/ranges/intersects.js
@@ -7,24 +7,24 @@ const rangeIntersection = require('../fixtures/range-intersection.js')
test('intersect comparators', t => {
t.plan(comparatorIntersection.length)
- comparatorIntersection.forEach(([c0, c1, expect]) => t.test(`${c0} ${c1} ${expect}`, t => {
- const comp0 = new Comparator(c0)
- const comp1 = new Comparator(c1)
+ comparatorIntersection.forEach(([c0, c1, expect, includePrerelease]) =>
+ t.test(`${c0} ${c1} ${expect}`, t => {
+ const opts = { loose: false, includePrerelease }
+ const comp0 = new Comparator(c0)
+ const comp1 = new Comparator(c1)
- t.equal(intersects(comp0, comp1), expect, `${c0} intersects ${c1} objects`)
- t.equal(intersects(comp1, comp0), expect, `${c1} intersects ${c0} objects`)
- t.equal(intersects(comp0, comp1, true), expect,
- `${c0} intersects ${c1} loose, objects`)
- t.equal(intersects(comp1, comp0, true), expect,
- `${c1} intersects ${c0} loose, objects`)
- t.equal(intersects(c0, c1), expect, `${c0} intersects ${c1}`)
- t.equal(intersects(c1, c0), expect, `${c1} intersects ${c0}`)
- t.equal(intersects(c0, c1, true), expect,
- `${c0} intersects ${c1} loose`)
- t.equal(intersects(c1, c0, true), expect,
- `${c1} intersects ${c0} loose`)
- t.end()
- }))
+ t.equal(intersects(comp0, comp1, opts), expect, `${c0} intersects ${c1} objects`)
+ t.equal(intersects(comp1, comp0, opts), expect, `${c1} intersects ${c0} objects`)
+ t.equal(intersects(c0, c1, opts), expect, `${c0} intersects ${c1}`)
+ t.equal(intersects(c1, c0, opts), expect, `${c1} intersects ${c0}`)
+
+ opts.loose = true
+ t.equal(intersects(comp0, comp1, opts), expect, `${c0} intersects ${c1} loose, objects`)
+ t.equal(intersects(comp1, comp0, opts), expect, `${c1} intersects ${c0} loose, objects`)
+ t.equal(intersects(c0, c1, opts), expect, `${c0} intersects ${c1} loose`)
+ t.equal(intersects(c1, c0, opts), expect, `${c1} intersects ${c0} loose`)
+ t.end()
+ }))
})
test('ranges intersect', (t) => {