diff --git a/.commitlintrc.js b/.commitlintrc.js new file mode 100644 index 0000000..5b0b1a5 --- /dev/null +++ b/.commitlintrc.js @@ -0,0 +1,10 @@ +/* This file is automatically added by @npmcli/template-oss. Do not edit. */ + +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']], + 'header-max-length': [2, 'always', 80], + 'subject-case': [0, 'always', ['lower-case', 'sentence-case', 'start-case']], + }, +} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..0e8ad00 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,15 @@ +/* This file is automatically added by @npmcli/template-oss. Do not edit. */ + +const { readdirSync: readdir } = require('fs') + +const localConfigs = readdir(__dirname) + .filter((file) => file.startsWith('.eslintrc.local.')) + .map((file) => `./${file}`) + +module.exports = { + root: true, + extends: [ + '@npmcli', + ...localConfigs, + ], +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..2c54b0d --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +* @npm/cli-team diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..d043192 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,54 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Bug +description: File a bug/issue +title: "[BUG] " +labels: [ Bug, Needs Triage ] + +body: + - type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please [search here](./issues) to see if an issue already exists for your problem. + options: + - label: I have searched the existing issues + required: true + - type: textarea + attributes: + label: Current Behavior + description: A clear & concise description of what you're experiencing. + validations: + required: false + - type: textarea + attributes: + label: Expected Behavior + description: A clear & concise description of what you expected to happen. + validations: + required: false + - type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + value: | + 1. In this environment... + 2. With this config... + 3. Run '...' + 4. See error... + validations: + required: false + - type: textarea + attributes: + label: Environment + description: | + examples: + - **npm**: 7.6.3 + - **Node**: 13.14.0 + - **OS**: Ubuntu 20.04 + - **platform**: Macbook Pro + value: | + - npm: + - Node: + - OS: + - platform: + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d640909 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,3 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +blank_issues_enabled: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..96d8eaf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +version: 2 + +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: daily + allow: + - dependency-type: direct + versioning-strategy: increase-if-necessary + commit-message: + prefix: deps + prefix-development: chore + labels: + - "Dependencies" diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..549243a --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,27 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Audit + +on: + workflow_dispatch: + schedule: + # "At 01:00 on Monday" https://crontab.guru/#0_1_*_*_1 + - cron: "0 1 * * 1" + +jobs: + audit: + runs-on: ubuntu-latest + steps: + - 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 + 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 --package-lock + - run: npm audit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..8067c86 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: CI + +on: + workflow_dispatch: + pull_request: + branches: + - '*' + push: + branches: + - main + - latest + schedule: + # "At 02:00 on Monday" https://crontab.guru/#0_2_*_*_1 + - cron: "0 2 * * 1" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - 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 + 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 + - run: npm run lint + + test: + strategy: + fail-fast: false + matrix: + node-version: + - 12.13.0 + - 12.x + - 14.15.0 + - 14.x + - 16.0.0 + - 16.x + platform: + - os: ubuntu-latest + shell: bash + - os: macos-latest + shell: bash + - os: windows-latest + shell: cmd + runs-on: ${{ matrix.platform.os }} + defaults: + run: + shell: ${{ matrix.platform.shell }} + steps: + - 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 + with: + node-version: ${{ matrix.node-version }} + - name: Update to workable npm (windows) + # 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: Update npm to 7 + # If we do test on npm 10 it needs npm7 + if: startsWith(matrix.node-version, '10.') + run: npm i --prefer-online --no-fund --no-audit -g npm@7 + - name: Update npm to latest + 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 + - run: npm test --ignore-scripts diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..56cd7b9 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,44 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: "CodeQL" + +on: + push: + branches: + - main + - latest + pull_request: + # The branches below must be a subset of the branches above + branches: + - main + - latest + schedule: + # "At 03:00 on Monday" https://crontab.guru/#0_3_*_*_1 + - cron: "0 3 * * 1" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ javascript ] + + steps: + - 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" + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.github/workflows/post-dependabot.yml b/.github/workflows/post-dependabot.yml new file mode 100644 index 0000000..bae1d8d --- /dev/null +++ b/.github/workflows/post-dependabot.yml @@ -0,0 +1,43 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Post Dependabot Actions + +on: pull_request + +# https://docs.github.com/en/rest/overview/permissions-required-for-github-apps +permissions: + actions: write + contents: write + +jobs: + Install: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - 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 + 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 + id: metadata + uses: dependabot/fetch-metadata@v1.1.1 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: npm install and commit + if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss') + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh pr checkout ${{ github.event.pull_request.number }} + npm install --ignore-scripts + npm run template-oss-apply + git add . + git commit -am "chore: postinstall for dependabot template-oss PR" + git push diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..93a5c3c --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,38 @@ +# This file is automatically added by @npmcli/template-oss. Do not edit. + +name: Pull Request Linting + +on: + pull_request: + types: + - opened + - reopened + - edited + - synchronize + +jobs: + check: + name: Check PR Title or Commits + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - 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 + 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 + 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 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..ab3a910 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,26 @@ +# 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/.gitignore b/.gitignore index 3c3629e..bf011b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,25 @@ -node_modules +# This file is automatically added by @npmcli/template-oss. Do not edit. + +# ignore everything in the root +/* + +# keep these +!/.eslintrc.local.* +!**/.gitignore +!/docs/ +!/tap-snapshots/ +!/test/ +!/map.js +!/scripts/ +!/README* +!/LICENSE* +!/CHANGELOG* +!/.commitlintrc.js +!/.eslintrc.js +!/.github/ +!/.gitignore +!/.npmrc +!/SECURITY.md +!/bin/ +!/lib/ +!/package.json diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..529f93e --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +; This file is automatically added by @npmcli/template-oss. Do not edit. + +package-lock=false diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 55fb573..0000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -sudo: false -language: node_js -node_js: - - "10" - - "8" - - "6" - - "11" diff --git a/CHANGELOG.md b/CHANGELOG.md index bad07c1..82f1f2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -# 3.0.0 +# Changelog + +## 3.0.0 ## Breaking Changes diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a93106d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +<!-- This file is automatically added by @npmcli/template-oss. Do not edit. --> + +Please send vulnerability reports through [hackerone](https://hackerone.com/github). diff --git a/index.js b/lib/index.js similarity index 88% rename from index.js rename to lib/index.js index eb43fa2..7cea134 100644 --- a/index.js +++ b/lib/index.js @@ -4,7 +4,7 @@ var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$') var builtins = require('builtins') var blacklist = [ 'node_modules', - 'favicon.ico' + 'favicon.ico', ] var validate = module.exports = function (name) { @@ -58,8 +58,6 @@ var validate = module.exports = function (name) { } }) - // really-long-package-names-------------------------------such--length-----many---wow - // the thisisareallyreallylongpackagenameitshouldpublishdowenowhavealimittothelengthofpackagenames-poch. if (name.length > 214) { warnings.push('name can no longer contain more than 214 characters') } @@ -97,9 +95,13 @@ var done = function (warnings, errors) { validForNewPackages: errors.length === 0 && warnings.length === 0, validForOldPackages: errors.length === 0, warnings: warnings, - errors: errors + errors: errors, + } + if (!result.warnings.length) { + delete result.warnings + } + if (!result.errors.length) { + delete result.errors } - if (!result.warnings.length) delete result.warnings - if (!result.errors.length) delete result.errors return result } diff --git a/package.json b/package.json index b72e9ef..6e69b63 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "validate-npm-package-name", "version": "3.0.0", "description": "Give me a string and I'll tell you if it's a valid npm package name", - "main": "index.js", + "main": "lib/", "directories": { "test": "test" }, @@ -10,18 +10,28 @@ "builtins": "^1.0.3" }, "devDependencies": { - "standard": "^8.6.0", - "tap": "^10.0.0" + "@npmcli/eslint-config": "^3.0.1", + "@npmcli/template-oss": "3.2.1", + "tap": "^16.0.1" }, "scripts": { "cov:test": "TAP_FLAGS='--cov' npm run test:code", "test:code": "tap ${TAP_FLAGS:-'--'} test/*.js", "test:style": "standard", - "test": "npm run test:code && npm run test:style" + "test": "tap", + "lint": "eslint \"**/*.js\"", + "postlint": "template-oss-check", + "template-oss-apply": "template-oss-apply --force", + "lintfix": "npm run lint -- --fix", + "preversion": "npm test", + "postversion": "npm publish", + "prepublishOnly": "git push origin --follow-tags", + "snap": "tap", + "posttest": "npm run lint" }, "repository": { "type": "git", - "url": "https://github.com/npm/validate-npm-package-name" + "url": "https://github.com/npm/validate-npm-package-name.git" }, "keywords": [ "npm", @@ -29,10 +39,26 @@ "names", "validation" ], - "author": "zeke", + "author": "GitHub Inc.", "license": "ISC", "bugs": { "url": "https://github.com/npm/validate-npm-package-name/issues" }, - "homepage": "https://github.com/npm/validate-npm-package-name" + "homepage": "https://github.com/npm/validate-npm-package-name", + "files": [ + "bin/", + "lib/" + ], + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "templateOSS": { + "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", + "version": "3.2.1" + }, + "tap": { + "statements": 88, + "branches": 92, + "lines": 88 + } } diff --git a/test/index.js b/test/index.js index 1f0bc61..1485755 100644 --- a/test/index.js +++ b/test/index.js @@ -6,104 +6,108 @@ var test = require('tap').test test('validate-npm-package-name', function (t) { // Traditional - t.deepEqual(validate('some-package'), {validForNewPackages: true, validForOldPackages: true}) - t.deepEqual(validate('example.com'), {validForNewPackages: true, validForOldPackages: true}) - t.deepEqual(validate('under_score'), {validForNewPackages: true, validForOldPackages: true}) - t.deepEqual(validate('period.js'), {validForNewPackages: true, validForOldPackages: true}) - t.deepEqual(validate('123numeric'), {validForNewPackages: true, validForOldPackages: true}) - t.deepEqual(validate('crazy!'), { + t.same(validate('some-package'), { validForNewPackages: true, validForOldPackages: true }) + t.same(validate('example.com'), { validForNewPackages: true, validForOldPackages: true }) + t.same(validate('under_score'), { validForNewPackages: true, validForOldPackages: true }) + t.same(validate('period.js'), { validForNewPackages: true, validForOldPackages: true }) + t.same(validate('123numeric'), { validForNewPackages: true, validForOldPackages: true }) + t.same(validate('crazy!'), { validForNewPackages: false, validForOldPackages: true, - warnings: ['name can no longer contain special characters ("~\'!()*")'] + warnings: ['name can no longer contain special characters ("~\'!()*")'], }) // Scoped (npm 2+) - t.deepEqual(validate('@npm/thingy'), {validForNewPackages: true, validForOldPackages: true}) - t.deepEqual(validate('@npm-zors/money!time.js'), { + t.same(validate('@npm/thingy'), { validForNewPackages: true, validForOldPackages: true }) + t.same(validate('@npm-zors/money!time.js'), { validForNewPackages: false, validForOldPackages: true, - warnings: ['name can no longer contain special characters ("~\'!()*")'] + warnings: ['name can no longer contain special characters ("~\'!()*")'], }) // Invalid - t.deepEqual(validate(''), { + t.same(validate(''), { validForNewPackages: false, validForOldPackages: false, - errors: ['name length must be greater than zero']}) + errors: ['name length must be greater than zero'] }) - t.deepEqual(validate(''), { + t.same(validate(''), { validForNewPackages: false, validForOldPackages: false, - errors: ['name length must be greater than zero']}) + errors: ['name length must be greater than zero'] }) - t.deepEqual(validate('.start-with-period'), { + t.same(validate('.start-with-period'), { validForNewPackages: false, validForOldPackages: false, - errors: ['name cannot start with a period']}) + errors: ['name cannot start with a period'] }) - t.deepEqual(validate('_start-with-underscore'), { + t.same(validate('_start-with-underscore'), { validForNewPackages: false, validForOldPackages: false, - errors: ['name cannot start with an underscore']}) + errors: ['name cannot start with an underscore'] }) - t.deepEqual(validate('contain:colons'), { + t.same(validate('contain:colons'), { validForNewPackages: false, validForOldPackages: false, - errors: ['name can only contain URL-friendly characters']}) + errors: ['name can only contain URL-friendly characters'] }) - t.deepEqual(validate(' leading-space'), { + t.same(validate(' leading-space'), { validForNewPackages: false, validForOldPackages: false, - errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters']}) + /* eslint-disable-next-line max-len */ + errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters'] }) - t.deepEqual(validate('trailing-space '), { + t.same(validate('trailing-space '), { validForNewPackages: false, validForOldPackages: false, - errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters']}) + /* eslint-disable-next-line max-len */ + errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters'] }) - t.deepEqual(validate('s/l/a/s/h/e/s'), { + t.same(validate('s/l/a/s/h/e/s'), { validForNewPackages: false, validForOldPackages: false, - errors: ['name can only contain URL-friendly characters']}) + errors: ['name can only contain URL-friendly characters'] }) - t.deepEqual(validate('node_modules'), { + t.same(validate('node_modules'), { validForNewPackages: false, validForOldPackages: false, - errors: ['node_modules is a blacklisted name']}) + errors: ['node_modules is a blacklisted name'] }) - t.deepEqual(validate('favicon.ico'), { + t.same(validate('favicon.ico'), { validForNewPackages: false, validForOldPackages: false, - errors: ['favicon.ico is a blacklisted name']}) + errors: ['favicon.ico is a blacklisted name'] }) // Node/IO Core - t.deepEqual(validate('http'), { + t.same(validate('http'), { validForNewPackages: false, validForOldPackages: true, - warnings: ['http is a core module name']}) + warnings: ['http is a core module name'] }) // Long Package Names - t.deepEqual(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou-'), { + /* eslint-disable-next-line max-len */ + t.same(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou-'), { validForNewPackages: false, validForOldPackages: true, - warnings: ['name can no longer contain more than 214 characters'] + warnings: ['name can no longer contain more than 214 characters'], }) - t.deepEqual(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou'), { + /* eslint-disable-next-line max-len */ + t.same(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou'), { validForNewPackages: true, - validForOldPackages: true + validForOldPackages: true, }) // Legacy Mixed-Case - t.deepEqual(validate('CAPITAL-LETTERS'), { + t.same(validate('CAPITAL-LETTERS'), { validForNewPackages: false, validForOldPackages: true, - warnings: ['name can no longer contain capital letters']}) + warnings: ['name can no longer contain capital letters'] }) t.end() })