| 
									
										
										
										
											2018-07-30 23:08:51 +08:00
										 |  |  | /* | 
					
						
							|  |  |  | 	MIT License http://www.opensource.org/licenses/mit-license.php
 | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-05 21:38:15 +08:00
										 |  |  | "use strict"; | 
					
						
							| 
									
										
										
										
											2018-07-30 23:08:51 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-04-05 21:38:15 +08:00
										 |  |  | const path = require("path"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-11 22:25:03 +08:00
										 |  |  | const WINDOWS_ABS_PATH_REGEXP = /^[a-zA-Z]:[\\/]/; | 
					
						
							| 
									
										
										
										
											2020-03-13 00:51:26 +08:00
										 |  |  | const SEGMENTS_SPLIT_REGEXP = /([|!])/; | 
					
						
							| 
									
										
										
										
											2018-12-26 17:40:42 +08:00
										 |  |  | const WINDOWS_PATH_SEPARATOR_REGEXP = /\\/g; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-21 05:37:42 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @typedef {Object} MakeRelativePathsCache | 
					
						
							|  |  |  |  * @property {Map<string, Map<string, string>>=} relativePaths | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2018-04-15 05:31:11 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							| 
									
										
										
										
											2018-12-08 01:12:04 +08:00
										 |  |  |  * @param {string} context context for relative path | 
					
						
							|  |  |  |  * @param {string} maybeAbsolutePath path to make relative | 
					
						
							|  |  |  |  * @returns {string} relative path in request style | 
					
						
							| 
									
										
										
										
											2018-04-15 05:31:11 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2018-12-08 01:12:04 +08:00
										 |  |  | const absoluteToRequest = (context, maybeAbsolutePath) => { | 
					
						
							| 
									
										
										
										
											2018-12-23 20:22:07 +08:00
										 |  |  | 	if (maybeAbsolutePath[0] === "/") { | 
					
						
							|  |  |  | 		if ( | 
					
						
							|  |  |  | 			maybeAbsolutePath.length > 1 && | 
					
						
							|  |  |  | 			maybeAbsolutePath[maybeAbsolutePath.length - 1] === "/" | 
					
						
							|  |  |  | 		) { | 
					
						
							|  |  |  | 			// this 'path' is actually a regexp generated by dynamic requires.
 | 
					
						
							|  |  |  | 			// Don't treat it as an absolute path.
 | 
					
						
							|  |  |  | 			return maybeAbsolutePath; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-26 17:40:42 +08:00
										 |  |  | 		const querySplitPos = maybeAbsolutePath.indexOf("?"); | 
					
						
							|  |  |  | 		let resource = | 
					
						
							|  |  |  | 			querySplitPos === -1 | 
					
						
							|  |  |  | 				? maybeAbsolutePath | 
					
						
							|  |  |  | 				: maybeAbsolutePath.slice(0, querySplitPos); | 
					
						
							|  |  |  | 		resource = path.posix.relative(context, resource); | 
					
						
							|  |  |  | 		if (!resource.startsWith("../")) { | 
					
						
							|  |  |  | 			resource = "./" + resource; | 
					
						
							| 
									
										
										
										
											2018-12-23 20:22:07 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-12-26 17:40:42 +08:00
										 |  |  | 		return querySplitPos === -1 | 
					
						
							|  |  |  | 			? resource | 
					
						
							|  |  |  | 			: resource + maybeAbsolutePath.slice(querySplitPos); | 
					
						
							| 
									
										
										
										
											2018-12-08 01:12:04 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-12-23 20:22:07 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-26 17:40:42 +08:00
										 |  |  | 	if (WINDOWS_ABS_PATH_REGEXP.test(maybeAbsolutePath)) { | 
					
						
							|  |  |  | 		const querySplitPos = maybeAbsolutePath.indexOf("?"); | 
					
						
							|  |  |  | 		let resource = | 
					
						
							|  |  |  | 			querySplitPos === -1 | 
					
						
							|  |  |  | 				? maybeAbsolutePath | 
					
						
							|  |  |  | 				: maybeAbsolutePath.slice(0, querySplitPos); | 
					
						
							|  |  |  | 		resource = path.win32.relative(context, resource); | 
					
						
							|  |  |  | 		if (!WINDOWS_ABS_PATH_REGEXP.test(resource)) { | 
					
						
							|  |  |  | 			resource = resource.replace(WINDOWS_PATH_SEPARATOR_REGEXP, "/"); | 
					
						
							|  |  |  | 			if (!resource.startsWith("../")) { | 
					
						
							|  |  |  | 				resource = "./" + resource; | 
					
						
							| 
									
										
										
										
											2018-12-08 01:12:04 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-12-26 17:40:42 +08:00
										 |  |  | 		return querySplitPos === -1 | 
					
						
							|  |  |  | 			? resource | 
					
						
							|  |  |  | 			: resource + maybeAbsolutePath.slice(querySplitPos); | 
					
						
							| 
									
										
										
										
											2018-05-21 16:46:09 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-12-23 20:22:07 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// not an absolute path
 | 
					
						
							|  |  |  | 	return maybeAbsolutePath; | 
					
						
							| 
									
										
										
										
											2017-04-05 21:38:15 +08:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-11 22:25:03 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {string} context context for relative path | 
					
						
							|  |  |  |  * @param {string} relativePath path | 
					
						
							|  |  |  |  * @returns {string} absolute path | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const requestToAbsolute = (context, relativePath) => { | 
					
						
							|  |  |  | 	if (relativePath.startsWith("./") || relativePath.startsWith("../")) | 
					
						
							|  |  |  | 		return path.join(context, relativePath); | 
					
						
							|  |  |  | 	return relativePath; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-19 19:40:00 +08:00
										 |  |  | const makeCacheable = fn => { | 
					
						
							| 
									
										
										
										
											2020-01-15 06:14:47 +08:00
										 |  |  | 	/** @type {WeakMap<object, Map<string, Map<string, string>>>} */ | 
					
						
							| 
									
										
										
										
											2019-01-19 19:40:00 +08:00
										 |  |  | 	const cache = new WeakMap(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} context context used to create relative path | 
					
						
							|  |  |  | 	 * @param {string} identifier identifier used to create relative path | 
					
						
							|  |  |  | 	 * @param {Object=} associatedObjectForCache an object to which the cache will be attached | 
					
						
							|  |  |  | 	 * @returns {string} the returned relative path | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	const cachedFn = (context, identifier, associatedObjectForCache) => { | 
					
						
							|  |  |  | 		if (!associatedObjectForCache) return fn(context, identifier); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		let innerCache = cache.get(associatedObjectForCache); | 
					
						
							|  |  |  | 		if (innerCache === undefined) { | 
					
						
							|  |  |  | 			innerCache = new Map(); | 
					
						
							|  |  |  | 			cache.set(associatedObjectForCache, innerCache); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		let cachedResult; | 
					
						
							|  |  |  | 		let innerSubCache = innerCache.get(context); | 
					
						
							|  |  |  | 		if (innerSubCache === undefined) { | 
					
						
							|  |  |  | 			innerCache.set(context, (innerSubCache = new Map())); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			cachedResult = innerSubCache.get(identifier); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (cachedResult !== undefined) { | 
					
						
							|  |  |  | 			return cachedResult; | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			const result = fn(context, identifier); | 
					
						
							|  |  |  | 			innerSubCache.set(identifier, result); | 
					
						
							|  |  |  | 			return result; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-15 06:14:47 +08:00
										 |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {Object=} associatedObjectForCache an object to which the cache will be attached | 
					
						
							|  |  |  | 	 * @returns {function(string, string): string} cached function | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	cachedFn.bindCache = associatedObjectForCache => { | 
					
						
							|  |  |  | 		let innerCache; | 
					
						
							|  |  |  | 		if (associatedObjectForCache) { | 
					
						
							|  |  |  | 			innerCache = cache.get(associatedObjectForCache); | 
					
						
							|  |  |  | 			if (innerCache === undefined) { | 
					
						
							|  |  |  | 				innerCache = new Map(); | 
					
						
							|  |  |  | 				cache.set(associatedObjectForCache, innerCache); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			innerCache = new Map(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * @param {string} context context used to create relative path | 
					
						
							|  |  |  | 		 * @param {string} identifier identifier used to create relative path | 
					
						
							|  |  |  | 		 * @returns {string} the returned relative path | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		const boundFn = (context, identifier) => { | 
					
						
							|  |  |  | 			let cachedResult; | 
					
						
							|  |  |  | 			let innerSubCache = innerCache.get(context); | 
					
						
							|  |  |  | 			if (innerSubCache === undefined) { | 
					
						
							|  |  |  | 				innerCache.set(context, (innerSubCache = new Map())); | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				cachedResult = innerSubCache.get(identifier); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if (cachedResult !== undefined) { | 
					
						
							|  |  |  | 				return cachedResult; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				const result = fn(context, identifier); | 
					
						
							|  |  |  | 				innerSubCache.set(identifier, result); | 
					
						
							|  |  |  | 				return result; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return boundFn; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} context context used to create relative path | 
					
						
							|  |  |  | 	 * @param {Object=} associatedObjectForCache an object to which the cache will be attached | 
					
						
							|  |  |  | 	 * @returns {function(string): string} cached function | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	cachedFn.bindContextCache = (context, associatedObjectForCache) => { | 
					
						
							|  |  |  | 		let innerSubCache; | 
					
						
							|  |  |  | 		if (associatedObjectForCache) { | 
					
						
							|  |  |  | 			let innerCache = cache.get(associatedObjectForCache); | 
					
						
							|  |  |  | 			if (innerCache === undefined) { | 
					
						
							|  |  |  | 				innerCache = new Map(); | 
					
						
							|  |  |  | 				cache.set(associatedObjectForCache, innerCache); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			innerSubCache = innerCache.get(context); | 
					
						
							|  |  |  | 			if (innerSubCache === undefined) { | 
					
						
							|  |  |  | 				innerCache.set(context, (innerSubCache = new Map())); | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			innerSubCache = new Map(); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/** | 
					
						
							|  |  |  | 		 * @param {string} identifier identifier used to create relative path | 
					
						
							|  |  |  | 		 * @returns {string} the returned relative path | 
					
						
							|  |  |  | 		 */ | 
					
						
							|  |  |  | 		const boundFn = identifier => { | 
					
						
							|  |  |  | 			const cachedResult = innerSubCache.get(identifier); | 
					
						
							|  |  |  | 			if (cachedResult !== undefined) { | 
					
						
							|  |  |  | 				return cachedResult; | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				const result = fn(context, identifier); | 
					
						
							|  |  |  | 				innerSubCache.set(identifier, result); | 
					
						
							|  |  |  | 				return result; | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return boundFn; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-19 19:40:00 +08:00
										 |  |  | 	return cachedFn; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-04-15 05:31:11 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @param {string} context context for relative path | 
					
						
							|  |  |  |  * @param {string} identifier identifier for path | 
					
						
							| 
									
										
										
										
											2018-05-08 20:31:51 +08:00
										 |  |  |  * @returns {string} a converted relative path | 
					
						
							| 
									
										
										
										
											2018-04-15 05:31:11 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2017-07-18 04:30:29 +08:00
										 |  |  | const _makePathsRelative = (context, identifier) => { | 
					
						
							|  |  |  | 	return identifier | 
					
						
							| 
									
										
										
										
											2020-03-13 00:51:26 +08:00
										 |  |  | 		.split(SEGMENTS_SPLIT_REGEXP) | 
					
						
							| 
									
										
										
										
											2018-12-08 01:12:04 +08:00
										 |  |  | 		.map(str => absoluteToRequest(context, str)) | 
					
						
							| 
									
										
										
										
											2017-07-18 04:30:29 +08:00
										 |  |  | 		.join(""); | 
					
						
							| 
									
										
										
										
											2017-07-18 04:47:18 +08:00
										 |  |  | }; | 
					
						
							| 
									
										
										
										
											2017-07-12 07:19:37 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-01-19 19:40:00 +08:00
										 |  |  | exports.makePathsRelative = makeCacheable(_makePathsRelative); | 
					
						
							| 
									
										
										
										
											2018-07-04 15:59:22 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {string} context absolute context path | 
					
						
							|  |  |  |  * @param {string} request any request string may containing absolute paths, query string, etc. | 
					
						
							|  |  |  |  * @returns {string} a new request string avoiding absolute paths when possible | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2019-01-19 19:40:00 +08:00
										 |  |  | const _contextify = (context, request) => { | 
					
						
							| 
									
										
										
										
											2018-07-04 15:59:22 +08:00
										 |  |  | 	return request | 
					
						
							|  |  |  | 		.split("!") | 
					
						
							| 
									
										
										
										
											2018-12-08 01:12:04 +08:00
										 |  |  | 		.map(r => absoluteToRequest(context, r)) | 
					
						
							| 
									
										
										
										
											2018-07-04 15:59:22 +08:00
										 |  |  | 		.join("!"); | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2019-01-19 19:40:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-11 22:25:03 +08:00
										 |  |  | const contextify = makeCacheable(_contextify); | 
					
						
							|  |  |  | exports.contextify = contextify; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {string} context absolute context path | 
					
						
							|  |  |  |  * @param {string} request any request string | 
					
						
							|  |  |  |  * @returns {string} a new request string using absolute paths when possible | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | const _absolutify = (context, request) => { | 
					
						
							|  |  |  | 	return request | 
					
						
							|  |  |  | 		.split("!") | 
					
						
							|  |  |  | 		.map(r => requestToAbsolute(context, r)) | 
					
						
							|  |  |  | 		.join("!"); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const absolutify = makeCacheable(_absolutify); | 
					
						
							|  |  |  | exports.absolutify = absolutify; | 
					
						
							| 
									
										
										
										
											2020-07-03 20:45:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-30 15:51:16 +08:00
										 |  |  | const PATH_QUERY_FRAGMENT_REGEXP = /^((?:\0.|[^?#\0])*)(\?(?:\0.|[^#\0])*)?(#.*)?$/; | 
					
						
							| 
									
										
										
										
											2020-07-03 20:45:49 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-06 23:39:52 +08:00
										 |  |  | /** @typedef {{ resource: string, path: string, query: string, fragment: string }} ParsedResource */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-03 20:45:49 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @param {string} str the path with query and fragment | 
					
						
							| 
									
										
										
										
											2020-07-06 23:39:52 +08:00
										 |  |  |  * @returns {ParsedResource} parsed parts | 
					
						
							| 
									
										
										
										
											2020-07-03 20:45:49 +08:00
										 |  |  |  */ | 
					
						
							| 
									
										
										
										
											2020-07-06 23:39:52 +08:00
										 |  |  | const _parseResource = str => { | 
					
						
							| 
									
										
										
										
											2020-07-03 20:45:49 +08:00
										 |  |  | 	const match = PATH_QUERY_FRAGMENT_REGEXP.exec(str); | 
					
						
							|  |  |  | 	return { | 
					
						
							| 
									
										
										
										
											2020-07-06 23:39:52 +08:00
										 |  |  | 		resource: str, | 
					
						
							| 
									
										
										
										
											2020-09-30 15:51:16 +08:00
										 |  |  | 		path: match[1].replace(/\0(.)/g, "$1"), | 
					
						
							|  |  |  | 		query: match[2] ? match[2].replace(/\0(.)/g, "$1") : "", | 
					
						
							| 
									
										
										
										
											2020-07-03 20:45:49 +08:00
										 |  |  | 		fragment: match[3] || "" | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2020-07-06 23:39:52 +08:00
										 |  |  | exports.parseResource = (realFn => { | 
					
						
							|  |  |  | 	/** @type {WeakMap<object, Map<string, ParsedResource>>} */ | 
					
						
							|  |  |  | 	const cache = new WeakMap(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	const getCache = associatedObjectForCache => { | 
					
						
							|  |  |  | 		const entry = cache.get(associatedObjectForCache); | 
					
						
							|  |  |  | 		if (entry !== undefined) return entry; | 
					
						
							|  |  |  | 		/** @type {Map<string, ParsedResource>} */ | 
					
						
							|  |  |  | 		const map = new Map(); | 
					
						
							|  |  |  | 		cache.set(associatedObjectForCache, map); | 
					
						
							|  |  |  | 		return map; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/** | 
					
						
							|  |  |  | 	 * @param {string} str the path with query and fragment | 
					
						
							|  |  |  | 	 * @param {Object=} associatedObjectForCache an object to which the cache will be attached | 
					
						
							|  |  |  | 	 * @returns {ParsedResource} parsed parts | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	const fn = (str, associatedObjectForCache) => { | 
					
						
							|  |  |  | 		if (!associatedObjectForCache) return realFn(str); | 
					
						
							|  |  |  | 		const cache = getCache(associatedObjectForCache); | 
					
						
							|  |  |  | 		const entry = cache.get(str); | 
					
						
							|  |  |  | 		if (entry !== undefined) return entry; | 
					
						
							|  |  |  | 		const result = realFn(str); | 
					
						
							|  |  |  | 		cache.set(str, result); | 
					
						
							|  |  |  | 		return result; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	fn.bindCache = associatedObjectForCache => { | 
					
						
							|  |  |  | 		const cache = getCache(associatedObjectForCache); | 
					
						
							|  |  |  | 		return str => { | 
					
						
							|  |  |  | 			const entry = cache.get(str); | 
					
						
							|  |  |  | 			if (entry !== undefined) return entry; | 
					
						
							|  |  |  | 			const result = realFn(str); | 
					
						
							|  |  |  | 			cache.set(str, result); | 
					
						
							|  |  |  | 			return result; | 
					
						
							|  |  |  | 		}; | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return fn; | 
					
						
							|  |  |  | })(_parseResource); | 
					
						
							| 
									
										
										
										
											2020-08-04 04:55:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * @param {string} filename the filename which should be undone | 
					
						
							|  |  |  |  * @param {boolean} enforceRelative true returns ./ for empty paths | 
					
						
							|  |  |  |  * @returns {string} repeated ../ to leave the directory of the provided filename to be back on root dir | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | exports.getUndoPath = (filename, enforceRelative) => { | 
					
						
							|  |  |  | 	let depth = -1; | 
					
						
							|  |  |  | 	for (const part of filename.split(/[/\\]+/)) { | 
					
						
							|  |  |  | 		if (part !== ".") { | 
					
						
							|  |  |  | 			depth += part === ".." ? -1 : 1; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return depth > 0 ? "../".repeat(depth) : enforceRelative ? "./" : ""; | 
					
						
							|  |  |  | }; |