@@ -9,13 +9,299 @@ const { join, dirname, readJson } = require("../util/fs");
99
1010/** @typedef {import("../util/fs").InputFileSystem } InputFileSystem */
1111
12+ // Extreme shorthand only for github. eg: foo/bar
13+ const RE_URL_GITHUB_EXTREME_SHORT = / ^ [ ^ / @ : . \s ] [ ^ / @ : \s ] * \/ [ ^ @ : \s ] * [ ^ / @ : \s ] # \S + / ;
14+
15+ // Short url with specific protocol. eg: github:foo/bar
16+ const RE_GIT_URL_SHORT = / ^ ( g i t h u b | g i t l a b | b i t b u c k e t | g i s t ) : \/ ? [ ^ / . ] + \/ ? / i;
17+
18+ // Currently supported protocols
19+ const RE_PROTOCOL =
20+ / ^ ( ( g i t \+ ) ? ( s s h | h t t p s ? | f i l e ) | g i t | g i t h u b | g i t l a b | b i t b u c k e t | g i s t ) : $ / i;
21+
22+ // Has custom protocol
23+ const RE_CUSTOM_PROTOCOL = / ^ ( ( g i t \+ ) ? ( s s h | h t t p s ? | f i l e ) | g i t ) : \/ \/ / i;
24+
25+ // Valid hash format for npm / yarn ...
26+ const RE_URL_HASH_VERSION = / # (?: s e m v e r : ) ? ( .+ ) / ;
27+
28+ // Simple hostname validate
29+ const RE_HOSTNAME = / ^ (?: [ ^ / . ] + ( \. [ ^ / ] + ) + | l o c a l h o s t ) $ / ;
30+
31+ // For hostname with colon. eg: ssh://user@github .com:foo/bar
32+ const RE_HOSTNAME_WITH_COLON =
33+ / ( [ ^ / @ # : . ] + (?: \. [ ^ / @ # : . ] + ) + | l o c a l h o s t ) : ( [ ^ # / 0 - 9 ] + ) / ;
34+
35+ // Reg for url without protocol
36+ const RE_NO_PROTOCOL = / ^ ( [ ^ / @ # : . ] + (?: \. [ ^ / @ # : . ] + ) + ) / ;
37+
38+ // RegExp for version string
39+ const VERSION_PATTERN_REGEXP = / ^ ( [ \d ^ = v < > ~ ] | [ * x X ] $ ) / ;
40+
41+ // Specific protocol for short url without normal hostname
42+ const PROTOCOLS_FOR_SHORT = [
43+ "github:" ,
44+ "gitlab:" ,
45+ "bitbucket:" ,
46+ "gist:" ,
47+ "file:"
48+ ] ;
49+
50+ // Default protocol for git url
51+ const DEF_GIT_PROTOCOL = "git+ssh://" ;
52+
53+ // thanks to https://github.com/npm/hosted-git-info/blob/latest/git-host-info.js
54+ const extractCommithashByDomain = {
55+ "github.com" : ( pathname , hash ) => {
56+ let [ , user , project , type , commithash ] = pathname . split ( "/" , 5 ) ;
57+ if ( type && type !== "tree" ) {
58+ return ;
59+ }
60+
61+ if ( ! type ) {
62+ commithash = hash ;
63+ } else {
64+ commithash = "#" + commithash ;
65+ }
66+
67+ if ( project && project . endsWith ( ".git" ) ) {
68+ project = project . slice ( 0 , - 4 ) ;
69+ }
70+
71+ if ( ! user || ! project ) {
72+ return ;
73+ }
74+
75+ return commithash ;
76+ } ,
77+ "gitlab.com" : ( pathname , hash ) => {
78+ const path = pathname . slice ( 1 ) ;
79+ if ( path . includes ( "/-/" ) || path . includes ( "/archive.tar.gz" ) ) {
80+ return ;
81+ }
82+
83+ const segments = path . split ( "/" ) ;
84+ let project = segments . pop ( ) ;
85+ if ( project . endsWith ( ".git" ) ) {
86+ project = project . slice ( 0 , - 4 ) ;
87+ }
88+
89+ const user = segments . join ( "/" ) ;
90+ if ( ! user || ! project ) {
91+ return ;
92+ }
93+
94+ return hash ;
95+ } ,
96+ "bitbucket.org" : ( pathname , hash ) => {
97+ let [ , user , project , aux ] = pathname . split ( "/" , 4 ) ;
98+ if ( [ "get" ] . includes ( aux ) ) {
99+ return ;
100+ }
101+
102+ if ( project && project . endsWith ( ".git" ) ) {
103+ project = project . slice ( 0 , - 4 ) ;
104+ }
105+
106+ if ( ! user || ! project ) {
107+ return ;
108+ }
109+
110+ return hash ;
111+ } ,
112+ "gist.github.com" : ( pathname , hash ) => {
113+ let [ , user , project , aux ] = pathname . split ( "/" , 4 ) ;
114+ if ( aux === "raw" ) {
115+ return ;
116+ }
117+
118+ if ( ! project ) {
119+ if ( ! user ) {
120+ return ;
121+ }
122+
123+ project = user ;
124+ user = null ;
125+ }
126+
127+ if ( project . endsWith ( ".git" ) ) {
128+ project = project . slice ( 0 , - 4 ) ;
129+ }
130+
131+ return hash ;
132+ }
133+ } ;
134+
135+ /**
136+ * extract commit hash from parsed url
137+ *
138+ * @inner
139+ * @param {Object } urlParsed parsed url
140+ * @returns {string } commithash
141+ */
142+ function getCommithash ( urlParsed ) {
143+ let { hostname, pathname, hash } = urlParsed ;
144+ hostname = hostname . replace ( / ^ w w w \. / , "" ) ;
145+
146+ try {
147+ hash = decodeURIComponent ( hash ) ;
148+ // eslint-disable-next-line no-empty
149+ } catch ( e ) { }
150+
151+ if ( extractCommithashByDomain [ hostname ] ) {
152+ return extractCommithashByDomain [ hostname ] ( pathname , hash ) || "" ;
153+ }
154+
155+ return hash ;
156+ }
157+
158+ /**
159+ * make url right for URL parse
160+ *
161+ * @inner
162+ * @param {string } gitUrl git url
163+ * @returns {string } fixed url
164+ */
165+ function correctUrl ( gitUrl ) {
166+ // like:
167+ // proto://hostname.com:user/repo -> proto://hostname.com/user/repo
168+ return gitUrl . replace ( RE_HOSTNAME_WITH_COLON , "$1/$2" ) ;
169+ }
170+
171+ /**
172+ * make url protocol right for URL parse
173+ *
174+ * @inner
175+ * @param {string } gitUrl git url
176+ * @returns {string } fixed url
177+ */
178+ function correctProtocol ( gitUrl ) {
179+ // eg: github:foo/bar#v1.0. Should not add double slash, in case of error parsed `pathname`
180+ if ( RE_GIT_URL_SHORT . test ( gitUrl ) ) {
181+ return gitUrl ;
182+ }
183+
184+ // eg: user@github .com:foo/bar
185+ if ( ! RE_CUSTOM_PROTOCOL . test ( gitUrl ) ) {
186+ return `${ DEF_GIT_PROTOCOL } ${ gitUrl } ` ;
187+ }
188+
189+ return gitUrl ;
190+ }
191+
192+ /**
193+ * extract git dep version from hash
194+ *
195+ * @inner
196+ * @param {string } hash hash
197+ * @returns {string } git dep version
198+ */
199+ function getVersionFromHash ( hash ) {
200+ const matched = hash . match ( RE_URL_HASH_VERSION ) ;
201+
202+ return ( matched && matched [ 1 ] ) || "" ;
203+ }
204+
205+ /**
206+ * if string can be decoded
207+ *
208+ * @inner
209+ * @param {string } str str to be checked
210+ * @returns {boolean } if can be decoded
211+ */
212+ function canBeDecoded ( str ) {
213+ try {
214+ decodeURIComponent ( str ) ;
215+ } catch ( e ) {
216+ return false ;
217+ }
218+
219+ return true ;
220+ }
221+
222+ /**
223+ * get right dep version from git url
224+ *
225+ * @inner
226+ * @param {string } gitUrl git url
227+ * @returns {string } dep version
228+ */
229+ function getGitUrlVersion ( gitUrl ) {
230+ let oriGitUrl = gitUrl ;
231+ // github extreme shorthand
232+ if ( RE_URL_GITHUB_EXTREME_SHORT . test ( gitUrl ) ) {
233+ gitUrl = "github:" + gitUrl ;
234+ } else {
235+ gitUrl = correctProtocol ( gitUrl ) ;
236+ }
237+
238+ gitUrl = correctUrl ( gitUrl ) ;
239+
240+ let parsed ;
241+ try {
242+ parsed = new URL ( gitUrl ) ;
243+ // eslint-disable-next-line no-empty
244+ } catch ( e ) { }
245+
246+ if ( ! parsed ) {
247+ return "" ;
248+ }
249+
250+ const { protocol, hostname, pathname, username, password } = parsed ;
251+ if ( ! RE_PROTOCOL . test ( protocol ) ) {
252+ return "" ;
253+ }
254+
255+ // pathname shouldn't be empty or URL malformed
256+ if ( ! pathname || ! canBeDecoded ( pathname ) ) {
257+ return "" ;
258+ }
259+
260+ // without protocol, there should have auth info
261+ if ( RE_NO_PROTOCOL . test ( oriGitUrl ) && ! username && ! password ) {
262+ return "" ;
263+ }
264+
265+ if ( ! PROTOCOLS_FOR_SHORT . includes ( protocol . toLowerCase ( ) ) ) {
266+ if ( ! RE_HOSTNAME . test ( hostname ) ) {
267+ return "" ;
268+ }
269+
270+ const commithash = getCommithash ( parsed ) ;
271+ return getVersionFromHash ( commithash ) || commithash ;
272+ }
273+
274+ // for protocol short
275+ return getVersionFromHash ( gitUrl ) ;
276+ }
277+
12278/**
13279 * @param {string } str maybe required version
14280 * @returns {boolean } true, if it looks like a version
15281 */
16- exports . isRequiredVersion = str => {
17- return / ^ ( [ \d ^ = v < > ~ ] | [ * x X ] $ ) / . test ( str ) ;
18- } ;
282+ function isRequiredVersion ( str ) {
283+ return VERSION_PATTERN_REGEXP . test ( str ) ;
284+ }
285+
286+ exports . isRequiredVersion = isRequiredVersion ;
287+
288+ /**
289+ * @see https://docs.npmjs.com/cli/v7/configuring-npm/package-json#urls-as-dependencies
290+ * @param {string } versionDesc version to be normalized
291+ * @returns {string } normalized version
292+ */
293+ function normalizeVersion ( versionDesc ) {
294+ versionDesc = ( versionDesc && versionDesc . trim ( ) ) || "" ;
295+
296+ if ( isRequiredVersion ( versionDesc ) ) {
297+ return versionDesc ;
298+ }
299+
300+ // add handle for URL Dependencies
301+ return getGitUrlVersion ( versionDesc . toLowerCase ( ) ) ;
302+ }
303+
304+ exports . normalizeVersion = normalizeVersion ;
19305
20306/**
21307 *
@@ -64,27 +350,27 @@ exports.getRequiredVersionFromDescriptionFile = (data, packageName) => {
64350 typeof data . optionalDependencies === "object" &&
65351 packageName in data . optionalDependencies
66352 ) {
67- return data . optionalDependencies [ packageName ] ;
353+ return normalizeVersion ( data . optionalDependencies [ packageName ] ) ;
68354 }
69355 if (
70356 data . dependencies &&
71357 typeof data . dependencies === "object" &&
72358 packageName in data . dependencies
73359 ) {
74- return data . dependencies [ packageName ] ;
360+ return normalizeVersion ( data . dependencies [ packageName ] ) ;
75361 }
76362 if (
77363 data . peerDependencies &&
78364 typeof data . peerDependencies === "object" &&
79365 packageName in data . peerDependencies
80366 ) {
81- return data . peerDependencies [ packageName ] ;
367+ return normalizeVersion ( data . peerDependencies [ packageName ] ) ;
82368 }
83369 if (
84370 data . devDependencies &&
85371 typeof data . devDependencies === "object" &&
86372 packageName in data . devDependencies
87373 ) {
88- return data . devDependencies [ packageName ] ;
374+ return normalizeVersion ( data . devDependencies [ packageName ] ) ;
89375 }
90376} ;
0 commit comments