Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING CHANGE: Invalidate special chars #13

Merged
merged 4 commits into from
Feb 13, 2017
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 27 additions & 24 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,79 @@
var scopedPackagePattern = new RegExp("^(?:@([^/]+?)[/])?([^/]+?)$");
var builtins = require("builtins")
var blacklist = [
"node_modules",
"favicon.ico"
];
'use strict'

var validate = module.exports = function(name) {
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
var builtins = require('builtins')
var blacklist = [
'node_modules',
'favicon.ico'
]

var validate = module.exports = function (name) {
var warnings = []
var errors = []

if (name === null) {
errors.push("name cannot be null")
errors.push('name cannot be null')
return done(warnings, errors)
}

if (name === undefined) {
errors.push("name cannot be undefined")
errors.push('name cannot be undefined')
return done(warnings, errors)
}

if (typeof name !== "string") {
errors.push("name must be a string")
if (typeof name !== 'string') {
errors.push('name must be a string')
return done(warnings, errors)
}

if (!name.length) {
errors.push("name length must be greater than zero")
errors.push('name length must be greater than zero')
}

if (name.match(/^\./)) {
errors.push("name cannot start with a period")
errors.push('name cannot start with a period')
}

if (name.match(/^_/)) {
errors.push("name cannot start with an underscore")
errors.push('name cannot start with an underscore')
}

if (name.trim() !== name) {
errors.push("name cannot contain leading or trailing spaces")
errors.push('name cannot contain leading or trailing spaces')
}

// No funny business
blacklist.forEach(function(blacklistedName){
blacklist.forEach(function (blacklistedName) {
if (name.toLowerCase() === blacklistedName) {
errors.push(blacklistedName + " is a blacklisted name")
errors.push(blacklistedName + ' is a blacklisted name')
}
})

// Generate warnings for stuff that used to be allowed

// core module names like http, events, util, etc
builtins.forEach(function(builtin){
builtins.forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + " is a core module name")
warnings.push(builtin + ' is a core module 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")
warnings.push('name can no longer contain more than 214 characters')
}

// mIxeD CaSe nAMEs
if (name.toLowerCase() !== name) {
warnings.push("name can no longer contain capital letters")
warnings.push('name can no longer contain capital letters')
}

if (encodeURIComponent(name) !== name) {
if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
warnings.push('name can no longer contain special characters ("~\'!()*")')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious about the choice of using warn rather than error, I imagine:

  1. on the registry we'll block new publications that cause this warning.
  2. the CLI will log the message but allow the publish.
  3. old packages will continue to be able to be published?

This seems reasonable to me, just refreshing myself with this module.

}

if (encodeURIComponent(name) !== name) {
// Maybe it's a scoped package name, like @user/package
var nameMatch = name.match(scopedPackagePattern)
if (nameMatch) {
Expand All @@ -80,11 +84,10 @@ var validate = module.exports = function(name) {
}
}

errors.push("name can only contain URL-friendly characters")
errors.push('name can only contain URL-friendly characters')
}

return done(warnings, errors)

}

validate.scopedPackagePattern = scopedPackagePattern
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
"test": "test"
},
"dependencies": {
"builtins": "0.0.7"
"builtins": "^1.0.3"
},
"devDependencies": {
"tap": "^0.4.13"
"standard": "^8.6.0",
"tap": "^10.0.0"
},
"scripts": {
"test": "tap test/*.js"
"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"
},
"repository": {
"type": "git",
Expand Down
87 changes: 47 additions & 40 deletions test/index.js
Original file line number Diff line number Diff line change
@@ -1,102 +1,109 @@
var validate = require("..")
var test = require("tap").test
var path = require("path")
var fs = require("fs")
'use strict'

test("validate-npm-package-name", function (t) {
var validate = require('..')
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!"), {validForNewPackages: true, validForOldPackages: true})
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!'), {
validForNewPackages: false,
validForOldPackages: true,
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"), {validForNewPackages: true, validForOldPackages: true})
t.deepEqual(validate('@npm/thingy'), {validForNewPackages: true, validForOldPackages: true})
t.deepEqual(validate('@npm-zors/money!time.js'), {
validForNewPackages: false,
validForOldPackages: true,
warnings: ['name can no longer contain special characters ("~\'!()*")']
})

// Invalid

t.deepEqual(validate(""), {
t.deepEqual(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.deepEqual(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.deepEqual(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.deepEqual(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.deepEqual(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.deepEqual(validate(' leading-space'), {
validForNewPackages: false,
validForOldPackages: false,
errors: ["name cannot contain leading or trailing spaces", "name can only contain URL-friendly characters"]})
errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters']})

t.deepEqual(validate("trailing-space "), {
t.deepEqual(validate('trailing-space '), {
validForNewPackages: false,
validForOldPackages: false,
errors: ["name cannot contain leading or trailing spaces", "name can only contain URL-friendly characters"]})
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.deepEqual(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.deepEqual(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.deepEqual(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.deepEqual(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-"), {
t.deepEqual(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"), {
t.deepEqual(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou'), {
validForNewPackages: true,
validForOldPackages: true
})

// Legacy Mixed-Case

t.deepEqual(validate("CAPITAL-LETTERS"), {
t.deepEqual(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()
})