Skip to content

Commit f847db3

Browse files
Merge pull request #13 from npm/invalidate-special-chars
BREAKING CHANGE: Invalidate special chars
2 parents 11cc3d2 + 55f1162 commit f847db3

4 files changed

Lines changed: 87 additions & 67 deletions

File tree

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
sudo: false
2+
language: node_js
3+
node_js:
4+
- '0.10'
5+
- '4'
6+
- '6'

index.js

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,79 @@
1-
var scopedPackagePattern = new RegExp("^(?:@([^/]+?)[/])?([^/]+?)$");
2-
var builtins = require("builtins")
3-
var blacklist = [
4-
"node_modules",
5-
"favicon.ico"
6-
];
1+
'use strict'
72

8-
var validate = module.exports = function(name) {
3+
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
4+
var builtins = require('builtins')
5+
var blacklist = [
6+
'node_modules',
7+
'favicon.ico'
8+
]
99

10+
var validate = module.exports = function (name) {
1011
var warnings = []
1112
var errors = []
1213

1314
if (name === null) {
14-
errors.push("name cannot be null")
15+
errors.push('name cannot be null')
1516
return done(warnings, errors)
1617
}
1718

1819
if (name === undefined) {
19-
errors.push("name cannot be undefined")
20+
errors.push('name cannot be undefined')
2021
return done(warnings, errors)
2122
}
2223

23-
if (typeof name !== "string") {
24-
errors.push("name must be a string")
24+
if (typeof name !== 'string') {
25+
errors.push('name must be a string')
2526
return done(warnings, errors)
2627
}
2728

2829
if (!name.length) {
29-
errors.push("name length must be greater than zero")
30+
errors.push('name length must be greater than zero')
3031
}
3132

3233
if (name.match(/^\./)) {
33-
errors.push("name cannot start with a period")
34+
errors.push('name cannot start with a period')
3435
}
3536

3637
if (name.match(/^_/)) {
37-
errors.push("name cannot start with an underscore")
38+
errors.push('name cannot start with an underscore')
3839
}
3940

4041
if (name.trim() !== name) {
41-
errors.push("name cannot contain leading or trailing spaces")
42+
errors.push('name cannot contain leading or trailing spaces')
4243
}
4344

4445
// No funny business
45-
blacklist.forEach(function(blacklistedName){
46+
blacklist.forEach(function (blacklistedName) {
4647
if (name.toLowerCase() === blacklistedName) {
47-
errors.push(blacklistedName + " is a blacklisted name")
48+
errors.push(blacklistedName + ' is a blacklisted name')
4849
}
4950
})
5051

5152
// Generate warnings for stuff that used to be allowed
5253

5354
// core module names like http, events, util, etc
54-
builtins.forEach(function(builtin){
55+
builtins.forEach(function (builtin) {
5556
if (name.toLowerCase() === builtin) {
56-
warnings.push(builtin + " is a core module name")
57+
warnings.push(builtin + ' is a core module name')
5758
}
5859
})
5960

6061
// really-long-package-names-------------------------------such--length-----many---wow
6162
// the thisisareallyreallylongpackagenameitshouldpublishdowenowhavealimittothelengthofpackagenames-poch.
6263
if (name.length > 214) {
63-
warnings.push("name can no longer contain more than 214 characters")
64+
warnings.push('name can no longer contain more than 214 characters')
6465
}
6566

6667
// mIxeD CaSe nAMEs
6768
if (name.toLowerCase() !== name) {
68-
warnings.push("name can no longer contain capital letters")
69+
warnings.push('name can no longer contain capital letters')
6970
}
7071

71-
if (encodeURIComponent(name) !== name) {
72+
if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
73+
warnings.push('name can no longer contain special characters ("~\'!()*")')
74+
}
7275

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

83-
errors.push("name can only contain URL-friendly characters")
87+
errors.push('name can only contain URL-friendly characters')
8488
}
8589

8690
return done(warnings, errors)
87-
8891
}
8992

9093
validate.scopedPackagePattern = scopedPackagePattern

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
"test": "test"
88
},
99
"dependencies": {
10-
"builtins": "0.0.7"
10+
"builtins": "^1.0.3"
1111
},
1212
"devDependencies": {
13-
"tap": "^0.4.13"
13+
"standard": "^8.6.0",
14+
"tap": "^10.0.0"
1415
},
1516
"scripts": {
16-
"test": "tap test/*.js"
17+
"cov:test": "TAP_FLAGS='--cov' npm run test:code",
18+
"test:code": "tap ${TAP_FLAGS:-'--'} test/*.js",
19+
"test:style": "standard",
20+
"test": "npm run test:code && npm run test:style"
1721
},
1822
"repository": {
1923
"type": "git",

test/index.js

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,109 @@
1-
var validate = require("..")
2-
var test = require("tap").test
3-
var path = require("path")
4-
var fs = require("fs")
1+
'use strict'
52

6-
test("validate-npm-package-name", function (t) {
3+
var validate = require('..')
4+
var test = require('tap').test
75

6+
test('validate-npm-package-name', function (t) {
87
// Traditional
98

10-
t.deepEqual(validate("some-package"), {validForNewPackages: true, validForOldPackages: true})
11-
t.deepEqual(validate("example.com"), {validForNewPackages: true, validForOldPackages: true})
12-
t.deepEqual(validate("under_score"), {validForNewPackages: true, validForOldPackages: true})
13-
t.deepEqual(validate("period.js"), {validForNewPackages: true, validForOldPackages: true})
14-
t.deepEqual(validate("123numeric"), {validForNewPackages: true, validForOldPackages: true})
15-
t.deepEqual(validate("crazy!"), {validForNewPackages: true, validForOldPackages: true})
9+
t.deepEqual(validate('some-package'), {validForNewPackages: true, validForOldPackages: true})
10+
t.deepEqual(validate('example.com'), {validForNewPackages: true, validForOldPackages: true})
11+
t.deepEqual(validate('under_score'), {validForNewPackages: true, validForOldPackages: true})
12+
t.deepEqual(validate('period.js'), {validForNewPackages: true, validForOldPackages: true})
13+
t.deepEqual(validate('123numeric'), {validForNewPackages: true, validForOldPackages: true})
14+
t.deepEqual(validate('crazy!'), {
15+
validForNewPackages: false,
16+
validForOldPackages: true,
17+
warnings: ['name can no longer contain special characters ("~\'!()*")']
18+
})
1619

1720
// Scoped (npm 2+)
1821

19-
t.deepEqual(validate("@npm/thingy"), {validForNewPackages: true, validForOldPackages: true})
20-
t.deepEqual(validate("@npm-zors/money!time.js"), {validForNewPackages: true, validForOldPackages: true})
22+
t.deepEqual(validate('@npm/thingy'), {validForNewPackages: true, validForOldPackages: true})
23+
t.deepEqual(validate('@npm-zors/money!time.js'), {
24+
validForNewPackages: false,
25+
validForOldPackages: true,
26+
warnings: ['name can no longer contain special characters ("~\'!()*")']
27+
})
2128

2229
// Invalid
2330

24-
t.deepEqual(validate(""), {
31+
t.deepEqual(validate(''), {
2532
validForNewPackages: false,
2633
validForOldPackages: false,
27-
errors: ["name length must be greater than zero"]})
34+
errors: ['name length must be greater than zero']})
2835

29-
t.deepEqual(validate(""), {
36+
t.deepEqual(validate(''), {
3037
validForNewPackages: false,
3138
validForOldPackages: false,
32-
errors: ["name length must be greater than zero"]})
39+
errors: ['name length must be greater than zero']})
3340

34-
t.deepEqual(validate(".start-with-period"), {
41+
t.deepEqual(validate('.start-with-period'), {
3542
validForNewPackages: false,
3643
validForOldPackages: false,
37-
errors: ["name cannot start with a period"]})
44+
errors: ['name cannot start with a period']})
3845

39-
t.deepEqual(validate("_start-with-underscore"), {
46+
t.deepEqual(validate('_start-with-underscore'), {
4047
validForNewPackages: false,
4148
validForOldPackages: false,
42-
errors: ["name cannot start with an underscore"]})
49+
errors: ['name cannot start with an underscore']})
4350

44-
t.deepEqual(validate("contain:colons"), {
51+
t.deepEqual(validate('contain:colons'), {
4552
validForNewPackages: false,
4653
validForOldPackages: false,
47-
errors: ["name can only contain URL-friendly characters"]})
54+
errors: ['name can only contain URL-friendly characters']})
4855

49-
t.deepEqual(validate(" leading-space"), {
56+
t.deepEqual(validate(' leading-space'), {
5057
validForNewPackages: false,
5158
validForOldPackages: false,
52-
errors: ["name cannot contain leading or trailing spaces", "name can only contain URL-friendly characters"]})
59+
errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters']})
5360

54-
t.deepEqual(validate("trailing-space "), {
61+
t.deepEqual(validate('trailing-space '), {
5562
validForNewPackages: false,
5663
validForOldPackages: false,
57-
errors: ["name cannot contain leading or trailing spaces", "name can only contain URL-friendly characters"]})
64+
errors: ['name cannot contain leading or trailing spaces', 'name can only contain URL-friendly characters']})
5865

59-
t.deepEqual(validate("s/l/a/s/h/e/s"), {
66+
t.deepEqual(validate('s/l/a/s/h/e/s'), {
6067
validForNewPackages: false,
6168
validForOldPackages: false,
62-
errors: ["name can only contain URL-friendly characters"]})
69+
errors: ['name can only contain URL-friendly characters']})
6370

64-
t.deepEqual(validate("node_modules"), {
71+
t.deepEqual(validate('node_modules'), {
6572
validForNewPackages: false,
6673
validForOldPackages: false,
67-
errors: ["node_modules is a blacklisted name"]})
74+
errors: ['node_modules is a blacklisted name']})
6875

69-
t.deepEqual(validate("favicon.ico"), {
76+
t.deepEqual(validate('favicon.ico'), {
7077
validForNewPackages: false,
7178
validForOldPackages: false,
72-
errors: ["favicon.ico is a blacklisted name"]})
79+
errors: ['favicon.ico is a blacklisted name']})
7380

7481
// Node/IO Core
7582

76-
t.deepEqual(validate("http"), {
83+
t.deepEqual(validate('http'), {
7784
validForNewPackages: false,
7885
validForOldPackages: true,
79-
warnings: ["http is a core module name"]})
86+
warnings: ['http is a core module name']})
8087

8188
// Long Package Names
8289

83-
t.deepEqual(validate("ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou-"), {
90+
t.deepEqual(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou-'), {
8491
validForNewPackages: false,
8592
validForOldPackages: true,
86-
warnings: ["name can no longer contain more than 214 characters"]
93+
warnings: ['name can no longer contain more than 214 characters']
8794
})
8895

89-
t.deepEqual(validate("ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou"), {
96+
t.deepEqual(validate('ifyouwanttogetthesumoftwonumberswherethosetwonumbersarechosenbyfindingthelargestoftwooutofthreenumbersandsquaringthemwhichismultiplyingthembyitselfthenyoushouldinputthreenumbersintothisfunctionanditwilldothatforyou'), {
9097
validForNewPackages: true,
9198
validForOldPackages: true
9299
})
93100

94101
// Legacy Mixed-Case
95102

96-
t.deepEqual(validate("CAPITAL-LETTERS"), {
103+
t.deepEqual(validate('CAPITAL-LETTERS'), {
97104
validForNewPackages: false,
98105
validForOldPackages: true,
99-
warnings: ["name can no longer contain capital letters"]})
106+
warnings: ['name can no longer contain capital letters']})
100107

101108
t.end()
102109
})

0 commit comments

Comments
 (0)