source: frontend/node_modules/webpack/lib/sharing/utils.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 12 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 10.2 KB
RevLine 
[9af201e]1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { dirname, join, readJson } = require("../util/fs");
9
10/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
11/** @typedef {import("../util/fs").JsonObject} JsonObject */
12/** @typedef {import("../util/fs").JsonPrimitive} JsonPrimitive */
13
14// Extreme shorthand only for github. eg: foo/bar
15const RE_URL_GITHUB_EXTREME_SHORT = /^[^/@:.\s][^/@:\s]*\/[^@:\s]*[^/@:\s]#\S+/;
16
17// Short url with specific protocol. eg: github:foo/bar
18const RE_GIT_URL_SHORT = /^(?:github|gitlab|bitbucket|gist):\/?[^/.]+\/?/i;
19
20// Currently supported protocols
21const RE_PROTOCOL =
22 /^(?:(?:git\+)?(?:ssh|https?|file)|git|github|gitlab|bitbucket|gist):$/i;
23
24// Has custom protocol
25const RE_CUSTOM_PROTOCOL = /^(?:(?:git\+)?(?:ssh|https?|file)|git):\/\//i;
26
27// Valid hash format for npm / yarn ...
28const RE_URL_HASH_VERSION = /#(?:semver:)?(.+)/;
29
30// Simple hostname validate
31const RE_HOSTNAME = /^(?:[^/.]+(?:\.[^/]+)+|localhost)$/;
32
33// For hostname with colon. eg: ssh://user@github.com:foo/bar
34const RE_HOSTNAME_WITH_COLON =
35 /([^/@#:.]+(?:\.[^/@#:.]+)+|localhost):([^#/0-9]+)/;
36
37// Reg for url without protocol
38const RE_NO_PROTOCOL = /^[^/@#:.]+(?:\.[^/@#:.]+)+/;
39
40// RegExp for version string
41const VERSION_PATTERN_REGEXP = /^(?:[\d^=v<>~]|[*xX]$)/;
42
43// Specific protocol for short url without normal hostname
44const PROTOCOLS_FOR_SHORT = [
45 "github:",
46 "gitlab:",
47 "bitbucket:",
48 "gist:",
49 "file:"
50];
51
52// Default protocol for git url
53const DEF_GIT_PROTOCOL = "git+ssh://";
54
55// thanks to https://github.com/npm/hosted-git-info/blob/latest/git-host-info.js
56const extractCommithashByDomain = {
57 /**
58 * Returns hash.
59 * @param {string} pathname pathname
60 * @param {string} hash hash
61 * @returns {string | undefined} hash
62 */
63 "github.com": (pathname, hash) => {
64 let [, user, project, type, commithash] = pathname.split("/", 5);
65 if (type && type !== "tree") {
66 return;
67 }
68
69 commithash = !type ? hash : `#${commithash}`;
70
71 if (project && project.endsWith(".git")) {
72 project = project.slice(0, -4);
73 }
74
75 if (!user || !project) {
76 return;
77 }
78
79 return commithash;
80 },
81 /**
82 * Returns hash.
83 * @param {string} pathname pathname
84 * @param {string} hash hash
85 * @returns {string | undefined} hash
86 */
87 "gitlab.com": (pathname, hash) => {
88 const path = pathname.slice(1);
89 if (path.includes("/-/") || path.includes("/archive.tar.gz")) {
90 return;
91 }
92
93 const segments = path.split("/");
94 let project = /** @type {string} */ (segments.pop());
95 if (project.endsWith(".git")) {
96 project = project.slice(0, -4);
97 }
98
99 const user = segments.join("/");
100 if (!user || !project) {
101 return;
102 }
103
104 return hash;
105 },
106 /**
107 * Returns hash.
108 * @param {string} pathname pathname
109 * @param {string} hash hash
110 * @returns {string | undefined} hash
111 */
112 "bitbucket.org": (pathname, hash) => {
113 let [, user, project, aux] = pathname.split("/", 4);
114 if (["get"].includes(aux)) {
115 return;
116 }
117
118 if (project && project.endsWith(".git")) {
119 project = project.slice(0, -4);
120 }
121
122 if (!user || !project) {
123 return;
124 }
125
126 return hash;
127 },
128 /**
129 * Returns hash.
130 * @param {string} pathname pathname
131 * @param {string} hash hash
132 * @returns {string | undefined} hash
133 */
134 "gist.github.com": (pathname, hash) => {
135 let [, user, project, aux] = pathname.split("/", 4);
136 if (aux === "raw") {
137 return;
138 }
139
140 if (!project) {
141 if (!user) {
142 return;
143 }
144
145 project = user;
146 }
147
148 if (project.endsWith(".git")) {
149 project = project.slice(0, -4);
150 }
151
152 return hash;
153 }
154};
155
156/**
157 * extract commit hash from parsed url
158 * @param {URL} urlParsed parsed url
159 * @returns {string} commithash
160 */
161function getCommithash(urlParsed) {
162 let { hostname, pathname, hash } = urlParsed;
163 hostname = hostname.replace(/^www\./, "");
164
165 try {
166 hash = decodeURIComponent(hash);
167 // eslint-disable-next-line no-empty
168 } catch (_err) {}
169
170 if (
171 extractCommithashByDomain[
172 /** @type {keyof extractCommithashByDomain} */ (hostname)
173 ]
174 ) {
175 return (
176 extractCommithashByDomain[
177 /** @type {keyof extractCommithashByDomain} */ (hostname)
178 ](pathname, hash) || ""
179 );
180 }
181
182 return hash;
183}
184
185/**
186 * make url right for URL parse
187 * @param {string} gitUrl git url
188 * @returns {string} fixed url
189 */
190function correctUrl(gitUrl) {
191 // like:
192 // proto://hostname.com:user/repo -> proto://hostname.com/user/repo
193 return gitUrl.replace(RE_HOSTNAME_WITH_COLON, "$1/$2");
194}
195
196/**
197 * make url protocol right for URL parse
198 * @param {string} gitUrl git url
199 * @returns {string} fixed url
200 */
201function correctProtocol(gitUrl) {
202 // eg: github:foo/bar#v1.0. Should not add double slash, in case of error parsed `pathname`
203 if (RE_GIT_URL_SHORT.test(gitUrl)) {
204 return gitUrl;
205 }
206
207 // eg: user@github.com:foo/bar
208 if (!RE_CUSTOM_PROTOCOL.test(gitUrl)) {
209 return `${DEF_GIT_PROTOCOL}${gitUrl}`;
210 }
211
212 return gitUrl;
213}
214
215/**
216 * extract git dep version from hash
217 * @param {string} hash hash
218 * @returns {string} git dep version
219 */
220function getVersionFromHash(hash) {
221 const matched = hash.match(RE_URL_HASH_VERSION);
222
223 return (matched && matched[1]) || "";
224}
225
226/**
227 * if string can be decoded
228 * @param {string} str str to be checked
229 * @returns {boolean} if can be decoded
230 */
231function canBeDecoded(str) {
232 try {
233 decodeURIComponent(str);
234 } catch (_err) {
235 return false;
236 }
237
238 return true;
239}
240
241/**
242 * get right dep version from git url
243 * @param {string} gitUrl git url
244 * @returns {string} dep version
245 */
246function getGitUrlVersion(gitUrl) {
247 const oriGitUrl = gitUrl;
248 // github extreme shorthand
249 gitUrl = RE_URL_GITHUB_EXTREME_SHORT.test(gitUrl)
250 ? `github:${gitUrl}`
251 : correctProtocol(gitUrl);
252
253 gitUrl = correctUrl(gitUrl);
254
255 /** @type {undefined | URL} */
256 let parsed;
257
258 try {
259 parsed = new URL(gitUrl);
260 // eslint-disable-next-line no-empty
261 } catch (_err) {}
262
263 if (!parsed) {
264 return "";
265 }
266
267 const { protocol, hostname, pathname, username, password } = parsed;
268 if (!RE_PROTOCOL.test(protocol)) {
269 return "";
270 }
271
272 // pathname shouldn't be empty or URL malformed
273 if (!pathname || !canBeDecoded(pathname)) {
274 return "";
275 }
276
277 // without protocol, there should have auth info
278 if (RE_NO_PROTOCOL.test(oriGitUrl) && !username && !password) {
279 return "";
280 }
281
282 if (!PROTOCOLS_FOR_SHORT.includes(protocol.toLowerCase())) {
283 if (!RE_HOSTNAME.test(hostname)) {
284 return "";
285 }
286
287 const commithash = getCommithash(parsed);
288 return getVersionFromHash(commithash) || commithash;
289 }
290
291 // for protocol short
292 return getVersionFromHash(gitUrl);
293}
294
295/** @typedef {{ data: JsonObject, path: string }} DescriptionFile */
296
297/**
298 * Gets description file.
299 * @param {InputFileSystem} fs file system
300 * @param {string} directory directory to start looking into
301 * @param {string[]} descriptionFiles possible description filenames
302 * @param {(err?: Error | null, descriptionFile?: DescriptionFile, paths?: string[]) => void} callback callback
303 * @param {(descriptionFile?: DescriptionFile) => boolean} satisfiesDescriptionFileData file data compliance check
304 * @param {Set<string>} checkedFilePaths set of file paths that have been checked
305 */
306const getDescriptionFile = (
307 fs,
308 directory,
309 descriptionFiles,
310 callback,
311 satisfiesDescriptionFileData,
312 checkedFilePaths = new Set()
313) => {
314 let i = 0;
315
316 const satisfiesDescriptionFileDataInternal = {
317 check: satisfiesDescriptionFileData,
318 checkedFilePaths
319 };
320
321 const tryLoadCurrent = () => {
322 if (i >= descriptionFiles.length) {
323 const parentDirectory = dirname(fs, directory);
324 if (!parentDirectory || parentDirectory === directory) {
325 return callback(null, undefined, [
326 ...satisfiesDescriptionFileDataInternal.checkedFilePaths
327 ]);
328 }
329 return getDescriptionFile(
330 fs,
331 parentDirectory,
332 descriptionFiles,
333 callback,
334 satisfiesDescriptionFileDataInternal.check,
335 satisfiesDescriptionFileDataInternal.checkedFilePaths
336 );
337 }
338 const filePath = join(fs, directory, descriptionFiles[i]);
339 readJson(fs, filePath, (err, data) => {
340 if (err) {
341 if ("code" in err && err.code === "ENOENT") {
342 i++;
343 return tryLoadCurrent();
344 }
345 return callback(err);
346 }
347 if (!data || typeof data !== "object" || Array.isArray(data)) {
348 return callback(
349 new Error(`Description file ${filePath} is not an object`)
350 );
351 }
352 if (
353 typeof satisfiesDescriptionFileDataInternal.check === "function" &&
354 !satisfiesDescriptionFileDataInternal.check({ data, path: filePath })
355 ) {
356 i++;
357 satisfiesDescriptionFileDataInternal.checkedFilePaths.add(filePath);
358 return tryLoadCurrent();
359 }
360 callback(null, { data, path: filePath });
361 });
362 };
363 tryLoadCurrent();
364};
365
366module.exports.getDescriptionFile = getDescriptionFile;
367
368/**
369 * Gets required version from description file.
370 * @param {JsonObject} data description file data i.e.: package.json
371 * @param {string} packageName name of the dependency
372 * @returns {string | undefined} normalized version
373 */
374const getRequiredVersionFromDescriptionFile = (data, packageName) => {
375 const dependencyTypes = [
376 "optionalDependencies",
377 "dependencies",
378 "peerDependencies",
379 "devDependencies"
380 ];
381
382 for (const dependencyType of dependencyTypes) {
383 const dependency = /** @type {JsonObject} */ (data[dependencyType]);
384 if (
385 dependency &&
386 typeof dependency === "object" &&
387 packageName in dependency
388 ) {
389 return normalizeVersion(
390 /** @type {Exclude<JsonPrimitive, null | boolean | number>} */ (
391 dependency[packageName]
392 )
393 );
394 }
395 }
396};
397
398module.exports.getRequiredVersionFromDescriptionFile =
399 getRequiredVersionFromDescriptionFile;
400
401/**
402 * Checks whether this object is required version.
403 * @param {string} str maybe required version
404 * @returns {boolean} true, if it looks like a version
405 */
406function isRequiredVersion(str) {
407 return VERSION_PATTERN_REGEXP.test(str);
408}
409
410module.exports.isRequiredVersion = isRequiredVersion;
411
412/**
413 * Normalizes version.
414 * @see https://docs.npmjs.com/cli/v7/configuring-npm/package-json#urls-as-dependencies
415 * @param {string} versionDesc version to be normalized
416 * @returns {string} normalized version
417 */
418function normalizeVersion(versionDesc) {
419 versionDesc = (versionDesc && versionDesc.trim()) || "";
420
421 if (isRequiredVersion(versionDesc)) {
422 return versionDesc;
423 }
424
425 // add handle for URL Dependencies
426 return getGitUrlVersion(versionDesc.toLowerCase());
427}
428
429module.exports.normalizeVersion = normalizeVersion;
Note: See TracBrowser for help on using the repository browser.