All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.lib.sharing.utils.js Maven / Gradle / Ivy

Go to download

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

The newest version!
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const { join, dirname, readJson } = require("../util/fs");

/** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
/** @typedef {import("../util/fs").JsonObject} JsonObject */
/** @typedef {import("../util/fs").JsonPrimitive} JsonPrimitive */

// Extreme shorthand only for github. eg: foo/bar
const RE_URL_GITHUB_EXTREME_SHORT = /^[^/@:.\s][^/@:\s]*\/[^@:\s]*[^/@:\s]#\S+/;

// Short url with specific protocol. eg: github:foo/bar
const RE_GIT_URL_SHORT = /^(github|gitlab|bitbucket|gist):\/?[^/.]+\/?/i;

// Currently supported protocols
const RE_PROTOCOL =
	/^((git\+)?(ssh|https?|file)|git|github|gitlab|bitbucket|gist):$/i;

// Has custom protocol
const RE_CUSTOM_PROTOCOL = /^((git\+)?(ssh|https?|file)|git):\/\//i;

// Valid hash format for npm / yarn ...
const RE_URL_HASH_VERSION = /#(?:semver:)?(.+)/;

// Simple hostname validate
const RE_HOSTNAME = /^(?:[^/.]+(\.[^/]+)+|localhost)$/;

// For hostname with colon. eg: ssh://[email protected]:foo/bar
const RE_HOSTNAME_WITH_COLON =
	/([^/@#:.]+(?:\.[^/@#:.]+)+|localhost):([^#/0-9]+)/;

// Reg for url without protocol
const RE_NO_PROTOCOL = /^([^/@#:.]+(?:\.[^/@#:.]+)+)/;

// RegExp for version string
const VERSION_PATTERN_REGEXP = /^([\d^=v<>~]|[*xX]$)/;

// Specific protocol for short url without normal hostname
const PROTOCOLS_FOR_SHORT = [
	"github:",
	"gitlab:",
	"bitbucket:",
	"gist:",
	"file:"
];

// Default protocol for git url
const DEF_GIT_PROTOCOL = "git+ssh://";

// thanks to https://github.com/npm/hosted-git-info/blob/latest/git-host-info.js
const extractCommithashByDomain = {
	/**
	 * @param {string} pathname pathname
	 * @param {string} hash hash
	 * @returns {string | undefined} hash
	 */
	"github.com": (pathname, hash) => {
		let [, user, project, type, commithash] = pathname.split("/", 5);
		if (type && type !== "tree") {
			return;
		}

		if (!type) {
			commithash = hash;
		} else {
			commithash = "#" + commithash;
		}

		if (project && project.endsWith(".git")) {
			project = project.slice(0, -4);
		}

		if (!user || !project) {
			return;
		}

		return commithash;
	},
	/**
	 * @param {string} pathname pathname
	 * @param {string} hash hash
	 * @returns {string | undefined} hash
	 */
	"gitlab.com": (pathname, hash) => {
		const path = pathname.slice(1);
		if (path.includes("/-/") || path.includes("/archive.tar.gz")) {
			return;
		}

		const segments = path.split("/");
		let project = /** @type {string} */ (segments.pop());
		if (project.endsWith(".git")) {
			project = project.slice(0, -4);
		}

		const user = segments.join("/");
		if (!user || !project) {
			return;
		}

		return hash;
	},
	/**
	 * @param {string} pathname pathname
	 * @param {string} hash hash
	 * @returns {string | undefined} hash
	 */
	"bitbucket.org": (pathname, hash) => {
		let [, user, project, aux] = pathname.split("/", 4);
		if (["get"].includes(aux)) {
			return;
		}

		if (project && project.endsWith(".git")) {
			project = project.slice(0, -4);
		}

		if (!user || !project) {
			return;
		}

		return hash;
	},
	/**
	 * @param {string} pathname pathname
	 * @param {string} hash hash
	 * @returns {string | undefined} hash
	 */
	"gist.github.com": (pathname, hash) => {
		let [, user, project, aux] = pathname.split("/", 4);
		if (aux === "raw") {
			return;
		}

		if (!project) {
			if (!user) {
				return;
			}

			project = user;
		}

		if (project.endsWith(".git")) {
			project = project.slice(0, -4);
		}

		return hash;
	}
};

/**
 * extract commit hash from parsed url
 *
 * @inner
 * @param {URL} urlParsed parsed url
 * @returns {string} commithash
 */
function getCommithash(urlParsed) {
	let { hostname, pathname, hash } = urlParsed;
	hostname = hostname.replace(/^www\./, "");

	try {
		hash = decodeURIComponent(hash);
		// eslint-disable-next-line no-empty
	} catch (e) {}

	if (
		extractCommithashByDomain[
			/** @type {keyof extractCommithashByDomain} */ (hostname)
		]
	) {
		return (
			extractCommithashByDomain[
				/** @type {keyof extractCommithashByDomain} */ (hostname)
			](pathname, hash) || ""
		);
	}

	return hash;
}

/**
 * make url right for URL parse
 *
 * @inner
 * @param {string} gitUrl git url
 * @returns {string} fixed url
 */
function correctUrl(gitUrl) {
	// like:
	// proto://hostname.com:user/repo -> proto://hostname.com/user/repo
	return gitUrl.replace(RE_HOSTNAME_WITH_COLON, "$1/$2");
}

/**
 * make url protocol right for URL parse
 *
 * @inner
 * @param {string} gitUrl git url
 * @returns {string} fixed url
 */
function correctProtocol(gitUrl) {
	// eg: github:foo/bar#v1.0. Should not add double slash, in case of error parsed `pathname`
	if (RE_GIT_URL_SHORT.test(gitUrl)) {
		return gitUrl;
	}

	// eg: [email protected]:foo/bar
	if (!RE_CUSTOM_PROTOCOL.test(gitUrl)) {
		return `${DEF_GIT_PROTOCOL}${gitUrl}`;
	}

	return gitUrl;
}

/**
 * extract git dep version from hash
 *
 * @inner
 * @param {string} hash hash
 * @returns {string} git dep version
 */
function getVersionFromHash(hash) {
	const matched = hash.match(RE_URL_HASH_VERSION);

	return (matched && matched[1]) || "";
}

/**
 * if string can be decoded
 *
 * @inner
 * @param {string} str str to be checked
 * @returns {boolean} if can be decoded
 */
function canBeDecoded(str) {
	try {
		decodeURIComponent(str);
	} catch (e) {
		return false;
	}

	return true;
}

/**
 * get right dep version from git url
 *
 * @inner
 * @param {string} gitUrl git url
 * @returns {string} dep version
 */
function getGitUrlVersion(gitUrl) {
	let oriGitUrl = gitUrl;
	// github extreme shorthand
	if (RE_URL_GITHUB_EXTREME_SHORT.test(gitUrl)) {
		gitUrl = "github:" + gitUrl;
	} else {
		gitUrl = correctProtocol(gitUrl);
	}

	gitUrl = correctUrl(gitUrl);

	let parsed;
	try {
		parsed = new URL(gitUrl);
		// eslint-disable-next-line no-empty
	} catch (e) {}

	if (!parsed) {
		return "";
	}

	const { protocol, hostname, pathname, username, password } = parsed;
	if (!RE_PROTOCOL.test(protocol)) {
		return "";
	}

	// pathname shouldn't be empty or URL malformed
	if (!pathname || !canBeDecoded(pathname)) {
		return "";
	}

	// without protocol, there should have auth info
	if (RE_NO_PROTOCOL.test(oriGitUrl) && !username && !password) {
		return "";
	}

	if (!PROTOCOLS_FOR_SHORT.includes(protocol.toLowerCase())) {
		if (!RE_HOSTNAME.test(hostname)) {
			return "";
		}

		const commithash = getCommithash(parsed);
		return getVersionFromHash(commithash) || commithash;
	}

	// for protocol short
	return getVersionFromHash(gitUrl);
}

/**
 * @param {string} str maybe required version
 * @returns {boolean} true, if it looks like a version
 */
function isRequiredVersion(str) {
	return VERSION_PATTERN_REGEXP.test(str);
}

exports.isRequiredVersion = isRequiredVersion;

/**
 * @see https://docs.npmjs.com/cli/v7/configuring-npm/package-json#urls-as-dependencies
 * @param {string} versionDesc version to be normalized
 * @returns {string} normalized version
 */
function normalizeVersion(versionDesc) {
	versionDesc = (versionDesc && versionDesc.trim()) || "";

	if (isRequiredVersion(versionDesc)) {
		return versionDesc;
	}

	// add handle for URL Dependencies
	return getGitUrlVersion(versionDesc.toLowerCase());
}

exports.normalizeVersion = normalizeVersion;

/**
 *
 * @param {InputFileSystem} fs file system
 * @param {string} directory directory to start looking into
 * @param {string[]} descriptionFiles possible description filenames
 * @param {function((Error | null)=, {data: object, path: string}=): void} callback callback
 */
const getDescriptionFile = (fs, directory, descriptionFiles, callback) => {
	let i = 0;
	const tryLoadCurrent = () => {
		if (i >= descriptionFiles.length) {
			const parentDirectory = dirname(fs, directory);
			if (!parentDirectory || parentDirectory === directory) return callback();
			return getDescriptionFile(
				fs,
				parentDirectory,
				descriptionFiles,
				callback
			);
		}
		const filePath = join(fs, directory, descriptionFiles[i]);
		readJson(fs, filePath, (err, data) => {
			if (err) {
				if ("code" in err && err.code === "ENOENT") {
					i++;
					return tryLoadCurrent();
				}
				return callback(err);
			}
			if (!data || typeof data !== "object" || Array.isArray(data)) {
				return callback(
					new Error(`Description file ${filePath} is not an object`)
				);
			}
			callback(null, { data, path: filePath });
		});
	};
	tryLoadCurrent();
};
exports.getDescriptionFile = getDescriptionFile;

/**
 *
 * @param {JsonObject} data description file data i.e.: package.json
 * @param {string} packageName name of the dependency
 * @returns {string | undefined} normalized version
 */
const getRequiredVersionFromDescriptionFile = (data, packageName) => {
	const dependencyTypes = [
		"optionalDependencies",
		"dependencies",
		"peerDependencies",
		"devDependencies"
	];

	for (const dependencyType of dependencyTypes) {
		const dependency = /** @type {JsonObject} */ (data[dependencyType]);
		if (
			dependency &&
			typeof dependency === "object" &&
			packageName in dependency
		) {
			return normalizeVersion(
				/** @type {Exclude} */ (
					dependency[packageName]
				)
			);
		}
	}
};
exports.getRequiredVersionFromDescriptionFile =
	getRequiredVersionFromDescriptionFile;




© 2015 - 2024 Weber Informatics LLC | Privacy Policy