包是否符合标准

源码共读

每周一起学源码

第七期 | validate-npm-package-name 检测 npm 包是否符合标准

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
'use strict'

// 这个正则用于检验包名 是否是 scoped package name
var scopedPackagePattern = new RegExp('^(?:@([^/]+?)[/])?([^/]+?)$')
// 一个第三方库 包含了一些特殊的关键字 字符串数组 例如http fs https等
var builtins = require('builtins')
// 跟上面这个库类似的功能
var blacklist = [
'node_modules',
'favicon.ico'
]

// 导出一个函数名
// name 传入检测的包名
var validate = module.exports = function (name) {
// 存放警告情况的数组
var warnings = []
// 存放错误情况的数组
var errors = []

// 以下是一系列用于判断包名是否合规的判断
// 前几个判断没通过直接就return出去了
// 不能是null
if (name === null) {
errors.push('name cannot be null')
return done(warnings, errors)
}

// 不能是undefined
if (name === undefined) {
errors.push('name cannot be undefined')
return done(warnings, errors)
}

// 必须是字符串
if (typeof name !== 'string') {
errors.push('name must be a string')
return done(warnings, errors)
}

// 长度不能为0
if (!name.length) {
errors.push('name length must be greater than zero')
}

// 不能以.开头
if (name.match(/^\./)) {
errors.push('name cannot start with a period')
}

// 不能以_开头
if (name.match(/^_/)) {
errors.push('name cannot start with an underscore')
}

// 必须是a-z 0-9 @以内的字符开头 后来新加的判断 这么说的话 大写字母也不能开头 特殊字符也不能开头了
if (name.length && !name.match(/^[a-z0-9@]/)) {
warnings.push('name can only start with lowercase character and digit')
}

// 不能有首尾空格
if (name.trim() !== name) {
errors.push('name cannot contain leading or trailing spaces')
}

// 不能是黑名单里面的字符串
// No funny business
blacklist.forEach(function (blacklistedName) {
if (name.toLowerCase() === blacklistedName) {
errors.push(blacklistedName + ' is a blacklisted name')
}
})

// Generate warnings for stuff that used to be allowed

// 不能是关键字字符串 多数是nodejs的关键字
// core module names like http, events, util, etc
builtins.forEach(function (builtin) {
if (name.toLowerCase() === builtin) {
warnings.push(builtin + ' is a core module name')
}
})

// 长度不能大于214个字符
// 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')
}

// 不能包含大写
// mIxeD CaSe nAMEs
if (name.toLowerCase() !== name) {
warnings.push('name can no longer contain capital letters')
}

// @user/package package不能以~'!()* 字符开头
if (/[~'!()*]/.test(name.split('/').slice(-1)[0])) {
warnings.push('name can no longer contain special characters ("~\'!()*")')
}

// 不能包含中文或其他语言文字
if (encodeURIComponent(name) !== name) {
// Maybe it's a scoped package name, like @user/package
var nameMatch = name.match(scopedPackagePattern)
// 如果是 @user/package形式 分别判断是否转码后相等
if (nameMatch) {
var user = nameMatch[1]
var pkg = nameMatch[2]
if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
return done(warnings, errors)
}
}

errors.push('name can only contain URL-friendly characters')
}
// 最后返回结果
return done(warnings, errors)
}

validate.scopedPackagePattern = scopedPackagePattern

// done方法 根据之前的判断情况 输出结果值
var done = function (warnings, errors) {
var result = {
validForNewPackages: errors.length === 0 && warnings.length === 0,
validForOldPackages: errors.length === 0,
warnings: warnings,
errors: errors
}
// 如果数组为空则删除这两个字段
if (!result.warnings.length) delete result.warnings
if (!result.errors.length) delete result.errors
return result
}

Author: XavierShi
Link: https://blog.xaviershi.com/2022/02/25/包是否符合标准/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.