diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json index cce965db..6182b423 100644 --- a/.obsidian/community-plugins.json +++ b/.obsidian/community-plugins.json @@ -3,5 +3,6 @@ "obsidian-git", "calendar", "obsidian-mind-map", - "obsidian-dictionary-plugin" + "obsidian-dictionary-plugin", + "obsidian-image-auto-upload-plugin" ] \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-image-auto-upload-plugin/main.js b/.obsidian/plugins/obsidian-image-auto-upload-plugin/main.js new file mode 100644 index 00000000..bbd3609b --- /dev/null +++ b/.obsidian/plugins/obsidian-image-auto-upload-plugin/main.js @@ -0,0 +1,6843 @@ +'use strict'; + +var obsidian = require('obsidian'); +var require$$0 = require('fs'); +var process$2 = require('node:process'); +var require$$0$1 = require('path'); +var require$$0$2 = require('child_process'); +var require$$0$3 = require('os'); +var require$$0$4 = require('assert'); +var require$$2 = require('events'); +var require$$0$6 = require('buffer'); +var require$$0$5 = require('stream'); +var require$$2$1 = require('util'); +var node_os = require('node:os'); +require('electron'); +var node_buffer = require('node:buffer'); + +var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; + +function getDefaultExportFromCjs (x) { + return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; +} + +function assertPath(path) { + if (typeof path !== 'string') { + throw new TypeError('Path must be a string. Received ' + JSON.stringify(path)); + } +} + +// Resolves . and .. elements in a path with directory names +function normalizeStringPosix(path, allowAboveRoot) { + var res = ''; + var lastSegmentLength = 0; + var lastSlash = -1; + var dots = 0; + var code; + for (var i = 0; i <= path.length; ++i) { + if (i < path.length) + code = path.charCodeAt(i); + else if (code === 47 /*/*/) + break; + else + code = 47 /*/*/; + if (code === 47 /*/*/) { + if (lastSlash === i - 1 || dots === 1) ; else if (lastSlash !== i - 1 && dots === 2) { + if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 /*.*/ || res.charCodeAt(res.length - 2) !== 46 /*.*/) { + if (res.length > 2) { + var lastSlashIndex = res.lastIndexOf('/'); + if (lastSlashIndex !== res.length - 1) { + if (lastSlashIndex === -1) { + res = ''; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf('/'); + } + lastSlash = i; + dots = 0; + continue; + } + } else if (res.length === 2 || res.length === 1) { + res = ''; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) + res += '/..'; + else + res = '..'; + lastSegmentLength = 2; + } + } else { + if (res.length > 0) + res += '/' + path.slice(lastSlash + 1, i); + else + res = path.slice(lastSlash + 1, i); + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === 46 /*.*/ && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +function _format(sep, pathObject) { + var dir = pathObject.dir || pathObject.root; + var base = pathObject.base || (pathObject.name || '') + (pathObject.ext || ''); + if (!dir) { + return base; + } + if (dir === pathObject.root) { + return dir + base; + } + return dir + sep + base; +} + +var posix = { + // path.resolve([from ...], to) + resolve: function resolve() { + var resolvedPath = ''; + var resolvedAbsolute = false; + var cwd; + + for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + var path; + if (i >= 0) + path = arguments[i]; + else { + if (cwd === undefined) + cwd = process.cwd(); + path = cwd; + } + + assertPath(path); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charCodeAt(0) === 47 /*/*/; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute); + + if (resolvedAbsolute) { + if (resolvedPath.length > 0) + return '/' + resolvedPath; + else + return '/'; + } else if (resolvedPath.length > 0) { + return resolvedPath; + } else { + return '.'; + } + }, + + normalize: function normalize(path) { + assertPath(path); + + if (path.length === 0) return '.'; + + var isAbsolute = path.charCodeAt(0) === 47 /*/*/; + var trailingSeparator = path.charCodeAt(path.length - 1) === 47 /*/*/; + + // Normalize the path + path = normalizeStringPosix(path, !isAbsolute); + + if (path.length === 0 && !isAbsolute) path = '.'; + if (path.length > 0 && trailingSeparator) path += '/'; + + if (isAbsolute) return '/' + path; + return path; + }, + + isAbsolute: function isAbsolute(path) { + assertPath(path); + return path.length > 0 && path.charCodeAt(0) === 47 /*/*/; + }, + + join: function join() { + if (arguments.length === 0) + return '.'; + var joined; + for (var i = 0; i < arguments.length; ++i) { + var arg = arguments[i]; + assertPath(arg); + if (arg.length > 0) { + if (joined === undefined) + joined = arg; + else + joined += '/' + arg; + } + } + if (joined === undefined) + return '.'; + return posix.normalize(joined); + }, + + relative: function relative(from, to) { + assertPath(from); + assertPath(to); + + if (from === to) return ''; + + from = posix.resolve(from); + to = posix.resolve(to); + + if (from === to) return ''; + + // Trim any leading backslashes + var fromStart = 1; + for (; fromStart < from.length; ++fromStart) { + if (from.charCodeAt(fromStart) !== 47 /*/*/) + break; + } + var fromEnd = from.length; + var fromLen = fromEnd - fromStart; + + // Trim any leading backslashes + var toStart = 1; + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== 47 /*/*/) + break; + } + var toEnd = to.length; + var toLen = toEnd - toStart; + + // Compare paths to find the longest common path from root + var length = fromLen < toLen ? fromLen : toLen; + var lastCommonSep = -1; + var i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === 47 /*/*/) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } else if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === 47 /*/*/) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo'; to='/' + lastCommonSep = 0; + } + } + break; + } + var fromCode = from.charCodeAt(fromStart + i); + var toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) + break; + else if (fromCode === 47 /*/*/) + lastCommonSep = i; + } + + var out = ''; + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === 47 /*/*/) { + if (out.length === 0) + out += '..'; + else + out += '/..'; + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) + return out + to.slice(toStart + lastCommonSep); + else { + toStart += lastCommonSep; + if (to.charCodeAt(toStart) === 47 /*/*/) + ++toStart; + return to.slice(toStart); + } + }, + + _makeLong: function _makeLong(path) { + return path; + }, + + dirname: function dirname(path) { + assertPath(path); + if (path.length === 0) return '.'; + var code = path.charCodeAt(0); + var hasRoot = code === 47 /*/*/; + var end = -1; + var matchedSlash = true; + for (var i = path.length - 1; i >= 1; --i) { + code = path.charCodeAt(i); + if (code === 47 /*/*/) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) return hasRoot ? '/' : '.'; + if (hasRoot && end === 1) return '//'; + return path.slice(0, end); + }, + + basename: function basename(path, ext) { + if (ext !== undefined && typeof ext !== 'string') throw new TypeError('"ext" argument must be a string'); + assertPath(path); + + var start = 0; + var end = -1; + var matchedSlash = true; + var i; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) return ''; + var extIdx = ext.length - 1; + var firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + var code = path.charCodeAt(i); + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) end = firstNonSlashEnd;else if (end === -1) end = path.length; + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) return ''; + return path.slice(start, end); + } + }, + + extname: function extname(path) { + assertPath(path); + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + for (var i = path.length - 1; i >= 0; --i) { + var code = path.charCodeAt(i); + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) + startDot = i; + else if (preDotState !== 1) + preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { + return ''; + } + return path.slice(startDot, end); + }, + + format: function format(pathObject) { + if (pathObject === null || typeof pathObject !== 'object') { + throw new TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject); + } + return _format('/', pathObject); + }, + + parse: function parse(path) { + assertPath(path); + + var ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) return ret; + var code = path.charCodeAt(0); + var isAbsolute = code === 47 /*/*/; + var start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + var startDot = -1; + var startPart = 0; + var end = -1; + var matchedSlash = true; + var i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + var preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + code = path.charCodeAt(i); + if (code === 47 /*/*/) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === 46 /*.*/) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) startDot = i;else if (preDotState !== 1) preDotState = 1; + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) ret.base = ret.name = path.slice(1, end);else ret.base = ret.name = path.slice(startPart, end); + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } + + if (startPart > 0) ret.dir = path.slice(0, startPart - 1);else if (isAbsolute) ret.dir = '/'; + + return ret; + }, + + sep: '/', + delimiter: ':', + win32: null, + posix: null +}; + +posix.posix = posix; + +var pathBrowserify = posix; + +var execa$2 = {exports: {}}; + +var crossSpawn$1 = {exports: {}}; + +var windows; +var hasRequiredWindows; + +function requireWindows () { + if (hasRequiredWindows) return windows; + hasRequiredWindows = 1; + windows = isexe; + isexe.sync = sync; + + var fs = require$$0; + + function checkPathExt (path, options) { + var pathext = options.pathExt !== undefined ? + options.pathExt : process.env.PATHEXT; + + if (!pathext) { + return true + } + + pathext = pathext.split(';'); + if (pathext.indexOf('') !== -1) { + return true + } + for (var i = 0; i < pathext.length; i++) { + var p = pathext[i].toLowerCase(); + if (p && path.substr(-p.length).toLowerCase() === p) { + return true + } + } + return false + } + + function checkStat (stat, path, options) { + if (!stat.isSymbolicLink() && !stat.isFile()) { + return false + } + return checkPathExt(path, options) + } + + function isexe (path, options, cb) { + fs.stat(path, function (er, stat) { + cb(er, er ? false : checkStat(stat, path, options)); + }); + } + + function sync (path, options) { + return checkStat(fs.statSync(path), path, options) + } + return windows; +} + +var mode; +var hasRequiredMode; + +function requireMode () { + if (hasRequiredMode) return mode; + hasRequiredMode = 1; + mode = isexe; + isexe.sync = sync; + + var fs = require$$0; + + function isexe (path, options, cb) { + fs.stat(path, function (er, stat) { + cb(er, er ? false : checkStat(stat, options)); + }); + } + + function sync (path, options) { + return checkStat(fs.statSync(path), options) + } + + function checkStat (stat, options) { + return stat.isFile() && checkMode(stat, options) + } + + function checkMode (stat, options) { + var mod = stat.mode; + var uid = stat.uid; + var gid = stat.gid; + + var myUid = options.uid !== undefined ? + options.uid : process.getuid && process.getuid(); + var myGid = options.gid !== undefined ? + options.gid : process.getgid && process.getgid(); + + var u = parseInt('100', 8); + var g = parseInt('010', 8); + var o = parseInt('001', 8); + var ug = u | g; + + var ret = (mod & o) || + (mod & g) && gid === myGid || + (mod & u) && uid === myUid || + (mod & ug) && myUid === 0; + + return ret + } + return mode; +} + +var core$1; +if (process.platform === 'win32' || commonjsGlobal.TESTING_WINDOWS) { + core$1 = requireWindows(); +} else { + core$1 = requireMode(); +} + +var isexe_1 = isexe$1; +isexe$1.sync = sync; + +function isexe$1 (path, options, cb) { + if (typeof options === 'function') { + cb = options; + options = {}; + } + + if (!cb) { + if (typeof Promise !== 'function') { + throw new TypeError('callback not provided') + } + + return new Promise(function (resolve, reject) { + isexe$1(path, options || {}, function (er, is) { + if (er) { + reject(er); + } else { + resolve(is); + } + }); + }) + } + + core$1(path, options || {}, function (er, is) { + // ignore EACCES because that just means we aren't allowed to run it + if (er) { + if (er.code === 'EACCES' || options && options.ignoreErrors) { + er = null; + is = false; + } + } + cb(er, is); + }); +} + +function sync (path, options) { + // my kingdom for a filtered catch + try { + return core$1.sync(path, options || {}) + } catch (er) { + if (options && options.ignoreErrors || er.code === 'EACCES') { + return false + } else { + throw er + } + } +} + +const isWindows = process.platform === 'win32' || + process.env.OSTYPE === 'cygwin' || + process.env.OSTYPE === 'msys'; + +const path$3 = require$$0$1; +const COLON = isWindows ? ';' : ':'; +const isexe = isexe_1; + +const getNotFoundError = (cmd) => + Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }); + +const getPathInfo = (cmd, opt) => { + const colon = opt.colon || COLON; + + // If it has a slash, then we don't bother searching the pathenv. + // just check the file itself, and that's it. + const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [''] + : ( + [ + // windows always checks the cwd first + ...(isWindows ? [process.cwd()] : []), + ...(opt.path || process.env.PATH || + /* istanbul ignore next: very unusual */ '').split(colon), + ] + ); + const pathExtExe = isWindows + ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM' + : ''; + const pathExt = isWindows ? pathExtExe.split(colon) : ['']; + + if (isWindows) { + if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') + pathExt.unshift(''); + } + + return { + pathEnv, + pathExt, + pathExtExe, + } +}; + +const which$1 = (cmd, opt, cb) => { + if (typeof opt === 'function') { + cb = opt; + opt = {}; + } + if (!opt) + opt = {}; + + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt); + const found = []; + + const step = i => new Promise((resolve, reject) => { + if (i === pathEnv.length) + return opt.all && found.length ? resolve(found) + : reject(getNotFoundError(cmd)) + + const ppRaw = pathEnv[i]; + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; + + const pCmd = path$3.join(pathPart, cmd); + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd; + + resolve(subStep(p, i, 0)); + }); + + const subStep = (p, i, ii) => new Promise((resolve, reject) => { + if (ii === pathExt.length) + return resolve(step(i + 1)) + const ext = pathExt[ii]; + isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { + if (!er && is) { + if (opt.all) + found.push(p + ext); + else + return resolve(p + ext) + } + return resolve(subStep(p, i, ii + 1)) + }); + }); + + return cb ? step(0).then(res => cb(null, res), cb) : step(0) +}; + +const whichSync = (cmd, opt) => { + opt = opt || {}; + + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt); + const found = []; + + for (let i = 0; i < pathEnv.length; i ++) { + const ppRaw = pathEnv[i]; + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw; + + const pCmd = path$3.join(pathPart, cmd); + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd; + + for (let j = 0; j < pathExt.length; j ++) { + const cur = p + pathExt[j]; + try { + const is = isexe.sync(cur, { pathExt: pathExtExe }); + if (is) { + if (opt.all) + found.push(cur); + else + return cur + } + } catch (ex) {} + } + } + + if (opt.all && found.length) + return found + + if (opt.nothrow) + return null + + throw getNotFoundError(cmd) +}; + +var which_1 = which$1; +which$1.sync = whichSync; + +var pathKey$1 = {exports: {}}; + +const pathKey = (options = {}) => { + const environment = options.env || process.env; + const platform = options.platform || process.platform; + + if (platform !== 'win32') { + return 'PATH'; + } + + return Object.keys(environment).reverse().find(key => key.toUpperCase() === 'PATH') || 'Path'; +}; + +pathKey$1.exports = pathKey; +// TODO: Remove this for the next major release +pathKey$1.exports.default = pathKey; + +var pathKeyExports = pathKey$1.exports; + +const path$2 = require$$0$1; +const which = which_1; +const getPathKey = pathKeyExports; + +function resolveCommandAttempt(parsed, withoutPathExt) { + const env = parsed.options.env || process.env; + const cwd = process.cwd(); + const hasCustomCwd = parsed.options.cwd != null; + // Worker threads do not have process.chdir() + const shouldSwitchCwd = hasCustomCwd && process.chdir !== undefined && !process.chdir.disabled; + + // If a custom `cwd` was specified, we need to change the process cwd + // because `which` will do stat calls but does not support a custom cwd + if (shouldSwitchCwd) { + try { + process.chdir(parsed.options.cwd); + } catch (err) { + /* Empty */ + } + } + + let resolved; + + try { + resolved = which.sync(parsed.command, { + path: env[getPathKey({ env })], + pathExt: withoutPathExt ? path$2.delimiter : undefined, + }); + } catch (e) { + /* Empty */ + } finally { + if (shouldSwitchCwd) { + process.chdir(cwd); + } + } + + // If we successfully resolved, ensure that an absolute path is returned + // Note that when a custom `cwd` was used, we need to resolve to an absolute path based on it + if (resolved) { + resolved = path$2.resolve(hasCustomCwd ? parsed.options.cwd : '', resolved); + } + + return resolved; +} + +function resolveCommand$1(parsed) { + return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true); +} + +var resolveCommand_1 = resolveCommand$1; + +var _escape = {}; + +// See http://www.robvanderwoude.com/escapechars.php +const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g; + +function escapeCommand(arg) { + // Escape meta chars + arg = arg.replace(metaCharsRegExp, '^$1'); + + return arg; +} + +function escapeArgument(arg, doubleEscapeMetaChars) { + // Convert to string + arg = `${arg}`; + + // Algorithm below is based on https://qntm.org/cmd + + // Sequence of backslashes followed by a double quote: + // double up all the backslashes and escape the double quote + arg = arg.replace(/(\\*)"/g, '$1$1\\"'); + + // Sequence of backslashes followed by the end of the string + // (which will become a double quote later): + // double up all the backslashes + arg = arg.replace(/(\\*)$/, '$1$1'); + + // All other backslashes occur literally + + // Quote the whole thing: + arg = `"${arg}"`; + + // Escape meta chars + arg = arg.replace(metaCharsRegExp, '^$1'); + + // Double escape meta chars if necessary + if (doubleEscapeMetaChars) { + arg = arg.replace(metaCharsRegExp, '^$1'); + } + + return arg; +} + +_escape.command = escapeCommand; +_escape.argument = escapeArgument; + +var shebangRegex$1 = /^#!(.*)/; + +const shebangRegex = shebangRegex$1; + +var shebangCommand$1 = (string = '') => { + const match = string.match(shebangRegex); + + if (!match) { + return null; + } + + const [path, argument] = match[0].replace(/#! ?/, '').split(' '); + const binary = path.split('/').pop(); + + if (binary === 'env') { + return argument; + } + + return argument ? `${binary} ${argument}` : binary; +}; + +const fs = require$$0; +const shebangCommand = shebangCommand$1; + +function readShebang$1(command) { + // Read the first 150 bytes from the file + const size = 150; + const buffer = Buffer.alloc(size); + + let fd; + + try { + fd = fs.openSync(command, 'r'); + fs.readSync(fd, buffer, 0, size, 0); + fs.closeSync(fd); + } catch (e) { /* Empty */ } + + // Attempt to extract shebang (null is returned if not a shebang) + return shebangCommand(buffer.toString()); +} + +var readShebang_1 = readShebang$1; + +const path$1 = require$$0$1; +const resolveCommand = resolveCommand_1; +const escape = _escape; +const readShebang = readShebang_1; + +const isWin$2 = process.platform === 'win32'; +const isExecutableRegExp = /\.(?:com|exe)$/i; +const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i; + +function detectShebang(parsed) { + parsed.file = resolveCommand(parsed); + + const shebang = parsed.file && readShebang(parsed.file); + + if (shebang) { + parsed.args.unshift(parsed.file); + parsed.command = shebang; + + return resolveCommand(parsed); + } + + return parsed.file; +} + +function parseNonShell(parsed) { + if (!isWin$2) { + return parsed; + } + + // Detect & add support for shebangs + const commandFile = detectShebang(parsed); + + // We don't need a shell if the command filename is an executable + const needsShell = !isExecutableRegExp.test(commandFile); + + // If a shell is required, use cmd.exe and take care of escaping everything correctly + // Note that `forceShell` is an hidden option used only in tests + if (parsed.options.forceShell || needsShell) { + // Need to double escape meta chars if the command is a cmd-shim located in `node_modules/.bin/` + // The cmd-shim simply calls execute the package bin file with NodeJS, proxying any argument + // Because the escape of metachars with ^ gets interpreted when the cmd.exe is first called, + // we need to double escape them + const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile); + + // Normalize posix paths into OS compatible paths (e.g.: foo/bar -> foo\bar) + // This is necessary otherwise it will always fail with ENOENT in those cases + parsed.command = path$1.normalize(parsed.command); + + // Escape command & arguments + parsed.command = escape.command(parsed.command); + parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars)); + + const shellCommand = [parsed.command].concat(parsed.args).join(' '); + + parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`]; + parsed.command = process.env.comspec || 'cmd.exe'; + parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped + } + + return parsed; +} + +function parse$1(command, args, options) { + // Normalize arguments, similar to nodejs + if (args && !Array.isArray(args)) { + options = args; + args = null; + } + + args = args ? args.slice(0) : []; // Clone array to avoid changing the original + options = Object.assign({}, options); // Clone object to avoid changing the original + + // Build our parsed object + const parsed = { + command, + args, + options, + file: undefined, + original: { + command, + args, + }, + }; + + // Delegate further parsing to shell or non-shell + return options.shell ? parsed : parseNonShell(parsed); +} + +var parse_1 = parse$1; + +const isWin$1 = process.platform === 'win32'; + +function notFoundError(original, syscall) { + return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), { + code: 'ENOENT', + errno: 'ENOENT', + syscall: `${syscall} ${original.command}`, + path: original.command, + spawnargs: original.args, + }); +} + +function hookChildProcess(cp, parsed) { + if (!isWin$1) { + return; + } + + const originalEmit = cp.emit; + + cp.emit = function (name, arg1) { + // If emitting "exit" event and exit code is 1, we need to check if + // the command exists and emit an "error" instead + // See https://github.com/IndigoUnited/node-cross-spawn/issues/16 + if (name === 'exit') { + const err = verifyENOENT(arg1, parsed); + + if (err) { + return originalEmit.call(cp, 'error', err); + } + } + + return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params + }; +} + +function verifyENOENT(status, parsed) { + if (isWin$1 && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawn'); + } + + return null; +} + +function verifyENOENTSync(status, parsed) { + if (isWin$1 && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawnSync'); + } + + return null; +} + +var enoent$1 = { + hookChildProcess, + verifyENOENT, + verifyENOENTSync, + notFoundError, +}; + +const cp = require$$0$2; +const parse = parse_1; +const enoent = enoent$1; + +function spawn(command, args, options) { + // Parse the arguments + const parsed = parse(command, args, options); + + // Spawn the child process + const spawned = cp.spawn(parsed.command, parsed.args, parsed.options); + + // Hook into child process "exit" event to emit an error if the command + // does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16 + enoent.hookChildProcess(spawned, parsed); + + return spawned; +} + +function spawnSync(command, args, options) { + // Parse the arguments + const parsed = parse(command, args, options); + + // Spawn the child process + const result = cp.spawnSync(parsed.command, parsed.args, parsed.options); + + // Analyze if the command does not exist, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16 + result.error = result.error || enoent.verifyENOENTSync(result.status, parsed); + + return result; +} + +crossSpawn$1.exports = spawn; +crossSpawn$1.exports.spawn = spawn; +crossSpawn$1.exports.sync = spawnSync; + +crossSpawn$1.exports._parse = parse; +crossSpawn$1.exports._enoent = enoent; + +var crossSpawnExports = crossSpawn$1.exports; + +var stripFinalNewline$1 = input => { + const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt(); + const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt(); + + if (input[input.length - 1] === LF) { + input = input.slice(0, input.length - 1); + } + + if (input[input.length - 1] === CR) { + input = input.slice(0, input.length - 1); + } + + return input; +}; + +var npmRunPath$1 = {exports: {}}; + +npmRunPath$1.exports; + +(function (module) { + const path = require$$0$1; + const pathKey = pathKeyExports; + + const npmRunPath = options => { + options = { + cwd: process.cwd(), + path: process.env[pathKey()], + execPath: process.execPath, + ...options + }; + + let previous; + let cwdPath = path.resolve(options.cwd); + const result = []; + + while (previous !== cwdPath) { + result.push(path.join(cwdPath, 'node_modules/.bin')); + previous = cwdPath; + cwdPath = path.resolve(cwdPath, '..'); + } + + // Ensure the running `node` binary is used + const execPathDir = path.resolve(options.cwd, options.execPath, '..'); + result.push(execPathDir); + + return result.concat(options.path).join(path.delimiter); + }; + + module.exports = npmRunPath; + // TODO: Remove this for the next major release + module.exports.default = npmRunPath; + + module.exports.env = options => { + options = { + env: process.env, + ...options + }; + + const env = {...options.env}; + const path = pathKey({env}); + + options.path = env[path]; + env[path] = module.exports(options); + + return env; + }; +} (npmRunPath$1)); + +var npmRunPathExports = npmRunPath$1.exports; + +var onetime$2 = {exports: {}}; + +var mimicFn$2 = {exports: {}}; + +const mimicFn$1 = (to, from) => { + for (const prop of Reflect.ownKeys(from)) { + Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); + } + + return to; +}; + +mimicFn$2.exports = mimicFn$1; +// TODO: Remove this for the next major release +mimicFn$2.exports.default = mimicFn$1; + +var mimicFnExports = mimicFn$2.exports; + +const mimicFn = mimicFnExports; + +const calledFunctions = new WeakMap(); + +const onetime$1 = (function_, options = {}) => { + if (typeof function_ !== 'function') { + throw new TypeError('Expected a function'); + } + + let returnValue; + let callCount = 0; + const functionName = function_.displayName || function_.name || ''; + + const onetime = function (...arguments_) { + calledFunctions.set(onetime, ++callCount); + + if (callCount === 1) { + returnValue = function_.apply(this, arguments_); + function_ = null; + } else if (options.throw === true) { + throw new Error(`Function \`${functionName}\` can only be called once`); + } + + return returnValue; + }; + + mimicFn(onetime, function_); + calledFunctions.set(onetime, callCount); + + return onetime; +}; + +onetime$2.exports = onetime$1; +// TODO: Remove this for the next major release +onetime$2.exports.default = onetime$1; + +onetime$2.exports.callCount = function_ => { + if (!calledFunctions.has(function_)) { + throw new Error(`The given function \`${function_.name}\` is not wrapped by the \`onetime\` package`); + } + + return calledFunctions.get(function_); +}; + +var onetimeExports = onetime$2.exports; + +var main = {}; + +var signals$2 = {}; + +var core = {}; + +Object.defineProperty(core,"__esModule",{value:true});core.SIGNALS=void 0; + +const SIGNALS=[ +{ +name:"SIGHUP", +number:1, +action:"terminate", +description:"Terminal closed", +standard:"posix"}, + +{ +name:"SIGINT", +number:2, +action:"terminate", +description:"User interruption with CTRL-C", +standard:"ansi"}, + +{ +name:"SIGQUIT", +number:3, +action:"core", +description:"User interruption with CTRL-\\", +standard:"posix"}, + +{ +name:"SIGILL", +number:4, +action:"core", +description:"Invalid machine instruction", +standard:"ansi"}, + +{ +name:"SIGTRAP", +number:5, +action:"core", +description:"Debugger breakpoint", +standard:"posix"}, + +{ +name:"SIGABRT", +number:6, +action:"core", +description:"Aborted", +standard:"ansi"}, + +{ +name:"SIGIOT", +number:6, +action:"core", +description:"Aborted", +standard:"bsd"}, + +{ +name:"SIGBUS", +number:7, +action:"core", +description: +"Bus error due to misaligned, non-existing address or paging error", +standard:"bsd"}, + +{ +name:"SIGEMT", +number:7, +action:"terminate", +description:"Command should be emulated but is not implemented", +standard:"other"}, + +{ +name:"SIGFPE", +number:8, +action:"core", +description:"Floating point arithmetic error", +standard:"ansi"}, + +{ +name:"SIGKILL", +number:9, +action:"terminate", +description:"Forced termination", +standard:"posix", +forced:true}, + +{ +name:"SIGUSR1", +number:10, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGSEGV", +number:11, +action:"core", +description:"Segmentation fault", +standard:"ansi"}, + +{ +name:"SIGUSR2", +number:12, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGPIPE", +number:13, +action:"terminate", +description:"Broken pipe or socket", +standard:"posix"}, + +{ +name:"SIGALRM", +number:14, +action:"terminate", +description:"Timeout or timer", +standard:"posix"}, + +{ +name:"SIGTERM", +number:15, +action:"terminate", +description:"Termination", +standard:"ansi"}, + +{ +name:"SIGSTKFLT", +number:16, +action:"terminate", +description:"Stack is empty or overflowed", +standard:"other"}, + +{ +name:"SIGCHLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"posix"}, + +{ +name:"SIGCLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"other"}, + +{ +name:"SIGCONT", +number:18, +action:"unpause", +description:"Unpaused", +standard:"posix", +forced:true}, + +{ +name:"SIGSTOP", +number:19, +action:"pause", +description:"Paused", +standard:"posix", +forced:true}, + +{ +name:"SIGTSTP", +number:20, +action:"pause", +description:"Paused using CTRL-Z or \"suspend\"", +standard:"posix"}, + +{ +name:"SIGTTIN", +number:21, +action:"pause", +description:"Background process cannot read terminal input", +standard:"posix"}, + +{ +name:"SIGBREAK", +number:21, +action:"terminate", +description:"User interruption with CTRL-BREAK", +standard:"other"}, + +{ +name:"SIGTTOU", +number:22, +action:"pause", +description:"Background process cannot write to terminal output", +standard:"posix"}, + +{ +name:"SIGURG", +number:23, +action:"ignore", +description:"Socket received out-of-band data", +standard:"bsd"}, + +{ +name:"SIGXCPU", +number:24, +action:"core", +description:"Process timed out", +standard:"bsd"}, + +{ +name:"SIGXFSZ", +number:25, +action:"core", +description:"File too big", +standard:"bsd"}, + +{ +name:"SIGVTALRM", +number:26, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGPROF", +number:27, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGWINCH", +number:28, +action:"ignore", +description:"Terminal window size changed", +standard:"bsd"}, + +{ +name:"SIGIO", +number:29, +action:"terminate", +description:"I/O is available", +standard:"other"}, + +{ +name:"SIGPOLL", +number:29, +action:"terminate", +description:"Watched event", +standard:"other"}, + +{ +name:"SIGINFO", +number:29, +action:"ignore", +description:"Request for process information", +standard:"other"}, + +{ +name:"SIGPWR", +number:30, +action:"terminate", +description:"Device running out of power", +standard:"systemv"}, + +{ +name:"SIGSYS", +number:31, +action:"core", +description:"Invalid system call", +standard:"other"}, + +{ +name:"SIGUNUSED", +number:31, +action:"terminate", +description:"Invalid system call", +standard:"other"}];core.SIGNALS=SIGNALS; + +var realtime = {}; + +Object.defineProperty(realtime,"__esModule",{value:true});realtime.SIGRTMAX=realtime.getRealtimeSignals=void 0; +const getRealtimeSignals=function(){ +const length=SIGRTMAX-SIGRTMIN+1; +return Array.from({length},getRealtimeSignal); +};realtime.getRealtimeSignals=getRealtimeSignals; + +const getRealtimeSignal=function(value,index){ +return { +name:`SIGRT${index+1}`, +number:SIGRTMIN+index, +action:"terminate", +description:"Application-specific signal (realtime)", +standard:"posix"}; + +}; + +const SIGRTMIN=34; +const SIGRTMAX=64;realtime.SIGRTMAX=SIGRTMAX; + +Object.defineProperty(signals$2,"__esModule",{value:true});signals$2.getSignals=void 0;var _os$1=require$$0$3; + +var _core=core; +var _realtime$1=realtime; + + + +const getSignals=function(){ +const realtimeSignals=(0, _realtime$1.getRealtimeSignals)(); +const signals=[..._core.SIGNALS,...realtimeSignals].map(normalizeSignal); +return signals; +};signals$2.getSignals=getSignals; + + + + + + + +const normalizeSignal=function({ +name, +number:defaultNumber, +description, +action, +forced=false, +standard}) +{ +const{ +signals:{[name]:constantSignal}}= +_os$1.constants; +const supported=constantSignal!==undefined; +const number=supported?constantSignal:defaultNumber; +return {name,number,description,supported,action,forced,standard}; +}; + +Object.defineProperty(main,"__esModule",{value:true});main.signalsByNumber=main.signalsByName=void 0;var _os=require$$0$3; + +var _signals=signals$2; +var _realtime=realtime; + + + +const getSignalsByName=function(){ +const signals=(0, _signals.getSignals)(); +return signals.reduce(getSignalByName,{}); +}; + +const getSignalByName=function( +signalByNameMemo, +{name,number,description,supported,action,forced,standard}) +{ +return { +...signalByNameMemo, +[name]:{name,number,description,supported,action,forced,standard}}; + +}; + +const signalsByName$1=getSignalsByName();main.signalsByName=signalsByName$1; + + + + +const getSignalsByNumber=function(){ +const signals=(0, _signals.getSignals)(); +const length=_realtime.SIGRTMAX+1; +const signalsA=Array.from({length},(value,number)=> +getSignalByNumber(number,signals)); + +return Object.assign({},...signalsA); +}; + +const getSignalByNumber=function(number,signals){ +const signal=findSignalByNumber(number,signals); + +if(signal===undefined){ +return {}; +} + +const{name,description,supported,action,forced,standard}=signal; +return { +[number]:{ +name, +number, +description, +supported, +action, +forced, +standard}}; + + +}; + + + +const findSignalByNumber=function(number,signals){ +const signal=signals.find(({name})=>_os.constants.signals[name]===number); + +if(signal!==undefined){ +return signal; +} + +return signals.find(signalA=>signalA.number===number); +}; + +const signalsByNumber=getSignalsByNumber();main.signalsByNumber=signalsByNumber; + +const {signalsByName} = main; + +const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { + if (timedOut) { + return `timed out after ${timeout} milliseconds`; + } + + if (isCanceled) { + return 'was canceled'; + } + + if (errorCode !== undefined) { + return `failed with ${errorCode}`; + } + + if (signal !== undefined) { + return `was killed with ${signal} (${signalDescription})`; + } + + if (exitCode !== undefined) { + return `failed with exit code ${exitCode}`; + } + + return 'failed'; +}; + +const makeError$1 = ({ + stdout, + stderr, + all, + error, + signal, + exitCode, + command, + escapedCommand, + timedOut, + isCanceled, + killed, + parsed: {options: {timeout}} +}) => { + // `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`. + // We normalize them to `undefined` + exitCode = exitCode === null ? undefined : exitCode; + signal = signal === null ? undefined : signal; + const signalDescription = signal === undefined ? undefined : signalsByName[signal].description; + + const errorCode = error && error.code; + + const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}); + const execaMessage = `Command ${prefix}: ${command}`; + const isError = Object.prototype.toString.call(error) === '[object Error]'; + const shortMessage = isError ? `${execaMessage}\n${error.message}` : execaMessage; + const message = [shortMessage, stderr, stdout].filter(Boolean).join('\n'); + + if (isError) { + error.originalMessage = error.message; + error.message = message; + } else { + error = new Error(message); + } + + error.shortMessage = shortMessage; + error.command = command; + error.escapedCommand = escapedCommand; + error.exitCode = exitCode; + error.signal = signal; + error.signalDescription = signalDescription; + error.stdout = stdout; + error.stderr = stderr; + + if (all !== undefined) { + error.all = all; + } + + if ('bufferedData' in error) { + delete error.bufferedData; + } + + error.failed = true; + error.timedOut = Boolean(timedOut); + error.isCanceled = isCanceled; + error.killed = killed && !timedOut; + + return error; +}; + +var error = makeError$1; + +var stdio = {exports: {}}; + +const aliases = ['stdin', 'stdout', 'stderr']; + +const hasAlias = options => aliases.some(alias => options[alias] !== undefined); + +const normalizeStdio$1 = options => { + if (!options) { + return; + } + + const {stdio} = options; + + if (stdio === undefined) { + return aliases.map(alias => options[alias]); + } + + if (hasAlias(options)) { + throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${aliases.map(alias => `\`${alias}\``).join(', ')}`); + } + + if (typeof stdio === 'string') { + return stdio; + } + + if (!Array.isArray(stdio)) { + throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``); + } + + const length = Math.max(stdio.length, aliases.length); + return Array.from({length}, (value, index) => stdio[index]); +}; + +stdio.exports = normalizeStdio$1; + +// `ipc` is pushed unless it is already present +stdio.exports.node = options => { + const stdio = normalizeStdio$1(options); + + if (stdio === 'ipc') { + return 'ipc'; + } + + if (stdio === undefined || typeof stdio === 'string') { + return [stdio, stdio, stdio, 'ipc']; + } + + if (stdio.includes('ipc')) { + return stdio; + } + + return [...stdio, 'ipc']; +}; + +var stdioExports = stdio.exports; + +var signalExit = {exports: {}}; + +var signals$1 = {exports: {}}; + +var hasRequiredSignals; + +function requireSignals () { + if (hasRequiredSignals) return signals$1.exports; + hasRequiredSignals = 1; + (function (module) { + // This is not the set of all possible signals. + // + // It IS, however, the set of all signals that trigger + // an exit on either Linux or BSD systems. Linux is a + // superset of the signal names supported on BSD, and + // the unknown signals just fail to register, so we can + // catch that easily enough. + // + // Don't bother with SIGKILL. It's uncatchable, which + // means that we can't fire any callbacks anyway. + // + // If a user does happen to register a handler on a non- + // fatal signal like SIGWINCH or something, and then + // exit, it'll end up firing `process.emit('exit')`, so + // the handler will be fired anyway. + // + // SIGBUS, SIGFPE, SIGSEGV and SIGILL, when not raised + // artificially, inherently leave the process in a + // state from which it is not safe to try and enter JS + // listeners. + module.exports = [ + 'SIGABRT', + 'SIGALRM', + 'SIGHUP', + 'SIGINT', + 'SIGTERM' + ]; + + if (process.platform !== 'win32') { + module.exports.push( + 'SIGVTALRM', + 'SIGXCPU', + 'SIGXFSZ', + 'SIGUSR2', + 'SIGTRAP', + 'SIGSYS', + 'SIGQUIT', + 'SIGIOT' + // should detect profiler and enable/disable accordingly. + // see #21 + // 'SIGPROF' + ); + } + + if (process.platform === 'linux') { + module.exports.push( + 'SIGIO', + 'SIGPOLL', + 'SIGPWR', + 'SIGSTKFLT', + 'SIGUNUSED' + ); + } + } (signals$1)); + return signals$1.exports; +} + +// Note: since nyc uses this module to output coverage, any lines +// that are in the direct sync flow of nyc's outputCoverage are +// ignored, since we can never get coverage for them. +// grab a reference to node's real process object right away +var process$1 = commonjsGlobal.process; + +const processOk = function (process) { + return process && + typeof process === 'object' && + typeof process.removeListener === 'function' && + typeof process.emit === 'function' && + typeof process.reallyExit === 'function' && + typeof process.listeners === 'function' && + typeof process.kill === 'function' && + typeof process.pid === 'number' && + typeof process.on === 'function' +}; + +// some kind of non-node environment, just no-op +/* istanbul ignore if */ +if (!processOk(process$1)) { + signalExit.exports = function () { + return function () {} + }; +} else { + var assert = require$$0$4; + var signals = requireSignals(); + var isWin = /^win/i.test(process$1.platform); + + var EE = require$$2; + /* istanbul ignore if */ + if (typeof EE !== 'function') { + EE = EE.EventEmitter; + } + + var emitter; + if (process$1.__signal_exit_emitter__) { + emitter = process$1.__signal_exit_emitter__; + } else { + emitter = process$1.__signal_exit_emitter__ = new EE(); + emitter.count = 0; + emitter.emitted = {}; + } + + // Because this emitter is a global, we have to check to see if a + // previous version of this library failed to enable infinite listeners. + // I know what you're about to say. But literally everything about + // signal-exit is a compromise with evil. Get used to it. + if (!emitter.infinite) { + emitter.setMaxListeners(Infinity); + emitter.infinite = true; + } + + signalExit.exports = function (cb, opts) { + /* istanbul ignore if */ + if (!processOk(commonjsGlobal.process)) { + return function () {} + } + assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler'); + + if (loaded === false) { + load(); + } + + var ev = 'exit'; + if (opts && opts.alwaysLast) { + ev = 'afterexit'; + } + + var remove = function () { + emitter.removeListener(ev, cb); + if (emitter.listeners('exit').length === 0 && + emitter.listeners('afterexit').length === 0) { + unload(); + } + }; + emitter.on(ev, cb); + + return remove + }; + + var unload = function unload () { + if (!loaded || !processOk(commonjsGlobal.process)) { + return + } + loaded = false; + + signals.forEach(function (sig) { + try { + process$1.removeListener(sig, sigListeners[sig]); + } catch (er) {} + }); + process$1.emit = originalProcessEmit; + process$1.reallyExit = originalProcessReallyExit; + emitter.count -= 1; + }; + signalExit.exports.unload = unload; + + var emit = function emit (event, code, signal) { + /* istanbul ignore if */ + if (emitter.emitted[event]) { + return + } + emitter.emitted[event] = true; + emitter.emit(event, code, signal); + }; + + // { : , ... } + var sigListeners = {}; + signals.forEach(function (sig) { + sigListeners[sig] = function listener () { + /* istanbul ignore if */ + if (!processOk(commonjsGlobal.process)) { + return + } + // If there are no other listeners, an exit is coming! + // Simplest way: remove us and then re-send the signal. + // We know that this will kill the process, so we can + // safely emit now. + var listeners = process$1.listeners(sig); + if (listeners.length === emitter.count) { + unload(); + emit('exit', null, sig); + /* istanbul ignore next */ + emit('afterexit', null, sig); + /* istanbul ignore next */ + if (isWin && sig === 'SIGHUP') { + // "SIGHUP" throws an `ENOSYS` error on Windows, + // so use a supported signal instead + sig = 'SIGINT'; + } + /* istanbul ignore next */ + process$1.kill(process$1.pid, sig); + } + }; + }); + + signalExit.exports.signals = function () { + return signals + }; + + var loaded = false; + + var load = function load () { + if (loaded || !processOk(commonjsGlobal.process)) { + return + } + loaded = true; + + // This is the number of onSignalExit's that are in play. + // It's important so that we can count the correct number of + // listeners on signals, and don't wait for the other one to + // handle it instead of us. + emitter.count += 1; + + signals = signals.filter(function (sig) { + try { + process$1.on(sig, sigListeners[sig]); + return true + } catch (er) { + return false + } + }); + + process$1.emit = processEmit; + process$1.reallyExit = processReallyExit; + }; + signalExit.exports.load = load; + + var originalProcessReallyExit = process$1.reallyExit; + var processReallyExit = function processReallyExit (code) { + /* istanbul ignore if */ + if (!processOk(commonjsGlobal.process)) { + return + } + process$1.exitCode = code || /* istanbul ignore next */ 0; + emit('exit', process$1.exitCode, null); + /* istanbul ignore next */ + emit('afterexit', process$1.exitCode, null); + /* istanbul ignore next */ + originalProcessReallyExit.call(process$1, process$1.exitCode); + }; + + var originalProcessEmit = process$1.emit; + var processEmit = function processEmit (ev, arg) { + if (ev === 'exit' && processOk(commonjsGlobal.process)) { + /* istanbul ignore else */ + if (arg !== undefined) { + process$1.exitCode = arg; + } + var ret = originalProcessEmit.apply(this, arguments); + /* istanbul ignore next */ + emit('exit', process$1.exitCode, null); + /* istanbul ignore next */ + emit('afterexit', process$1.exitCode, null); + /* istanbul ignore next */ + return ret + } else { + return originalProcessEmit.apply(this, arguments) + } + }; +} + +var signalExitExports = signalExit.exports; + +const os = require$$0$3; +const onExit = signalExitExports; + +const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; + +// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior +const spawnedKill$1 = (kill, signal = 'SIGTERM', options = {}) => { + const killResult = kill(signal); + setKillTimeout(kill, signal, options, killResult); + return killResult; +}; + +const setKillTimeout = (kill, signal, options, killResult) => { + if (!shouldForceKill(signal, options, killResult)) { + return; + } + + const timeout = getForceKillAfterTimeout(options); + const t = setTimeout(() => { + kill('SIGKILL'); + }, timeout); + + // Guarded because there's no `.unref()` when `execa` is used in the renderer + // process in Electron. This cannot be tested since we don't run tests in + // Electron. + // istanbul ignore else + if (t.unref) { + t.unref(); + } +}; + +const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => { + return isSigterm(signal) && forceKillAfterTimeout !== false && killResult; +}; + +const isSigterm = signal => { + return signal === os.constants.signals.SIGTERM || + (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM'); +}; + +const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => { + if (forceKillAfterTimeout === true) { + return DEFAULT_FORCE_KILL_TIMEOUT; + } + + if (!Number.isFinite(forceKillAfterTimeout) || forceKillAfterTimeout < 0) { + throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`); + } + + return forceKillAfterTimeout; +}; + +// `childProcess.cancel()` +const spawnedCancel$1 = (spawned, context) => { + const killResult = spawned.kill(); + + if (killResult) { + context.isCanceled = true; + } +}; + +const timeoutKill = (spawned, signal, reject) => { + spawned.kill(signal); + reject(Object.assign(new Error('Timed out'), {timedOut: true, signal})); +}; + +// `timeout` option handling +const setupTimeout$1 = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => { + if (timeout === 0 || timeout === undefined) { + return spawnedPromise; + } + + let timeoutId; + const timeoutPromise = new Promise((resolve, reject) => { + timeoutId = setTimeout(() => { + timeoutKill(spawned, killSignal, reject); + }, timeout); + }); + + const safeSpawnedPromise = spawnedPromise.finally(() => { + clearTimeout(timeoutId); + }); + + return Promise.race([timeoutPromise, safeSpawnedPromise]); +}; + +const validateTimeout$1 = ({timeout}) => { + if (timeout !== undefined && (!Number.isFinite(timeout) || timeout < 0)) { + throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); + } +}; + +// `cleanup` option handling +const setExitHandler$1 = async (spawned, {cleanup, detached}, timedPromise) => { + if (!cleanup || detached) { + return timedPromise; + } + + const removeExitHandler = onExit(() => { + spawned.kill(); + }); + + return timedPromise.finally(() => { + removeExitHandler(); + }); +}; + +var kill = { + spawnedKill: spawnedKill$1, + spawnedCancel: spawnedCancel$1, + setupTimeout: setupTimeout$1, + validateTimeout: validateTimeout$1, + setExitHandler: setExitHandler$1 +}; + +const isStream$1 = stream => + stream !== null && + typeof stream === 'object' && + typeof stream.pipe === 'function'; + +isStream$1.writable = stream => + isStream$1(stream) && + stream.writable !== false && + typeof stream._write === 'function' && + typeof stream._writableState === 'object'; + +isStream$1.readable = stream => + isStream$1(stream) && + stream.readable !== false && + typeof stream._read === 'function' && + typeof stream._readableState === 'object'; + +isStream$1.duplex = stream => + isStream$1.writable(stream) && + isStream$1.readable(stream); + +isStream$1.transform = stream => + isStream$1.duplex(stream) && + typeof stream._transform === 'function'; + +var isStream_1 = isStream$1; + +var getStream$2 = {exports: {}}; + +const {PassThrough: PassThroughStream} = require$$0$5; + +var bufferStream$1 = options => { + options = {...options}; + + const {array} = options; + let {encoding} = options; + const isBuffer = encoding === 'buffer'; + let objectMode = false; + + if (array) { + objectMode = !(encoding || isBuffer); + } else { + encoding = encoding || 'utf8'; + } + + if (isBuffer) { + encoding = null; + } + + const stream = new PassThroughStream({objectMode}); + + if (encoding) { + stream.setEncoding(encoding); + } + + let length = 0; + const chunks = []; + + stream.on('data', chunk => { + chunks.push(chunk); + + if (objectMode) { + length = chunks.length; + } else { + length += chunk.length; + } + }); + + stream.getBufferedValue = () => { + if (array) { + return chunks; + } + + return isBuffer ? Buffer.concat(chunks, length) : chunks.join(''); + }; + + stream.getBufferedLength = () => length; + + return stream; +}; + +const {constants: BufferConstants} = require$$0$6; +const stream$1 = require$$0$5; +const {promisify} = require$$2$1; +const bufferStream = bufferStream$1; + +const streamPipelinePromisified = promisify(stream$1.pipeline); + +class MaxBufferError extends Error { + constructor() { + super('maxBuffer exceeded'); + this.name = 'MaxBufferError'; + } +} + +async function getStream$1(inputStream, options) { + if (!inputStream) { + throw new Error('Expected a stream'); + } + + options = { + maxBuffer: Infinity, + ...options + }; + + const {maxBuffer} = options; + const stream = bufferStream(options); + + await new Promise((resolve, reject) => { + const rejectPromise = error => { + // Don't retrieve an oversized buffer. + if (error && stream.getBufferedLength() <= BufferConstants.MAX_LENGTH) { + error.bufferedData = stream.getBufferedValue(); + } + + reject(error); + }; + + (async () => { + try { + await streamPipelinePromisified(inputStream, stream); + resolve(); + } catch (error) { + rejectPromise(error); + } + })(); + + stream.on('data', () => { + if (stream.getBufferedLength() > maxBuffer) { + rejectPromise(new MaxBufferError()); + } + }); + }); + + return stream.getBufferedValue(); +} + +getStream$2.exports = getStream$1; +getStream$2.exports.buffer = (stream, options) => getStream$1(stream, {...options, encoding: 'buffer'}); +getStream$2.exports.array = (stream, options) => getStream$1(stream, {...options, array: true}); +getStream$2.exports.MaxBufferError = MaxBufferError; + +var getStreamExports = getStream$2.exports; + +const { PassThrough } = require$$0$5; + +var mergeStream$1 = function (/*streams...*/) { + var sources = []; + var output = new PassThrough({objectMode: true}); + + output.setMaxListeners(0); + + output.add = add; + output.isEmpty = isEmpty; + + output.on('unpipe', remove); + + Array.prototype.slice.call(arguments).forEach(add); + + return output + + function add (source) { + if (Array.isArray(source)) { + source.forEach(add); + return this + } + + sources.push(source); + source.once('end', remove.bind(null, source)); + source.once('error', output.emit.bind(output, 'error')); + source.pipe(output, {end: false}); + return this + } + + function isEmpty () { + return sources.length == 0; + } + + function remove (source) { + sources = sources.filter(function (it) { return it !== source }); + if (!sources.length && output.readable) { output.end(); } + } +}; + +const isStream = isStream_1; +const getStream = getStreamExports; +const mergeStream = mergeStream$1; + +// `input` option +const handleInput$1 = (spawned, input) => { + // Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852 + // @todo remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0 + if (input === undefined || spawned.stdin === undefined) { + return; + } + + if (isStream(input)) { + input.pipe(spawned.stdin); + } else { + spawned.stdin.end(input); + } +}; + +// `all` interleaves `stdout` and `stderr` +const makeAllStream$1 = (spawned, {all}) => { + if (!all || (!spawned.stdout && !spawned.stderr)) { + return; + } + + const mixed = mergeStream(); + + if (spawned.stdout) { + mixed.add(spawned.stdout); + } + + if (spawned.stderr) { + mixed.add(spawned.stderr); + } + + return mixed; +}; + +// On failure, `result.stdout|stderr|all` should contain the currently buffered stream +const getBufferedData = async (stream, streamPromise) => { + if (!stream) { + return; + } + + stream.destroy(); + + try { + return await streamPromise; + } catch (error) { + return error.bufferedData; + } +}; + +const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { + if (!stream || !buffer) { + return; + } + + if (encoding) { + return getStream(stream, {encoding, maxBuffer}); + } + + return getStream.buffer(stream, {maxBuffer}); +}; + +// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all) +const getSpawnedResult$1 = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => { + const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer}); + const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer}); + const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2}); + + try { + return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]); + } catch (error) { + return Promise.all([ + {error, signal: error.signal, timedOut: error.timedOut}, + getBufferedData(stdout, stdoutPromise), + getBufferedData(stderr, stderrPromise), + getBufferedData(all, allPromise) + ]); + } +}; + +const validateInputSync$1 = ({input}) => { + if (isStream(input)) { + throw new TypeError('The `input` option cannot be a stream in sync mode'); + } +}; + +var stream = { + handleInput: handleInput$1, + makeAllStream: makeAllStream$1, + getSpawnedResult: getSpawnedResult$1, + validateInputSync: validateInputSync$1 +}; + +const nativePromisePrototype = (async () => {})().constructor.prototype; +const descriptors = ['then', 'catch', 'finally'].map(property => [ + property, + Reflect.getOwnPropertyDescriptor(nativePromisePrototype, property) +]); + +// The return value is a mixin of `childProcess` and `Promise` +const mergePromise$1 = (spawned, promise) => { + for (const [property, descriptor] of descriptors) { + // Starting the main `promise` is deferred to avoid consuming streams + const value = typeof promise === 'function' ? + (...args) => Reflect.apply(descriptor.value, promise(), args) : + descriptor.value.bind(promise); + + Reflect.defineProperty(spawned, property, {...descriptor, value}); + } + + return spawned; +}; + +// Use promises instead of `child_process` events +const getSpawnedPromise$1 = spawned => { + return new Promise((resolve, reject) => { + spawned.on('exit', (exitCode, signal) => { + resolve({exitCode, signal}); + }); + + spawned.on('error', error => { + reject(error); + }); + + if (spawned.stdin) { + spawned.stdin.on('error', error => { + reject(error); + }); + } + }); +}; + +var promise = { + mergePromise: mergePromise$1, + getSpawnedPromise: getSpawnedPromise$1 +}; + +const normalizeArgs = (file, args = []) => { + if (!Array.isArray(args)) { + return [file]; + } + + return [file, ...args]; +}; + +const NO_ESCAPE_REGEXP = /^[\w.-]+$/; +const DOUBLE_QUOTES_REGEXP = /"/g; + +const escapeArg = arg => { + if (typeof arg !== 'string' || NO_ESCAPE_REGEXP.test(arg)) { + return arg; + } + + return `"${arg.replace(DOUBLE_QUOTES_REGEXP, '\\"')}"`; +}; + +const joinCommand$1 = (file, args) => { + return normalizeArgs(file, args).join(' '); +}; + +const getEscapedCommand$1 = (file, args) => { + return normalizeArgs(file, args).map(arg => escapeArg(arg)).join(' '); +}; + +const SPACES_REGEXP = / +/g; + +// Handle `execa.command()` +const parseCommand$1 = command => { + const tokens = []; + for (const token of command.trim().split(SPACES_REGEXP)) { + // Allow spaces to be escaped by a backslash if not meant as a delimiter + const previousToken = tokens[tokens.length - 1]; + if (previousToken && previousToken.endsWith('\\')) { + // Merge previous token with current one + tokens[tokens.length - 1] = `${previousToken.slice(0, -1)} ${token}`; + } else { + tokens.push(token); + } + } + + return tokens; +}; + +var command = { + joinCommand: joinCommand$1, + getEscapedCommand: getEscapedCommand$1, + parseCommand: parseCommand$1 +}; + +const path = require$$0$1; +const childProcess = require$$0$2; +const crossSpawn = crossSpawnExports; +const stripFinalNewline = stripFinalNewline$1; +const npmRunPath = npmRunPathExports; +const onetime = onetimeExports; +const makeError = error; +const normalizeStdio = stdioExports; +const {spawnedKill, spawnedCancel, setupTimeout, validateTimeout, setExitHandler} = kill; +const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = stream; +const {mergePromise, getSpawnedPromise} = promise; +const {joinCommand, parseCommand, getEscapedCommand} = command; + +const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; + +const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => { + const env = extendEnv ? {...process.env, ...envOption} : envOption; + + if (preferLocal) { + return npmRunPath.env({env, cwd: localDir, execPath}); + } + + return env; +}; + +const handleArguments = (file, args, options = {}) => { + const parsed = crossSpawn._parse(file, args, options); + file = parsed.command; + args = parsed.args; + options = parsed.options; + + options = { + maxBuffer: DEFAULT_MAX_BUFFER, + buffer: true, + stripFinalNewline: true, + extendEnv: true, + preferLocal: false, + localDir: options.cwd || process.cwd(), + execPath: process.execPath, + encoding: 'utf8', + reject: true, + cleanup: true, + all: false, + windowsHide: true, + ...options + }; + + options.env = getEnv(options); + + options.stdio = normalizeStdio(options); + + if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') { + // #116 + args.unshift('/q'); + } + + return {file, args, options, parsed}; +}; + +const handleOutput = (options, value, error) => { + if (typeof value !== 'string' && !Buffer.isBuffer(value)) { + // When `execa.sync()` errors, we normalize it to '' to mimic `execa()` + return error === undefined ? undefined : ''; + } + + if (options.stripFinalNewline) { + return stripFinalNewline(value); + } + + return value; +}; + +const execa = (file, args, options) => { + const parsed = handleArguments(file, args, options); + const command = joinCommand(file, args); + const escapedCommand = getEscapedCommand(file, args); + + validateTimeout(parsed.options); + + let spawned; + try { + spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options); + } catch (error) { + // Ensure the returned error is always both a promise and a child process + const dummySpawned = new childProcess.ChildProcess(); + const errorPromise = Promise.reject(makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + escapedCommand, + parsed, + timedOut: false, + isCanceled: false, + killed: false + })); + return mergePromise(dummySpawned, errorPromise); + } + + const spawnedPromise = getSpawnedPromise(spawned); + const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise); + const processDone = setExitHandler(spawned, parsed.options, timedPromise); + + const context = {isCanceled: false}; + + spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned)); + spawned.cancel = spawnedCancel.bind(null, spawned, context); + + const handlePromise = async () => { + const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone); + const stdout = handleOutput(parsed.options, stdoutResult); + const stderr = handleOutput(parsed.options, stderrResult); + const all = handleOutput(parsed.options, allResult); + + if (error || exitCode !== 0 || signal !== null) { + const returnedError = makeError({ + error, + exitCode, + signal, + stdout, + stderr, + all, + command, + escapedCommand, + parsed, + timedOut, + isCanceled: context.isCanceled, + killed: spawned.killed + }); + + if (!parsed.options.reject) { + return returnedError; + } + + throw returnedError; + } + + return { + command, + escapedCommand, + exitCode: 0, + stdout, + stderr, + all, + failed: false, + timedOut: false, + isCanceled: false, + killed: false + }; + }; + + const handlePromiseOnce = onetime(handlePromise); + + handleInput(spawned, parsed.options.input); + + spawned.all = makeAllStream(spawned, parsed.options); + + return mergePromise(spawned, handlePromiseOnce); +}; + +execa$2.exports = execa; + +execa$2.exports.sync = (file, args, options) => { + const parsed = handleArguments(file, args, options); + const command = joinCommand(file, args); + const escapedCommand = getEscapedCommand(file, args); + + validateInputSync(parsed.options); + + let result; + try { + result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options); + } catch (error) { + throw makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + escapedCommand, + parsed, + timedOut: false, + isCanceled: false, + killed: false + }); + } + + const stdout = handleOutput(parsed.options, result.stdout, result.error); + const stderr = handleOutput(parsed.options, result.stderr, result.error); + + if (result.error || result.status !== 0 || result.signal !== null) { + const error = makeError({ + stdout, + stderr, + error: result.error, + signal: result.signal, + exitCode: result.status, + command, + escapedCommand, + parsed, + timedOut: result.error && result.error.code === 'ETIMEDOUT', + isCanceled: false, + killed: result.signal !== null + }); + + if (!parsed.options.reject) { + return error; + } + + throw error; + } + + return { + command, + escapedCommand, + exitCode: 0, + stdout, + stderr, + failed: false, + timedOut: false, + isCanceled: false, + killed: false + }; +}; + +execa$2.exports.command = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa(file, args, options); +}; + +execa$2.exports.commandSync = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa.sync(file, args, options); +}; + +execa$2.exports.node = (scriptPath, args, options = {}) => { + if (args && !Array.isArray(args) && typeof args === 'object') { + options = args; + args = []; + } + + const stdio = normalizeStdio.node(options); + const defaultExecArgv = process.execArgv.filter(arg => !arg.startsWith('--inspect')); + + const { + nodePath = process.execPath, + nodeOptions = defaultExecArgv + } = options; + + return execa( + nodePath, + [ + ...nodeOptions, + scriptPath, + ...(Array.isArray(args) ? args : []) + ], + { + ...options, + stdin: undefined, + stdout: undefined, + stderr: undefined, + stdio, + shell: false + } + ); +}; + +var execaExports = execa$2.exports; +var execa$1 = /*@__PURE__*/getDefaultExportFromCjs(execaExports); + +function ansiRegex({onlyFirst = false} = {}) { + // Valid string terminator sequences are BEL, ESC\, and 0x9c + const ST = '(?:\\u0007|\\u001B\\u005C|\\u009C)'; + const pattern = [ + `[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?${ST})`, + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))', + ].join('|'); + + return new RegExp(pattern, onlyFirst ? undefined : 'g'); +} + +const regex = ansiRegex(); + +function stripAnsi(string) { + if (typeof string !== 'string') { + throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``); + } + + // Even though the regex is global, we don't need to reset the `.lastIndex` + // because unlike `.exec()` and `.test()`, `.replace()` does it automatically + // and doing it manually has a performance penalty. + return string.replace(regex, ''); +} + +const detectDefaultShell = () => { + const {env} = process$2; + + if (process$2.platform === 'win32') { + return env.COMSPEC || 'cmd.exe'; + } + + try { + const {shell} = node_os.userInfo(); + if (shell) { + return shell; + } + } catch {} + + if (process$2.platform === 'darwin') { + return env.SHELL || '/bin/zsh'; + } + + return env.SHELL || '/bin/sh'; +}; + +// Stores default shell when imported. +const defaultShell = detectDefaultShell(); + +const args = [ + '-ilc', + 'echo -n "_SHELL_ENV_DELIMITER_"; env; echo -n "_SHELL_ENV_DELIMITER_"; exit', +]; + +const env = { + // Disables Oh My Zsh auto-update thing that can block the process. + DISABLE_AUTO_UPDATE: 'true', +}; + +const parseEnv = env => { + env = env.split('_SHELL_ENV_DELIMITER_')[1]; + const returnValue = {}; + + for (const line of stripAnsi(env).split('\n').filter(line => Boolean(line))) { + const [key, ...values] = line.split('='); + returnValue[key] = values.join('='); + } + + return returnValue; +}; + +function shellEnvSync(shell) { + if (process$2.platform === 'win32') { + return process$2.env; + } + + try { + const {stdout} = execa$1.sync(shell || defaultShell, args, {env}); + return parseEnv(stdout); + } catch (error) { + if (shell) { + throw error; + } else { + return process$2.env; + } + } +} + +function shellPathSync() { + const {PATH} = shellEnvSync(); + return PATH; +} + +function fixPath() { + if (process$2.platform === 'win32') { + return; + } + + process$2.env.PATH = shellPathSync() || [ + './node_modules/.bin', + '/.nodebrew/current/bin', + '/usr/local/bin', + process$2.env.PATH, + ].join(':'); +} + +const IMAGE_EXT_LIST = [ + ".png", + ".jpg", + ".jpeg", + ".bmp", + ".gif", + ".svg", + ".tiff", + ".webp", + ".avif", +]; +function isAnImage(ext) { + return IMAGE_EXT_LIST.includes(ext.toLowerCase()); +} +function isAssetTypeAnImage(path) { + return isAnImage(pathBrowserify.extname(path)); +} +function getOS() { + const { appVersion } = navigator; + if (appVersion.indexOf("Win") !== -1) { + return "Windows"; + } + else if (appVersion.indexOf("Mac") !== -1) { + return "MacOS"; + } + else if (appVersion.indexOf("X11") !== -1) { + return "Linux"; + } + else { + return "Unknown OS"; + } +} +async function streamToString(stream) { + const chunks = []; + for await (const chunk of stream) { + chunks.push(Buffer.from(chunk)); + } + // @ts-ignore + return Buffer.concat(chunks).toString("utf-8"); +} +function getUrlAsset(url) { + return (url = url.substr(1 + url.lastIndexOf("/")).split("?")[0]).split("#")[0]; +} +function getLastImage(list) { + const reversedList = list.reverse(); + let lastImage; + reversedList.forEach(item => { + if (item && item.startsWith("http")) { + lastImage = item; + return item; + } + }); + return lastImage; +} +function arrayToObject(arr, key) { + const obj = {}; + arr.forEach(element => { + obj[element[key]] = element; + }); + return obj; +} +function bufferToArrayBuffer(buffer) { + const arrayBuffer = new ArrayBuffer(buffer.length); + const view = new Uint8Array(arrayBuffer); + for (let i = 0; i < buffer.length; i++) { + view[i] = buffer[i]; + } + return arrayBuffer; +} +function uuid() { + return Math.random().toString(36).slice(2); +} + +// Primitive types +function dv(array) { + return new DataView(array.buffer, array.byteOffset); +} +/** + * 8-bit unsigned integer + */ +const UINT8 = { + len: 1, + get(array, offset) { + return dv(array).getUint8(offset); + }, + put(array, offset, value) { + dv(array).setUint8(offset, value); + return offset + 1; + } +}; +/** + * 16-bit unsigned integer, Little Endian byte order + */ +const UINT16_LE = { + len: 2, + get(array, offset) { + return dv(array).getUint16(offset, true); + }, + put(array, offset, value) { + dv(array).setUint16(offset, value, true); + return offset + 2; + } +}; +/** + * 16-bit unsigned integer, Big Endian byte order + */ +const UINT16_BE = { + len: 2, + get(array, offset) { + return dv(array).getUint16(offset); + }, + put(array, offset, value) { + dv(array).setUint16(offset, value); + return offset + 2; + } +}; +/** + * 32-bit unsigned integer, Little Endian byte order + */ +const UINT32_LE = { + len: 4, + get(array, offset) { + return dv(array).getUint32(offset, true); + }, + put(array, offset, value) { + dv(array).setUint32(offset, value, true); + return offset + 4; + } +}; +/** + * 32-bit unsigned integer, Big Endian byte order + */ +const UINT32_BE = { + len: 4, + get(array, offset) { + return dv(array).getUint32(offset); + }, + put(array, offset, value) { + dv(array).setUint32(offset, value); + return offset + 4; + } +}; +/** + * 32-bit signed integer, Big Endian byte order + */ +const INT32_BE = { + len: 4, + get(array, offset) { + return dv(array).getInt32(offset); + }, + put(array, offset, value) { + dv(array).setInt32(offset, value); + return offset + 4; + } +}; +/** + * 64-bit unsigned integer, Little Endian byte order + */ +const UINT64_LE = { + len: 8, + get(array, offset) { + return dv(array).getBigUint64(offset, true); + }, + put(array, offset, value) { + dv(array).setBigUint64(offset, value, true); + return offset + 8; + } +}; +/** + * Consume a fixed number of bytes from the stream and return a string with a specified encoding. + */ +class StringType { + constructor(len, encoding) { + this.len = len; + this.encoding = encoding; + } + get(uint8Array, offset) { + return node_buffer.Buffer.from(uint8Array).toString(this.encoding, offset, offset + this.len); + } +} + +const defaultMessages = 'End-Of-Stream'; +/** + * Thrown on read operation of the end of file or stream has been reached + */ +class EndOfStreamError extends Error { + constructor() { + super(defaultMessages); + } +} + +class Deferred { + constructor() { + this.resolve = () => null; + this.reject = () => null; + this.promise = new Promise((resolve, reject) => { + this.reject = reject; + this.resolve = resolve; + }); + } +} + +class AbstractStreamReader { + constructor() { + /** + * Maximum request length on read-stream operation + */ + this.maxStreamReadSize = 1 * 1024 * 1024; + this.endOfStream = false; + /** + * Store peeked data + * @type {Array} + */ + this.peekQueue = []; + } + async peek(uint8Array, offset, length) { + const bytesRead = await this.read(uint8Array, offset, length); + this.peekQueue.push(uint8Array.subarray(offset, offset + bytesRead)); // Put read data back to peek buffer + return bytesRead; + } + async read(buffer, offset, length) { + if (length === 0) { + return 0; + } + let bytesRead = this.readFromPeekBuffer(buffer, offset, length); + bytesRead += await this.readRemainderFromStream(buffer, offset + bytesRead, length - bytesRead); + if (bytesRead === 0) { + throw new EndOfStreamError(); + } + return bytesRead; + } + /** + * Read chunk from stream + * @param buffer - Target Uint8Array (or Buffer) to store data read from stream in + * @param offset - Offset target + * @param length - Number of bytes to read + * @returns Number of bytes read + */ + readFromPeekBuffer(buffer, offset, length) { + let remaining = length; + let bytesRead = 0; + // consume peeked data first + while (this.peekQueue.length > 0 && remaining > 0) { + const peekData = this.peekQueue.pop(); // Front of queue + if (!peekData) + throw new Error('peekData should be defined'); + const lenCopy = Math.min(peekData.length, remaining); + buffer.set(peekData.subarray(0, lenCopy), offset + bytesRead); + bytesRead += lenCopy; + remaining -= lenCopy; + if (lenCopy < peekData.length) { + // remainder back to queue + this.peekQueue.push(peekData.subarray(lenCopy)); + } + } + return bytesRead; + } + async readRemainderFromStream(buffer, offset, initialRemaining) { + let remaining = initialRemaining; + let bytesRead = 0; + // Continue reading from stream if required + while (remaining > 0 && !this.endOfStream) { + const reqLen = Math.min(remaining, this.maxStreamReadSize); + const chunkLen = await this.readFromStream(buffer, offset + bytesRead, reqLen); + if (chunkLen === 0) + break; + bytesRead += chunkLen; + remaining -= chunkLen; + } + return bytesRead; + } +} + +/** + * Node.js Readable Stream Reader + * Ref: https://nodejs.org/api/stream.html#readable-streams + */ +class StreamReader extends AbstractStreamReader { + constructor(s) { + super(); + this.s = s; + /** + * Deferred used for postponed read request (as not data is yet available to read) + */ + this.deferred = null; + if (!s.read || !s.once) { + throw new Error('Expected an instance of stream.Readable'); + } + this.s.once('end', () => this.reject(new EndOfStreamError())); + this.s.once('error', err => this.reject(err)); + this.s.once('close', () => this.reject(new Error('Stream closed'))); + } + /** + * Read chunk from stream + * @param buffer Target Uint8Array (or Buffer) to store data read from stream in + * @param offset Offset target + * @param length Number of bytes to read + * @returns Number of bytes read + */ + async readFromStream(buffer, offset, length) { + if (this.endOfStream) { + return 0; + } + const readBuffer = this.s.read(length); + if (readBuffer) { + buffer.set(readBuffer, offset); + return readBuffer.length; + } + const request = { + buffer, + offset, + length, + deferred: new Deferred() + }; + this.deferred = request.deferred; + this.s.once('readable', () => { + this.readDeferred(request); + }); + return request.deferred.promise; + } + /** + * Process deferred read request + * @param request Deferred read request + */ + readDeferred(request) { + const readBuffer = this.s.read(request.length); + if (readBuffer) { + request.buffer.set(readBuffer, request.offset); + request.deferred.resolve(readBuffer.length); + this.deferred = null; + } + else { + this.s.once('readable', () => { + this.readDeferred(request); + }); + } + } + reject(err) { + this.endOfStream = true; + if (this.deferred) { + this.deferred.reject(err); + this.deferred = null; + } + } + async abort() { + this.s.destroy(); + } +} + +/** + * Core tokenizer + */ +class AbstractTokenizer { + constructor(fileInfo) { + /** + * Tokenizer-stream position + */ + this.position = 0; + this.numBuffer = new Uint8Array(8); + this.fileInfo = fileInfo ? fileInfo : {}; + } + /** + * Read a token from the tokenizer-stream + * @param token - The token to read + * @param position - If provided, the desired position in the tokenizer-stream + * @returns Promise with token data + */ + async readToken(token, position = this.position) { + const uint8Array = new Uint8Array(token.len); + const len = await this.readBuffer(uint8Array, { position }); + if (len < token.len) + throw new EndOfStreamError(); + return token.get(uint8Array, 0); + } + /** + * Peek a token from the tokenizer-stream. + * @param token - Token to peek from the tokenizer-stream. + * @param position - Offset where to begin reading within the file. If position is null, data will be read from the current file position. + * @returns Promise with token data + */ + async peekToken(token, position = this.position) { + const uint8Array = new Uint8Array(token.len); + const len = await this.peekBuffer(uint8Array, { position }); + if (len < token.len) + throw new EndOfStreamError(); + return token.get(uint8Array, 0); + } + /** + * Read a numeric token from the stream + * @param token - Numeric token + * @returns Promise with number + */ + async readNumber(token) { + const len = await this.readBuffer(this.numBuffer, { length: token.len }); + if (len < token.len) + throw new EndOfStreamError(); + return token.get(this.numBuffer, 0); + } + /** + * Read a numeric token from the stream + * @param token - Numeric token + * @returns Promise with number + */ + async peekNumber(token) { + const len = await this.peekBuffer(this.numBuffer, { length: token.len }); + if (len < token.len) + throw new EndOfStreamError(); + return token.get(this.numBuffer, 0); + } + /** + * Ignore number of bytes, advances the pointer in under tokenizer-stream. + * @param length - Number of bytes to ignore + * @return resolves the number of bytes ignored, equals length if this available, otherwise the number of bytes available + */ + async ignore(length) { + if (this.fileInfo.size !== undefined) { + const bytesLeft = this.fileInfo.size - this.position; + if (length > bytesLeft) { + this.position += bytesLeft; + return bytesLeft; + } + } + this.position += length; + return length; + } + async close() { + // empty + } + normalizeOptions(uint8Array, options) { + if (options && options.position !== undefined && options.position < this.position) { + throw new Error('`options.position` must be equal or greater than `tokenizer.position`'); + } + if (options) { + return { + mayBeLess: options.mayBeLess === true, + offset: options.offset ? options.offset : 0, + length: options.length ? options.length : (uint8Array.length - (options.offset ? options.offset : 0)), + position: options.position ? options.position : this.position + }; + } + return { + mayBeLess: false, + offset: 0, + length: uint8Array.length, + position: this.position + }; + } +} + +const maxBufferSize = 256000; +class ReadStreamTokenizer extends AbstractTokenizer { + constructor(streamReader, fileInfo) { + super(fileInfo); + this.streamReader = streamReader; + } + /** + * Get file information, an HTTP-client may implement this doing a HEAD request + * @return Promise with file information + */ + async getFileInfo() { + return this.fileInfo; + } + /** + * Read buffer from tokenizer + * @param uint8Array - Target Uint8Array to fill with data read from the tokenizer-stream + * @param options - Read behaviour options + * @returns Promise with number of bytes read + */ + async readBuffer(uint8Array, options) { + const normOptions = this.normalizeOptions(uint8Array, options); + const skipBytes = normOptions.position - this.position; + if (skipBytes > 0) { + await this.ignore(skipBytes); + return this.readBuffer(uint8Array, options); + } + else if (skipBytes < 0) { + throw new Error('`options.position` must be equal or greater than `tokenizer.position`'); + } + if (normOptions.length === 0) { + return 0; + } + const bytesRead = await this.streamReader.read(uint8Array, normOptions.offset, normOptions.length); + this.position += bytesRead; + if ((!options || !options.mayBeLess) && bytesRead < normOptions.length) { + throw new EndOfStreamError(); + } + return bytesRead; + } + /** + * Peek (read ahead) buffer from tokenizer + * @param uint8Array - Uint8Array (or Buffer) to write data to + * @param options - Read behaviour options + * @returns Promise with number of bytes peeked + */ + async peekBuffer(uint8Array, options) { + const normOptions = this.normalizeOptions(uint8Array, options); + let bytesRead = 0; + if (normOptions.position) { + const skipBytes = normOptions.position - this.position; + if (skipBytes > 0) { + const skipBuffer = new Uint8Array(normOptions.length + skipBytes); + bytesRead = await this.peekBuffer(skipBuffer, { mayBeLess: normOptions.mayBeLess }); + uint8Array.set(skipBuffer.subarray(skipBytes), normOptions.offset); + return bytesRead - skipBytes; + } + else if (skipBytes < 0) { + throw new Error('Cannot peek from a negative offset in a stream'); + } + } + if (normOptions.length > 0) { + try { + bytesRead = await this.streamReader.peek(uint8Array, normOptions.offset, normOptions.length); + } + catch (err) { + if (options && options.mayBeLess && err instanceof EndOfStreamError) { + return 0; + } + throw err; + } + if ((!normOptions.mayBeLess) && bytesRead < normOptions.length) { + throw new EndOfStreamError(); + } + } + return bytesRead; + } + async ignore(length) { + // debug(`ignore ${this.position}...${this.position + length - 1}`); + const bufSize = Math.min(maxBufferSize, length); + const buf = new Uint8Array(bufSize); + let totBytesRead = 0; + while (totBytesRead < length) { + const remaining = length - totBytesRead; + const bytesRead = await this.readBuffer(buf, { length: Math.min(bufSize, remaining) }); + if (bytesRead < 0) { + return bytesRead; + } + totBytesRead += bytesRead; + } + return totBytesRead; + } +} + +class BufferTokenizer extends AbstractTokenizer { + /** + * Construct BufferTokenizer + * @param uint8Array - Uint8Array to tokenize + * @param fileInfo - Pass additional file information to the tokenizer + */ + constructor(uint8Array, fileInfo) { + super(fileInfo); + this.uint8Array = uint8Array; + this.fileInfo.size = this.fileInfo.size ? this.fileInfo.size : uint8Array.length; + } + /** + * Read buffer from tokenizer + * @param uint8Array - Uint8Array to tokenize + * @param options - Read behaviour options + * @returns {Promise} + */ + async readBuffer(uint8Array, options) { + if (options && options.position) { + if (options.position < this.position) { + throw new Error('`options.position` must be equal or greater than `tokenizer.position`'); + } + this.position = options.position; + } + const bytesRead = await this.peekBuffer(uint8Array, options); + this.position += bytesRead; + return bytesRead; + } + /** + * Peek (read ahead) buffer from tokenizer + * @param uint8Array + * @param options - Read behaviour options + * @returns {Promise} + */ + async peekBuffer(uint8Array, options) { + const normOptions = this.normalizeOptions(uint8Array, options); + const bytes2read = Math.min(this.uint8Array.length - normOptions.position, normOptions.length); + if ((!normOptions.mayBeLess) && bytes2read < normOptions.length) { + throw new EndOfStreamError(); + } + else { + uint8Array.set(this.uint8Array.subarray(normOptions.position, normOptions.position + bytes2read), normOptions.offset); + return bytes2read; + } + } + async close() { + // empty + } +} + +/** + * Construct ReadStreamTokenizer from given Stream. + * Will set fileSize, if provided given Stream has set the .path property/ + * @param stream - Read from Node.js Stream.Readable + * @param fileInfo - Pass the file information, like size and MIME-type of the corresponding stream. + * @returns ReadStreamTokenizer + */ +function fromStream(stream, fileInfo) { + fileInfo = fileInfo ? fileInfo : {}; + return new ReadStreamTokenizer(new StreamReader(stream), fileInfo); +} +/** + * Construct ReadStreamTokenizer from given Buffer. + * @param uint8Array - Uint8Array to tokenize + * @param fileInfo - Pass additional file information to the tokenizer + * @returns BufferTokenizer + */ +function fromBuffer(uint8Array, fileInfo) { + return new BufferTokenizer(uint8Array, fileInfo); +} + +function stringToBytes(string) { + return [...string].map(character => character.charCodeAt(0)); // eslint-disable-line unicorn/prefer-code-point +} + +/** +Checks whether the TAR checksum is valid. + +@param {Buffer} buffer - The TAR header `[offset ... offset + 512]`. +@param {number} offset - TAR header offset. +@returns {boolean} `true` if the TAR checksum is valid, otherwise `false`. +*/ +function tarHeaderChecksumMatches(buffer, offset = 0) { + const readSum = Number.parseInt(buffer.toString('utf8', 148, 154).replace(/\0.*$/, '').trim(), 8); // Read sum in header + if (Number.isNaN(readSum)) { + return false; + } + + let sum = 8 * 0x20; // Initialize signed bit sum + + for (let index = offset; index < offset + 148; index++) { + sum += buffer[index]; + } + + for (let index = offset + 156; index < offset + 512; index++) { + sum += buffer[index]; + } + + return readSum === sum; +} + +/** +ID3 UINT32 sync-safe tokenizer token. +28 bits (representing up to 256MB) integer, the msb is 0 to avoid "false syncsignals". +*/ +const uint32SyncSafeToken = { + get: (buffer, offset) => (buffer[offset + 3] & 0x7F) | ((buffer[offset + 2]) << 7) | ((buffer[offset + 1]) << 14) | ((buffer[offset]) << 21), + len: 4, +}; + +const extensions = [ + 'jpg', + 'png', + 'apng', + 'gif', + 'webp', + 'flif', + 'xcf', + 'cr2', + 'cr3', + 'orf', + 'arw', + 'dng', + 'nef', + 'rw2', + 'raf', + 'tif', + 'bmp', + 'icns', + 'jxr', + 'psd', + 'indd', + 'zip', + 'tar', + 'rar', + 'gz', + 'bz2', + '7z', + 'dmg', + 'mp4', + 'mid', + 'mkv', + 'webm', + 'mov', + 'avi', + 'mpg', + 'mp2', + 'mp3', + 'm4a', + 'oga', + 'ogg', + 'ogv', + 'opus', + 'flac', + 'wav', + 'spx', + 'amr', + 'pdf', + 'epub', + 'elf', + 'macho', + 'exe', + 'swf', + 'rtf', + 'wasm', + 'woff', + 'woff2', + 'eot', + 'ttf', + 'otf', + 'ico', + 'flv', + 'ps', + 'xz', + 'sqlite', + 'nes', + 'crx', + 'xpi', + 'cab', + 'deb', + 'ar', + 'rpm', + 'Z', + 'lz', + 'cfb', + 'mxf', + 'mts', + 'blend', + 'bpg', + 'docx', + 'pptx', + 'xlsx', + '3gp', + '3g2', + 'j2c', + 'jp2', + 'jpm', + 'jpx', + 'mj2', + 'aif', + 'qcp', + 'odt', + 'ods', + 'odp', + 'xml', + 'mobi', + 'heic', + 'cur', + 'ktx', + 'ape', + 'wv', + 'dcm', + 'ics', + 'glb', + 'pcap', + 'dsf', + 'lnk', + 'alias', + 'voc', + 'ac3', + 'm4v', + 'm4p', + 'm4b', + 'f4v', + 'f4p', + 'f4b', + 'f4a', + 'mie', + 'asf', + 'ogm', + 'ogx', + 'mpc', + 'arrow', + 'shp', + 'aac', + 'mp1', + 'it', + 's3m', + 'xm', + 'ai', + 'skp', + 'avif', + 'eps', + 'lzh', + 'pgp', + 'asar', + 'stl', + 'chm', + '3mf', + 'zst', + 'jxl', + 'vcf', + 'jls', + 'pst', + 'dwg', + 'parquet', + 'class', + 'arj', + 'cpio', + 'ace', + 'avro', + 'icc', + 'fbx', +]; + +const mimeTypes = [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'image/flif', + 'image/x-xcf', + 'image/x-canon-cr2', + 'image/x-canon-cr3', + 'image/tiff', + 'image/bmp', + 'image/vnd.ms-photo', + 'image/vnd.adobe.photoshop', + 'application/x-indesign', + 'application/epub+zip', + 'application/x-xpinstall', + 'application/vnd.oasis.opendocument.text', + 'application/vnd.oasis.opendocument.spreadsheet', + 'application/vnd.oasis.opendocument.presentation', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'application/zip', + 'application/x-tar', + 'application/x-rar-compressed', + 'application/gzip', + 'application/x-bzip2', + 'application/x-7z-compressed', + 'application/x-apple-diskimage', + 'application/x-apache-arrow', + 'video/mp4', + 'audio/midi', + 'video/x-matroska', + 'video/webm', + 'video/quicktime', + 'video/vnd.avi', + 'audio/vnd.wave', + 'audio/qcelp', + 'audio/x-ms-asf', + 'video/x-ms-asf', + 'application/vnd.ms-asf', + 'video/mpeg', + 'video/3gpp', + 'audio/mpeg', + 'audio/mp4', // RFC 4337 + 'audio/opus', + 'video/ogg', + 'audio/ogg', + 'application/ogg', + 'audio/x-flac', + 'audio/ape', + 'audio/wavpack', + 'audio/amr', + 'application/pdf', + 'application/x-elf', + 'application/x-mach-binary', + 'application/x-msdownload', + 'application/x-shockwave-flash', + 'application/rtf', + 'application/wasm', + 'font/woff', + 'font/woff2', + 'application/vnd.ms-fontobject', + 'font/ttf', + 'font/otf', + 'image/x-icon', + 'video/x-flv', + 'application/postscript', + 'application/eps', + 'application/x-xz', + 'application/x-sqlite3', + 'application/x-nintendo-nes-rom', + 'application/x-google-chrome-extension', + 'application/vnd.ms-cab-compressed', + 'application/x-deb', + 'application/x-unix-archive', + 'application/x-rpm', + 'application/x-compress', + 'application/x-lzip', + 'application/x-cfb', + 'application/x-mie', + 'application/mxf', + 'video/mp2t', + 'application/x-blender', + 'image/bpg', + 'image/j2c', + 'image/jp2', + 'image/jpx', + 'image/jpm', + 'image/mj2', + 'audio/aiff', + 'application/xml', + 'application/x-mobipocket-ebook', + 'image/heif', + 'image/heif-sequence', + 'image/heic', + 'image/heic-sequence', + 'image/icns', + 'image/ktx', + 'application/dicom', + 'audio/x-musepack', + 'text/calendar', + 'text/vcard', + 'model/gltf-binary', + 'application/vnd.tcpdump.pcap', + 'audio/x-dsf', // Non-standard + 'application/x.ms.shortcut', // Invented by us + 'application/x.apple.alias', // Invented by us + 'audio/x-voc', + 'audio/vnd.dolby.dd-raw', + 'audio/x-m4a', + 'image/apng', + 'image/x-olympus-orf', + 'image/x-sony-arw', + 'image/x-adobe-dng', + 'image/x-nikon-nef', + 'image/x-panasonic-rw2', + 'image/x-fujifilm-raf', + 'video/x-m4v', + 'video/3gpp2', + 'application/x-esri-shape', + 'audio/aac', + 'audio/x-it', + 'audio/x-s3m', + 'audio/x-xm', + 'video/MP1S', + 'video/MP2P', + 'application/vnd.sketchup.skp', + 'image/avif', + 'application/x-lzh-compressed', + 'application/pgp-encrypted', + 'application/x-asar', + 'model/stl', + 'application/vnd.ms-htmlhelp', + 'model/3mf', + 'image/jxl', + 'application/zstd', + 'image/jls', + 'application/vnd.ms-outlook', + 'image/vnd.dwg', + 'application/x-parquet', + 'application/java-vm', + 'application/x-arj', + 'application/x-cpio', + 'application/x-ace-compressed', + 'application/avro', + 'application/vnd.iccprofile', + 'application/x.autodesk.fbx', // Invented by us +]; + +const minimumBytes = 4100; // A fair amount of file-types are detectable within this range. + +async function fileTypeFromBuffer(input) { + return new FileTypeParser().fromBuffer(input); +} + +function _check(buffer, headers, options) { + options = { + offset: 0, + ...options, + }; + + for (const [index, header] of headers.entries()) { + // If a bitmask is set + if (options.mask) { + // If header doesn't equal `buf` with bits masked off + if (header !== (options.mask[index] & buffer[index + options.offset])) { + return false; + } + } else if (header !== buffer[index + options.offset]) { + return false; + } + } + + return true; +} + +class FileTypeParser { + constructor(options) { + this.detectors = options?.customDetectors; + + this.fromTokenizer = this.fromTokenizer.bind(this); + this.fromBuffer = this.fromBuffer.bind(this); + this.parse = this.parse.bind(this); + } + + async fromTokenizer(tokenizer) { + const initialPosition = tokenizer.position; + + for (const detector of this.detectors || []) { + const fileType = await detector(tokenizer); + if (fileType) { + return fileType; + } + + if (initialPosition !== tokenizer.position) { + return undefined; // Cannot proceed scanning of the tokenizer is at an arbitrary position + } + } + + return this.parse(tokenizer); + } + + async fromBuffer(input) { + if (!(input instanceof Uint8Array || input instanceof ArrayBuffer)) { + throw new TypeError(`Expected the \`input\` argument to be of type \`Uint8Array\` or \`Buffer\` or \`ArrayBuffer\`, got \`${typeof input}\``); + } + + const buffer = input instanceof Uint8Array ? input : new Uint8Array(input); + + if (!(buffer?.length > 1)) { + return; + } + + return this.fromTokenizer(fromBuffer(buffer)); + } + + async fromBlob(blob) { + const buffer = await blob.arrayBuffer(); + return this.fromBuffer(new Uint8Array(buffer)); + } + + async fromStream(stream) { + const tokenizer = await fromStream(stream); + try { + return await this.fromTokenizer(tokenizer); + } finally { + await tokenizer.close(); + } + } + + async toDetectionStream(readableStream, options = {}) { + const {default: stream} = await import('node:stream'); + const {sampleSize = minimumBytes} = options; + + return new Promise((resolve, reject) => { + readableStream.on('error', reject); + + readableStream.once('readable', () => { + (async () => { + try { + // Set up output stream + const pass = new stream.PassThrough(); + const outputStream = stream.pipeline ? stream.pipeline(readableStream, pass, () => {}) : readableStream.pipe(pass); + + // Read the input stream and detect the filetype + const chunk = readableStream.read(sampleSize) ?? readableStream.read() ?? node_buffer.Buffer.alloc(0); + try { + pass.fileType = await this.fromBuffer(chunk); + } catch (error) { + if (error instanceof EndOfStreamError) { + pass.fileType = undefined; + } else { + reject(error); + } + } + + resolve(outputStream); + } catch (error) { + reject(error); + } + })(); + }); + }); + } + + check(header, options) { + return _check(this.buffer, header, options); + } + + checkString(header, options) { + return this.check(stringToBytes(header), options); + } + + async parse(tokenizer) { + this.buffer = node_buffer.Buffer.alloc(minimumBytes); + + // Keep reading until EOF if the file size is unknown. + if (tokenizer.fileInfo.size === undefined) { + tokenizer.fileInfo.size = Number.MAX_SAFE_INTEGER; + } + + this.tokenizer = tokenizer; + + await tokenizer.peekBuffer(this.buffer, {length: 12, mayBeLess: true}); + + // -- 2-byte signatures -- + + if (this.check([0x42, 0x4D])) { + return { + ext: 'bmp', + mime: 'image/bmp', + }; + } + + if (this.check([0x0B, 0x77])) { + return { + ext: 'ac3', + mime: 'audio/vnd.dolby.dd-raw', + }; + } + + if (this.check([0x78, 0x01])) { + return { + ext: 'dmg', + mime: 'application/x-apple-diskimage', + }; + } + + if (this.check([0x4D, 0x5A])) { + return { + ext: 'exe', + mime: 'application/x-msdownload', + }; + } + + if (this.check([0x25, 0x21])) { + await tokenizer.peekBuffer(this.buffer, {length: 24, mayBeLess: true}); + + if ( + this.checkString('PS-Adobe-', {offset: 2}) + && this.checkString(' EPSF-', {offset: 14}) + ) { + return { + ext: 'eps', + mime: 'application/eps', + }; + } + + return { + ext: 'ps', + mime: 'application/postscript', + }; + } + + if ( + this.check([0x1F, 0xA0]) + || this.check([0x1F, 0x9D]) + ) { + return { + ext: 'Z', + mime: 'application/x-compress', + }; + } + + if (this.check([0xC7, 0x71])) { + return { + ext: 'cpio', + mime: 'application/x-cpio', + }; + } + + if (this.check([0x60, 0xEA])) { + return { + ext: 'arj', + mime: 'application/x-arj', + }; + } + + // -- 3-byte signatures -- + + if (this.check([0xEF, 0xBB, 0xBF])) { // UTF-8-BOM + // Strip off UTF-8-BOM + this.tokenizer.ignore(3); + return this.parse(tokenizer); + } + + if (this.check([0x47, 0x49, 0x46])) { + return { + ext: 'gif', + mime: 'image/gif', + }; + } + + if (this.check([0x49, 0x49, 0xBC])) { + return { + ext: 'jxr', + mime: 'image/vnd.ms-photo', + }; + } + + if (this.check([0x1F, 0x8B, 0x8])) { + return { + ext: 'gz', + mime: 'application/gzip', + }; + } + + if (this.check([0x42, 0x5A, 0x68])) { + return { + ext: 'bz2', + mime: 'application/x-bzip2', + }; + } + + if (this.checkString('ID3')) { + await tokenizer.ignore(6); // Skip ID3 header until the header size + const id3HeaderLength = await tokenizer.readToken(uint32SyncSafeToken); + if (tokenizer.position + id3HeaderLength > tokenizer.fileInfo.size) { + // Guess file type based on ID3 header for backward compatibility + return { + ext: 'mp3', + mime: 'audio/mpeg', + }; + } + + await tokenizer.ignore(id3HeaderLength); + return this.fromTokenizer(tokenizer); // Skip ID3 header, recursion + } + + // Musepack, SV7 + if (this.checkString('MP+')) { + return { + ext: 'mpc', + mime: 'audio/x-musepack', + }; + } + + if ( + (this.buffer[0] === 0x43 || this.buffer[0] === 0x46) + && this.check([0x57, 0x53], {offset: 1}) + ) { + return { + ext: 'swf', + mime: 'application/x-shockwave-flash', + }; + } + + // -- 4-byte signatures -- + + // Requires a sample size of 4 bytes + if (this.check([0xFF, 0xD8, 0xFF])) { + if (this.check([0xF7], {offset: 3})) { // JPG7/SOF55, indicating a ISO/IEC 14495 / JPEG-LS file + return { + ext: 'jls', + mime: 'image/jls', + }; + } + + return { + ext: 'jpg', + mime: 'image/jpeg', + }; + } + + if (this.check([0x4F, 0x62, 0x6A, 0x01])) { + return { + ext: 'avro', + mime: 'application/avro', + }; + } + + if (this.checkString('FLIF')) { + return { + ext: 'flif', + mime: 'image/flif', + }; + } + + if (this.checkString('8BPS')) { + return { + ext: 'psd', + mime: 'image/vnd.adobe.photoshop', + }; + } + + if (this.checkString('WEBP', {offset: 8})) { + return { + ext: 'webp', + mime: 'image/webp', + }; + } + + // Musepack, SV8 + if (this.checkString('MPCK')) { + return { + ext: 'mpc', + mime: 'audio/x-musepack', + }; + } + + if (this.checkString('FORM')) { + return { + ext: 'aif', + mime: 'audio/aiff', + }; + } + + if (this.checkString('icns', {offset: 0})) { + return { + ext: 'icns', + mime: 'image/icns', + }; + } + + // Zip-based file formats + // Need to be before the `zip` check + if (this.check([0x50, 0x4B, 0x3, 0x4])) { // Local file header signature + try { + while (tokenizer.position + 30 < tokenizer.fileInfo.size) { + await tokenizer.readBuffer(this.buffer, {length: 30}); + + // https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers + const zipHeader = { + compressedSize: this.buffer.readUInt32LE(18), + uncompressedSize: this.buffer.readUInt32LE(22), + filenameLength: this.buffer.readUInt16LE(26), + extraFieldLength: this.buffer.readUInt16LE(28), + }; + + zipHeader.filename = await tokenizer.readToken(new StringType(zipHeader.filenameLength, 'utf-8')); + await tokenizer.ignore(zipHeader.extraFieldLength); + + // Assumes signed `.xpi` from addons.mozilla.org + if (zipHeader.filename === 'META-INF/mozilla.rsa') { + return { + ext: 'xpi', + mime: 'application/x-xpinstall', + }; + } + + if (zipHeader.filename.endsWith('.rels') || zipHeader.filename.endsWith('.xml')) { + const type = zipHeader.filename.split('/')[0]; + switch (type) { + case '_rels': + break; + case 'word': + return { + ext: 'docx', + mime: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }; + case 'ppt': + return { + ext: 'pptx', + mime: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + }; + case 'xl': + return { + ext: 'xlsx', + mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }; + default: + break; + } + } + + if (zipHeader.filename.startsWith('xl/')) { + return { + ext: 'xlsx', + mime: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + }; + } + + if (zipHeader.filename.startsWith('3D/') && zipHeader.filename.endsWith('.model')) { + return { + ext: '3mf', + mime: 'model/3mf', + }; + } + + // The docx, xlsx and pptx file types extend the Office Open XML file format: + // https://en.wikipedia.org/wiki/Office_Open_XML_file_formats + // We look for: + // - one entry named '[Content_Types].xml' or '_rels/.rels', + // - one entry indicating specific type of file. + // MS Office, OpenOffice and LibreOffice may put the parts in different order, so the check should not rely on it. + if (zipHeader.filename === 'mimetype' && zipHeader.compressedSize === zipHeader.uncompressedSize) { + let mimeType = await tokenizer.readToken(new StringType(zipHeader.compressedSize, 'utf-8')); + mimeType = mimeType.trim(); + + switch (mimeType) { + case 'application/epub+zip': + return { + ext: 'epub', + mime: 'application/epub+zip', + }; + case 'application/vnd.oasis.opendocument.text': + return { + ext: 'odt', + mime: 'application/vnd.oasis.opendocument.text', + }; + case 'application/vnd.oasis.opendocument.spreadsheet': + return { + ext: 'ods', + mime: 'application/vnd.oasis.opendocument.spreadsheet', + }; + case 'application/vnd.oasis.opendocument.presentation': + return { + ext: 'odp', + mime: 'application/vnd.oasis.opendocument.presentation', + }; + default: + } + } + + // Try to find next header manually when current one is corrupted + if (zipHeader.compressedSize === 0) { + let nextHeaderIndex = -1; + + while (nextHeaderIndex < 0 && (tokenizer.position < tokenizer.fileInfo.size)) { + await tokenizer.peekBuffer(this.buffer, {mayBeLess: true}); + + nextHeaderIndex = this.buffer.indexOf('504B0304', 0, 'hex'); + // Move position to the next header if found, skip the whole buffer otherwise + await tokenizer.ignore(nextHeaderIndex >= 0 ? nextHeaderIndex : this.buffer.length); + } + } else { + await tokenizer.ignore(zipHeader.compressedSize); + } + } + } catch (error) { + if (!(error instanceof EndOfStreamError)) { + throw error; + } + } + + return { + ext: 'zip', + mime: 'application/zip', + }; + } + + if (this.checkString('OggS')) { + // This is an OGG container + await tokenizer.ignore(28); + const type = node_buffer.Buffer.alloc(8); + await tokenizer.readBuffer(type); + + // Needs to be before `ogg` check + if (_check(type, [0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64])) { + return { + ext: 'opus', + mime: 'audio/opus', + }; + } + + // If ' theora' in header. + if (_check(type, [0x80, 0x74, 0x68, 0x65, 0x6F, 0x72, 0x61])) { + return { + ext: 'ogv', + mime: 'video/ogg', + }; + } + + // If '\x01video' in header. + if (_check(type, [0x01, 0x76, 0x69, 0x64, 0x65, 0x6F, 0x00])) { + return { + ext: 'ogm', + mime: 'video/ogg', + }; + } + + // If ' FLAC' in header https://xiph.org/flac/faq.html + if (_check(type, [0x7F, 0x46, 0x4C, 0x41, 0x43])) { + return { + ext: 'oga', + mime: 'audio/ogg', + }; + } + + // 'Speex ' in header https://en.wikipedia.org/wiki/Speex + if (_check(type, [0x53, 0x70, 0x65, 0x65, 0x78, 0x20, 0x20])) { + return { + ext: 'spx', + mime: 'audio/ogg', + }; + } + + // If '\x01vorbis' in header + if (_check(type, [0x01, 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73])) { + return { + ext: 'ogg', + mime: 'audio/ogg', + }; + } + + // Default OGG container https://www.iana.org/assignments/media-types/application/ogg + return { + ext: 'ogx', + mime: 'application/ogg', + }; + } + + if ( + this.check([0x50, 0x4B]) + && (this.buffer[2] === 0x3 || this.buffer[2] === 0x5 || this.buffer[2] === 0x7) + && (this.buffer[3] === 0x4 || this.buffer[3] === 0x6 || this.buffer[3] === 0x8) + ) { + return { + ext: 'zip', + mime: 'application/zip', + }; + } + + // + + // File Type Box (https://en.wikipedia.org/wiki/ISO_base_media_file_format) + // It's not required to be first, but it's recommended to be. Almost all ISO base media files start with `ftyp` box. + // `ftyp` box must contain a brand major identifier, which must consist of ISO 8859-1 printable characters. + // Here we check for 8859-1 printable characters (for simplicity, it's a mask which also catches one non-printable character). + if ( + this.checkString('ftyp', {offset: 4}) + && (this.buffer[8] & 0x60) !== 0x00 // Brand major, first character ASCII? + ) { + // They all can have MIME `video/mp4` except `application/mp4` special-case which is hard to detect. + // For some cases, we're specific, everything else falls to `video/mp4` with `mp4` extension. + const brandMajor = this.buffer.toString('binary', 8, 12).replace('\0', ' ').trim(); + switch (brandMajor) { + case 'avif': + case 'avis': + return {ext: 'avif', mime: 'image/avif'}; + case 'mif1': + return {ext: 'heic', mime: 'image/heif'}; + case 'msf1': + return {ext: 'heic', mime: 'image/heif-sequence'}; + case 'heic': + case 'heix': + return {ext: 'heic', mime: 'image/heic'}; + case 'hevc': + case 'hevx': + return {ext: 'heic', mime: 'image/heic-sequence'}; + case 'qt': + return {ext: 'mov', mime: 'video/quicktime'}; + case 'M4V': + case 'M4VH': + case 'M4VP': + return {ext: 'm4v', mime: 'video/x-m4v'}; + case 'M4P': + return {ext: 'm4p', mime: 'video/mp4'}; + case 'M4B': + return {ext: 'm4b', mime: 'audio/mp4'}; + case 'M4A': + return {ext: 'm4a', mime: 'audio/x-m4a'}; + case 'F4V': + return {ext: 'f4v', mime: 'video/mp4'}; + case 'F4P': + return {ext: 'f4p', mime: 'video/mp4'}; + case 'F4A': + return {ext: 'f4a', mime: 'audio/mp4'}; + case 'F4B': + return {ext: 'f4b', mime: 'audio/mp4'}; + case 'crx': + return {ext: 'cr3', mime: 'image/x-canon-cr3'}; + default: + if (brandMajor.startsWith('3g')) { + if (brandMajor.startsWith('3g2')) { + return {ext: '3g2', mime: 'video/3gpp2'}; + } + + return {ext: '3gp', mime: 'video/3gpp'}; + } + + return {ext: 'mp4', mime: 'video/mp4'}; + } + } + + if (this.checkString('MThd')) { + return { + ext: 'mid', + mime: 'audio/midi', + }; + } + + if ( + this.checkString('wOFF') + && ( + this.check([0x00, 0x01, 0x00, 0x00], {offset: 4}) + || this.checkString('OTTO', {offset: 4}) + ) + ) { + return { + ext: 'woff', + mime: 'font/woff', + }; + } + + if ( + this.checkString('wOF2') + && ( + this.check([0x00, 0x01, 0x00, 0x00], {offset: 4}) + || this.checkString('OTTO', {offset: 4}) + ) + ) { + return { + ext: 'woff2', + mime: 'font/woff2', + }; + } + + if (this.check([0xD4, 0xC3, 0xB2, 0xA1]) || this.check([0xA1, 0xB2, 0xC3, 0xD4])) { + return { + ext: 'pcap', + mime: 'application/vnd.tcpdump.pcap', + }; + } + + // Sony DSD Stream File (DSF) + if (this.checkString('DSD ')) { + return { + ext: 'dsf', + mime: 'audio/x-dsf', // Non-standard + }; + } + + if (this.checkString('LZIP')) { + return { + ext: 'lz', + mime: 'application/x-lzip', + }; + } + + if (this.checkString('fLaC')) { + return { + ext: 'flac', + mime: 'audio/x-flac', + }; + } + + if (this.check([0x42, 0x50, 0x47, 0xFB])) { + return { + ext: 'bpg', + mime: 'image/bpg', + }; + } + + if (this.checkString('wvpk')) { + return { + ext: 'wv', + mime: 'audio/wavpack', + }; + } + + if (this.checkString('%PDF')) { + try { + await tokenizer.ignore(1350); + const maxBufferSize = 10 * 1024 * 1024; + const buffer = node_buffer.Buffer.alloc(Math.min(maxBufferSize, tokenizer.fileInfo.size)); + await tokenizer.readBuffer(buffer, {mayBeLess: true}); + + // Check if this is an Adobe Illustrator file + if (buffer.includes(node_buffer.Buffer.from('AIPrivateData'))) { + return { + ext: 'ai', + mime: 'application/postscript', + }; + } + } catch (error) { + // Swallow end of stream error if file is too small for the Adobe AI check + if (!(error instanceof EndOfStreamError)) { + throw error; + } + } + + // Assume this is just a normal PDF + return { + ext: 'pdf', + mime: 'application/pdf', + }; + } + + if (this.check([0x00, 0x61, 0x73, 0x6D])) { + return { + ext: 'wasm', + mime: 'application/wasm', + }; + } + + // TIFF, little-endian type + if (this.check([0x49, 0x49])) { + const fileType = await this.readTiffHeader(false); + if (fileType) { + return fileType; + } + } + + // TIFF, big-endian type + if (this.check([0x4D, 0x4D])) { + const fileType = await this.readTiffHeader(true); + if (fileType) { + return fileType; + } + } + + if (this.checkString('MAC ')) { + return { + ext: 'ape', + mime: 'audio/ape', + }; + } + + // https://github.com/file/file/blob/master/magic/Magdir/matroska + if (this.check([0x1A, 0x45, 0xDF, 0xA3])) { // Root element: EBML + async function readField() { + const msb = await tokenizer.peekNumber(UINT8); + let mask = 0x80; + let ic = 0; // 0 = A, 1 = B, 2 = C, 3 + // = D + + while ((msb & mask) === 0 && mask !== 0) { + ++ic; + mask >>= 1; + } + + const id = node_buffer.Buffer.alloc(ic + 1); + await tokenizer.readBuffer(id); + return id; + } + + async function readElement() { + const id = await readField(); + const lengthField = await readField(); + lengthField[0] ^= 0x80 >> (lengthField.length - 1); + const nrLength = Math.min(6, lengthField.length); // JavaScript can max read 6 bytes integer + return { + id: id.readUIntBE(0, id.length), + len: lengthField.readUIntBE(lengthField.length - nrLength, nrLength), + }; + } + + async function readChildren(children) { + while (children > 0) { + const element = await readElement(); + if (element.id === 0x42_82) { + const rawValue = await tokenizer.readToken(new StringType(element.len, 'utf-8')); + return rawValue.replace(/\00.*$/g, ''); // Return DocType + } + + await tokenizer.ignore(element.len); // ignore payload + --children; + } + } + + const re = await readElement(); + const docType = await readChildren(re.len); + + switch (docType) { + case 'webm': + return { + ext: 'webm', + mime: 'video/webm', + }; + + case 'matroska': + return { + ext: 'mkv', + mime: 'video/x-matroska', + }; + + default: + return; + } + } + + // RIFF file format which might be AVI, WAV, QCP, etc + if (this.check([0x52, 0x49, 0x46, 0x46])) { + if (this.check([0x41, 0x56, 0x49], {offset: 8})) { + return { + ext: 'avi', + mime: 'video/vnd.avi', + }; + } + + if (this.check([0x57, 0x41, 0x56, 0x45], {offset: 8})) { + return { + ext: 'wav', + mime: 'audio/vnd.wave', + }; + } + + // QLCM, QCP file + if (this.check([0x51, 0x4C, 0x43, 0x4D], {offset: 8})) { + return { + ext: 'qcp', + mime: 'audio/qcelp', + }; + } + } + + if (this.checkString('SQLi')) { + return { + ext: 'sqlite', + mime: 'application/x-sqlite3', + }; + } + + if (this.check([0x4E, 0x45, 0x53, 0x1A])) { + return { + ext: 'nes', + mime: 'application/x-nintendo-nes-rom', + }; + } + + if (this.checkString('Cr24')) { + return { + ext: 'crx', + mime: 'application/x-google-chrome-extension', + }; + } + + if ( + this.checkString('MSCF') + || this.checkString('ISc(') + ) { + return { + ext: 'cab', + mime: 'application/vnd.ms-cab-compressed', + }; + } + + if (this.check([0xED, 0xAB, 0xEE, 0xDB])) { + return { + ext: 'rpm', + mime: 'application/x-rpm', + }; + } + + if (this.check([0xC5, 0xD0, 0xD3, 0xC6])) { + return { + ext: 'eps', + mime: 'application/eps', + }; + } + + if (this.check([0x28, 0xB5, 0x2F, 0xFD])) { + return { + ext: 'zst', + mime: 'application/zstd', + }; + } + + if (this.check([0x7F, 0x45, 0x4C, 0x46])) { + return { + ext: 'elf', + mime: 'application/x-elf', + }; + } + + if (this.check([0x21, 0x42, 0x44, 0x4E])) { + return { + ext: 'pst', + mime: 'application/vnd.ms-outlook', + }; + } + + if (this.checkString('PAR1')) { + return { + ext: 'parquet', + mime: 'application/x-parquet', + }; + } + + if (this.check([0xCF, 0xFA, 0xED, 0xFE])) { + return { + ext: 'macho', + mime: 'application/x-mach-binary', + }; + } + + // -- 5-byte signatures -- + + if (this.check([0x4F, 0x54, 0x54, 0x4F, 0x00])) { + return { + ext: 'otf', + mime: 'font/otf', + }; + } + + if (this.checkString('#!AMR')) { + return { + ext: 'amr', + mime: 'audio/amr', + }; + } + + if (this.checkString('{\\rtf')) { + return { + ext: 'rtf', + mime: 'application/rtf', + }; + } + + if (this.check([0x46, 0x4C, 0x56, 0x01])) { + return { + ext: 'flv', + mime: 'video/x-flv', + }; + } + + if (this.checkString('IMPM')) { + return { + ext: 'it', + mime: 'audio/x-it', + }; + } + + if ( + this.checkString('-lh0-', {offset: 2}) + || this.checkString('-lh1-', {offset: 2}) + || this.checkString('-lh2-', {offset: 2}) + || this.checkString('-lh3-', {offset: 2}) + || this.checkString('-lh4-', {offset: 2}) + || this.checkString('-lh5-', {offset: 2}) + || this.checkString('-lh6-', {offset: 2}) + || this.checkString('-lh7-', {offset: 2}) + || this.checkString('-lzs-', {offset: 2}) + || this.checkString('-lz4-', {offset: 2}) + || this.checkString('-lz5-', {offset: 2}) + || this.checkString('-lhd-', {offset: 2}) + ) { + return { + ext: 'lzh', + mime: 'application/x-lzh-compressed', + }; + } + + // MPEG program stream (PS or MPEG-PS) + if (this.check([0x00, 0x00, 0x01, 0xBA])) { + // MPEG-PS, MPEG-1 Part 1 + if (this.check([0x21], {offset: 4, mask: [0xF1]})) { + return { + ext: 'mpg', // May also be .ps, .mpeg + mime: 'video/MP1S', + }; + } + + // MPEG-PS, MPEG-2 Part 1 + if (this.check([0x44], {offset: 4, mask: [0xC4]})) { + return { + ext: 'mpg', // May also be .mpg, .m2p, .vob or .sub + mime: 'video/MP2P', + }; + } + } + + if (this.checkString('ITSF')) { + return { + ext: 'chm', + mime: 'application/vnd.ms-htmlhelp', + }; + } + + if (this.check([0xCA, 0xFE, 0xBA, 0xBE])) { + return { + ext: 'class', + mime: 'application/java-vm', + }; + } + + // -- 6-byte signatures -- + + if (this.check([0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00])) { + return { + ext: 'xz', + mime: 'application/x-xz', + }; + } + + if (this.checkString('= 1000 && version <= 1050) { + return { + ext: 'dwg', + mime: 'image/vnd.dwg', + }; + } + } + + if (this.checkString('070707')) { + return { + ext: 'cpio', + mime: 'application/x-cpio', + }; + } + + // -- 7-byte signatures -- + + if (this.checkString('BLENDER')) { + return { + ext: 'blend', + mime: 'application/x-blender', + }; + } + + if (this.checkString('!')) { + await tokenizer.ignore(8); + const string = await tokenizer.readToken(new StringType(13, 'ascii')); + if (string === 'debian-binary') { + return { + ext: 'deb', + mime: 'application/x-deb', + }; + } + + return { + ext: 'ar', + mime: 'application/x-unix-archive', + }; + } + + if (this.checkString('**ACE', {offset: 7})) { + await tokenizer.peekBuffer(this.buffer, {length: 14, mayBeLess: true}); + if (this.checkString('**', {offset: 12})) { + return { + ext: 'ace', + mime: 'application/x-ace-compressed', + }; + } + } + + // -- 8-byte signatures -- + + if (this.check([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])) { + // APNG format (https://wiki.mozilla.org/APNG_Specification) + // 1. Find the first IDAT (image data) chunk (49 44 41 54) + // 2. Check if there is an "acTL" chunk before the IDAT one (61 63 54 4C) + + // Offset calculated as follows: + // - 8 bytes: PNG signature + // - 4 (length) + 4 (chunk type) + 13 (chunk data) + 4 (CRC): IHDR chunk + + await tokenizer.ignore(8); // ignore PNG signature + + async function readChunkHeader() { + return { + length: await tokenizer.readToken(INT32_BE), + type: await tokenizer.readToken(new StringType(4, 'binary')), + }; + } + + do { + const chunk = await readChunkHeader(); + if (chunk.length < 0) { + return; // Invalid chunk length + } + + switch (chunk.type) { + case 'IDAT': + return { + ext: 'png', + mime: 'image/png', + }; + case 'acTL': + return { + ext: 'apng', + mime: 'image/apng', + }; + default: + await tokenizer.ignore(chunk.length + 4); // Ignore chunk-data + CRC + } + } while (tokenizer.position + 8 < tokenizer.fileInfo.size); + + return { + ext: 'png', + mime: 'image/png', + }; + } + + if (this.check([0x41, 0x52, 0x52, 0x4F, 0x57, 0x31, 0x00, 0x00])) { + return { + ext: 'arrow', + mime: 'application/x-apache-arrow', + }; + } + + if (this.check([0x67, 0x6C, 0x54, 0x46, 0x02, 0x00, 0x00, 0x00])) { + return { + ext: 'glb', + mime: 'model/gltf-binary', + }; + } + + // `mov` format variants + if ( + this.check([0x66, 0x72, 0x65, 0x65], {offset: 4}) // `free` + || this.check([0x6D, 0x64, 0x61, 0x74], {offset: 4}) // `mdat` MJPEG + || this.check([0x6D, 0x6F, 0x6F, 0x76], {offset: 4}) // `moov` + || this.check([0x77, 0x69, 0x64, 0x65], {offset: 4}) // `wide` + ) { + return { + ext: 'mov', + mime: 'video/quicktime', + }; + } + + // -- 9-byte signatures -- + + if (this.check([0x49, 0x49, 0x52, 0x4F, 0x08, 0x00, 0x00, 0x00, 0x18])) { + return { + ext: 'orf', + mime: 'image/x-olympus-orf', + }; + } + + if (this.checkString('gimp xcf ')) { + return { + ext: 'xcf', + mime: 'image/x-xcf', + }; + } + + // -- 12-byte signatures -- + + if (this.check([0x49, 0x49, 0x55, 0x00, 0x18, 0x00, 0x00, 0x00, 0x88, 0xE7, 0x74, 0xD8])) { + return { + ext: 'rw2', + mime: 'image/x-panasonic-rw2', + }; + } + + // ASF_Header_Object first 80 bytes + if (this.check([0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11, 0xA6, 0xD9])) { + async function readHeader() { + const guid = node_buffer.Buffer.alloc(16); + await tokenizer.readBuffer(guid); + return { + id: guid, + size: Number(await tokenizer.readToken(UINT64_LE)), + }; + } + + await tokenizer.ignore(30); + // Search for header should be in first 1KB of file. + while (tokenizer.position + 24 < tokenizer.fileInfo.size) { + const header = await readHeader(); + let payload = header.size - 24; + if (_check(header.id, [0x91, 0x07, 0xDC, 0xB7, 0xB7, 0xA9, 0xCF, 0x11, 0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65])) { + // Sync on Stream-Properties-Object (B7DC0791-A9B7-11CF-8EE6-00C00C205365) + const typeId = node_buffer.Buffer.alloc(16); + payload -= await tokenizer.readBuffer(typeId); + + if (_check(typeId, [0x40, 0x9E, 0x69, 0xF8, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) { + // Found audio: + return { + ext: 'asf', + mime: 'audio/x-ms-asf', + }; + } + + if (_check(typeId, [0xC0, 0xEF, 0x19, 0xBC, 0x4D, 0x5B, 0xCF, 0x11, 0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B])) { + // Found video: + return { + ext: 'asf', + mime: 'video/x-ms-asf', + }; + } + + break; + } + + await tokenizer.ignore(payload); + } + + // Default to ASF generic extension + return { + ext: 'asf', + mime: 'application/vnd.ms-asf', + }; + } + + if (this.check([0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A])) { + return { + ext: 'ktx', + mime: 'image/ktx', + }; + } + + if ((this.check([0x7E, 0x10, 0x04]) || this.check([0x7E, 0x18, 0x04])) && this.check([0x30, 0x4D, 0x49, 0x45], {offset: 4})) { + return { + ext: 'mie', + mime: 'application/x-mie', + }; + } + + if (this.check([0x27, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], {offset: 2})) { + return { + ext: 'shp', + mime: 'application/x-esri-shape', + }; + } + + if (this.check([0xFF, 0x4F, 0xFF, 0x51])) { + return { + ext: 'j2c', + mime: 'image/j2c', + }; + } + + if (this.check([0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A])) { + // JPEG-2000 family + + await tokenizer.ignore(20); + const type = await tokenizer.readToken(new StringType(4, 'ascii')); + switch (type) { + case 'jp2 ': + return { + ext: 'jp2', + mime: 'image/jp2', + }; + case 'jpx ': + return { + ext: 'jpx', + mime: 'image/jpx', + }; + case 'jpm ': + return { + ext: 'jpm', + mime: 'image/jpm', + }; + case 'mjp2': + return { + ext: 'mj2', + mime: 'image/mj2', + }; + default: + return; + } + } + + if ( + this.check([0xFF, 0x0A]) + || this.check([0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A]) + ) { + return { + ext: 'jxl', + mime: 'image/jxl', + }; + } + + if (this.check([0xFE, 0xFF])) { // UTF-16-BOM-LE + if (this.check([0, 60, 0, 63, 0, 120, 0, 109, 0, 108], {offset: 2})) { + return { + ext: 'xml', + mime: 'application/xml', + }; + } + + return undefined; // Some unknown text based format + } + + // -- Unsafe signatures -- + + if ( + this.check([0x0, 0x0, 0x1, 0xBA]) + || this.check([0x0, 0x0, 0x1, 0xB3]) + ) { + return { + ext: 'mpg', + mime: 'video/mpeg', + }; + } + + if (this.check([0x00, 0x01, 0x00, 0x00, 0x00])) { + return { + ext: 'ttf', + mime: 'font/ttf', + }; + } + + if (this.check([0x00, 0x00, 0x01, 0x00])) { + return { + ext: 'ico', + mime: 'image/x-icon', + }; + } + + if (this.check([0x00, 0x00, 0x02, 0x00])) { + return { + ext: 'cur', + mime: 'image/x-icon', + }; + } + + if (this.check([0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1])) { + // Detected Microsoft Compound File Binary File (MS-CFB) Format. + return { + ext: 'cfb', + mime: 'application/x-cfb', + }; + } + + // Increase sample size from 12 to 256. + await tokenizer.peekBuffer(this.buffer, {length: Math.min(256, tokenizer.fileInfo.size), mayBeLess: true}); + + if (this.check([0x61, 0x63, 0x73, 0x70], {offset: 36})) { + return { + ext: 'icc', + mime: 'application/vnd.iccprofile', + }; + } + + // -- 15-byte signatures -- + + if (this.checkString('BEGIN:')) { + if (this.checkString('VCARD', {offset: 6})) { + return { + ext: 'vcf', + mime: 'text/vcard', + }; + } + + if (this.checkString('VCALENDAR', {offset: 6})) { + return { + ext: 'ics', + mime: 'text/calendar', + }; + } + } + + // `raf` is here just to keep all the raw image detectors together. + if (this.checkString('FUJIFILMCCD-RAW')) { + return { + ext: 'raf', + mime: 'image/x-fujifilm-raf', + }; + } + + if (this.checkString('Extended Module:')) { + return { + ext: 'xm', + mime: 'audio/x-xm', + }; + } + + if (this.checkString('Creative Voice File')) { + return { + ext: 'voc', + mime: 'audio/x-voc', + }; + } + + if (this.check([0x04, 0x00, 0x00, 0x00]) && this.buffer.length >= 16) { // Rough & quick check Pickle/ASAR + const jsonSize = this.buffer.readUInt32LE(12); + if (jsonSize > 12 && this.buffer.length >= jsonSize + 16) { + try { + const header = this.buffer.slice(16, jsonSize + 16).toString(); + const json = JSON.parse(header); + // Check if Pickle is ASAR + if (json.files) { // Final check, assuring Pickle/ASAR format + return { + ext: 'asar', + mime: 'application/x-asar', + }; + } + } catch {} + } + } + + if (this.check([0x06, 0x0E, 0x2B, 0x34, 0x02, 0x05, 0x01, 0x01, 0x0D, 0x01, 0x02, 0x01, 0x01, 0x02])) { + return { + ext: 'mxf', + mime: 'application/mxf', + }; + } + + if (this.checkString('SCRM', {offset: 44})) { + return { + ext: 's3m', + mime: 'audio/x-s3m', + }; + } + + // Raw MPEG-2 transport stream (188-byte packets) + if (this.check([0x47]) && this.check([0x47], {offset: 188})) { + return { + ext: 'mts', + mime: 'video/mp2t', + }; + } + + // Blu-ray Disc Audio-Video (BDAV) MPEG-2 transport stream has 4-byte TP_extra_header before each 188-byte packet + if (this.check([0x47], {offset: 4}) && this.check([0x47], {offset: 196})) { + return { + ext: 'mts', + mime: 'video/mp2t', + }; + } + + if (this.check([0x42, 0x4F, 0x4F, 0x4B, 0x4D, 0x4F, 0x42, 0x49], {offset: 60})) { + return { + ext: 'mobi', + mime: 'application/x-mobipocket-ebook', + }; + } + + if (this.check([0x44, 0x49, 0x43, 0x4D], {offset: 128})) { + return { + ext: 'dcm', + mime: 'application/dicom', + }; + } + + if (this.check([0x4C, 0x00, 0x00, 0x00, 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46])) { + return { + ext: 'lnk', + mime: 'application/x.ms.shortcut', // Invented by us + }; + } + + if (this.check([0x62, 0x6F, 0x6F, 0x6B, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x72, 0x6B, 0x00, 0x00, 0x00, 0x00])) { + return { + ext: 'alias', + mime: 'application/x.apple.alias', // Invented by us + }; + } + + if (this.checkString('Kaydara FBX Binary \u0000')) { + return { + ext: 'fbx', + mime: 'application/x.autodesk.fbx', // Invented by us + }; + } + + if ( + this.check([0x4C, 0x50], {offset: 34}) + && ( + this.check([0x00, 0x00, 0x01], {offset: 8}) + || this.check([0x01, 0x00, 0x02], {offset: 8}) + || this.check([0x02, 0x00, 0x02], {offset: 8}) + ) + ) { + return { + ext: 'eot', + mime: 'application/vnd.ms-fontobject', + }; + } + + if (this.check([0x06, 0x06, 0xED, 0xF5, 0xD8, 0x1D, 0x46, 0xE5, 0xBD, 0x31, 0xEF, 0xE7, 0xFE, 0x74, 0xB7, 0x1D])) { + return { + ext: 'indd', + mime: 'application/x-indesign', + }; + } + + // Increase sample size from 256 to 512 + await tokenizer.peekBuffer(this.buffer, {length: Math.min(512, tokenizer.fileInfo.size), mayBeLess: true}); + + // Requires a buffer size of 512 bytes + if (tarHeaderChecksumMatches(this.buffer)) { + return { + ext: 'tar', + mime: 'application/x-tar', + }; + } + + if (this.check([0xFF, 0xFE])) { // UTF-16-BOM-BE + if (this.check([60, 0, 63, 0, 120, 0, 109, 0, 108, 0], {offset: 2})) { + return { + ext: 'xml', + mime: 'application/xml', + }; + } + + if (this.check([0xFF, 0x0E, 0x53, 0x00, 0x6B, 0x00, 0x65, 0x00, 0x74, 0x00, 0x63, 0x00, 0x68, 0x00, 0x55, 0x00, 0x70, 0x00, 0x20, 0x00, 0x4D, 0x00, 0x6F, 0x00, 0x64, 0x00, 0x65, 0x00, 0x6C, 0x00], {offset: 2})) { + return { + ext: 'skp', + mime: 'application/vnd.sketchup.skp', + }; + } + + return undefined; // Some text based format + } + + if (this.checkString('-----BEGIN PGP MESSAGE-----')) { + return { + ext: 'pgp', + mime: 'application/pgp-encrypted', + }; + } + + // Check MPEG 1 or 2 Layer 3 header, or 'layer 0' for ADTS (MPEG sync-word 0xFFE) + if (this.buffer.length >= 2 && this.check([0xFF, 0xE0], {offset: 0, mask: [0xFF, 0xE0]})) { + if (this.check([0x10], {offset: 1, mask: [0x16]})) { + // Check for (ADTS) MPEG-2 + if (this.check([0x08], {offset: 1, mask: [0x08]})) { + return { + ext: 'aac', + mime: 'audio/aac', + }; + } + + // Must be (ADTS) MPEG-4 + return { + ext: 'aac', + mime: 'audio/aac', + }; + } + + // MPEG 1 or 2 Layer 3 header + // Check for MPEG layer 3 + if (this.check([0x02], {offset: 1, mask: [0x06]})) { + return { + ext: 'mp3', + mime: 'audio/mpeg', + }; + } + + // Check for MPEG layer 2 + if (this.check([0x04], {offset: 1, mask: [0x06]})) { + return { + ext: 'mp2', + mime: 'audio/mpeg', + }; + } + + // Check for MPEG layer 1 + if (this.check([0x06], {offset: 1, mask: [0x06]})) { + return { + ext: 'mp1', + mime: 'audio/mpeg', + }; + } + } + } + + async readTiffTag(bigEndian) { + const tagId = await this.tokenizer.readToken(bigEndian ? UINT16_BE : UINT16_LE); + this.tokenizer.ignore(10); + switch (tagId) { + case 50_341: + return { + ext: 'arw', + mime: 'image/x-sony-arw', + }; + case 50_706: + return { + ext: 'dng', + mime: 'image/x-adobe-dng', + }; + } + } + + async readTiffIFD(bigEndian) { + const numberOfTags = await this.tokenizer.readToken(bigEndian ? UINT16_BE : UINT16_LE); + for (let n = 0; n < numberOfTags; ++n) { + const fileType = await this.readTiffTag(bigEndian); + if (fileType) { + return fileType; + } + } + } + + async readTiffHeader(bigEndian) { + const version = (bigEndian ? UINT16_BE : UINT16_LE).get(this.buffer, 2); + const ifdOffset = (bigEndian ? UINT32_BE : UINT32_LE).get(this.buffer, 4); + + if (version === 42) { + // TIFF file header + if (ifdOffset >= 6) { + if (this.checkString('CR', {offset: 8})) { + return { + ext: 'cr2', + mime: 'image/x-canon-cr2', + }; + } + + if (ifdOffset >= 8 && (this.check([0x1C, 0x00, 0xFE, 0x00], {offset: 8}) || this.check([0x1F, 0x00, 0x0B, 0x00], {offset: 8}))) { + return { + ext: 'nef', + mime: 'image/x-nikon-nef', + }; + } + } + + await this.tokenizer.ignore(ifdOffset); + const fileType = await this.readTiffIFD(bigEndian); + return fileType ?? { + ext: 'tif', + mime: 'image/tiff', + }; + } + + if (version === 43) { // Big TIFF file header + return { + ext: 'tif', + mime: 'image/tiff', + }; + } + } +} + +new Set(extensions); +new Set(mimeTypes); + +const imageExtensions = new Set([ + 'jpg', + 'png', + 'gif', + 'webp', + 'flif', + 'cr2', + 'tif', + 'bmp', + 'jxr', + 'psd', + 'ico', + 'bpg', + 'jp2', + 'jpm', + 'jpx', + 'heic', + 'cur', + 'dcm', + 'avif', +]); + +async function imageType(input) { + const result = await fileTypeFromBuffer(input); + return imageExtensions.has(result?.ext) && result; +} + +// العربية +var ar = {}; + +// čeština +var cz = {}; + +// Dansk +var da = {}; + +// Deutsch +var de = {}; + +// English +var en = { + // setting.ts + "Plugin Settings": "Plugin Settings", + "Auto pasted upload": "Auto pasted upload", + "If you set this value true, when you paste image, it will be auto uploaded(you should set the picGo server rightly)": "If you set this value true, when you paste image, it will be auto uploaded(you should set the picGo server rightly)", + "Default uploader": "Default uploader", + "PicGo server": "PicGo server upload route", + "PicGo server desc": "upload route, use PicList will be able to set picbed and config through query", + "Please input PicGo server": "Please input upload route", + "PicGo delete server": "PicGo server delete route(you need to use PicList app)", + "PicList desc": "Search PicList on Github to download and install", + "Please input PicGo delete server": "Please input delete server", + "Delete image using PicList": "Delete image using PicList", + "PicGo-Core path": "PicGo-Core path", + "Delete successfully": "Delete successfully", + "Delete failed": "Delete failed", + "Image size suffix": "Image size suffix", + "Image size suffix Description": "like |300 for resize image in ob.", + "Please input image size suffix": "Please input image size suffix", + "Error, could not delete": "Error, could not delete", + "Please input PicGo-Core path, default using environment variables": "Please input PicGo-Core path, default using environment variables", + "Work on network": "Work on network", + "Work on network Description": "Allow upload network image by 'Upload all' command.\n Or when you paste, md standard image link in your clipboard will be auto upload.", + fixPath: "fixPath", + fixPathWarning: "This option is used to fix PicGo-core upload failures on Linux and Mac. It modifies the PATH variable within Obsidian. If Obsidian encounters any bugs, turn off the option, try again! ", + "Upload when clipboard has image and text together": "Upload when clipboard has image and text together", + "When you copy, some application like Excel will image and text to clipboard, you can upload or not.": "When you copy, some application like Excel will image and text to clipboard, you can upload or not.", + "Network Domain Black List": "Network Domain Black List", + "Network Domain Black List Description": "Image in the domain list will not be upload,use comma separated", + "Delete source file after you upload file": "Delete source file after you upload file", + "Delete source file in ob assets after you upload file.": "Delete source file in ob assets after you upload file.", + "Image desc": "Image desc", + reserve: "default", + "remove all": "none", + "remove default": "remove image.png", + "Remote server mode": "Remote server mode", + "Remote server mode desc": "If you have deployed piclist-core or piclist on the server.", + "Can not find image file": "Can not find image file", + "File has been changedd, upload failure": "File has been changedd, upload failure", + "File has been changedd, download failure": "File has been changedd, download failure", + "Warning: upload files is different of reciver files from api": "Warning: upload files num is different of reciver files from api", +}; + +// British English +var enGB = {}; + +// Español +var es = {}; + +// français +var fr = {}; + +// हिन्दी +var hi = {}; + +// Bahasa Indonesia +var id = {}; + +// Italiano +var it = {}; + +// 日本語 +var ja = {}; + +// 한국어 +var ko = {}; + +// Nederlands +var nl = {}; + +// Norsk +var no = {}; + +// język polski +var pl = {}; + +// Português +var pt = {}; + +// Português do Brasil +// Brazilian Portuguese +var ptBR = {}; + +// Română +var ro = {}; + +// русский +var ru = {}; + +// Türkçe +var tr = {}; + +// 简体中文 +var zhCN = { + // setting.ts + "Plugin Settings": "插件设置", + "Auto pasted upload": "剪切板自动上传", + "If you set this value true, when you paste image, it will be auto uploaded(you should set the picGo server rightly)": "启用该选项后,黏贴图片时会自动上传(你需要正确配置picgo)", + "Default uploader": "默认上传器", + "PicGo server": "PicGo server 上传接口", + "PicGo server desc": "上传接口,使用PicList时可通过设置URL参数指定图床和配置", + "Please input PicGo server": "请输入上传接口地址", + "PicGo delete server": "PicGo server 删除接口(请使用PicList来启用此功能)", + "PicList desc": "PicList是PicGo二次开发版,请Github搜索PicList下载", + "Please input PicGo delete server": "请输入删除接口地址", + "Delete image using PicList": "使用 PicList 删除图片", + "PicGo-Core path": "PicGo-Core 路径", + "Delete successfully": "删除成功", + "Delete failed": "删除失败", + "Error, could not delete": "错误,无法删除", + "Image size suffix": "图片大小后缀", + "Image size suffix Description": "比如:|300 用于调整图片大小", + "Please input image size suffix": "请输入图片大小后缀", + "Please input PicGo-Core path, default using environment variables": "请输入 PicGo-Core path,默认使用环境变量", + "Work on network": "应用网络图片", + "Work on network Description": "当你上传所有图片时,也会上传网络图片。以及当你进行黏贴时,剪切板中的标准 md 图片会被上传", + fixPath: "修正PATH变量", + fixPathWarning: "此选项用于修复Linux和Mac上 PicGo-Core 上传失败的问题。它会修改 Obsidian 内的 PATH 变量,如果 Obsidian 遇到任何BUG,先关闭这个选项试试!", + "Upload when clipboard has image and text together": "当剪切板同时拥有文本和图片剪切板数据时是否上传图片", + "When you copy, some application like Excel will image and text to clipboard, you can upload or not.": "当你复制时,某些应用例如 Excel 会在剪切板同时文本和图像数据,确认是否上传。", + "Network Domain Black List": "网络图片域名黑名单", + "Network Domain Black List Description": "黑名单域名中的图片将不会被上传,用英文逗号分割", + "Delete source file after you upload file": "上传文件后移除源文件", + "Delete source file in ob assets after you upload file.": "上传文件后移除在ob附件文件夹中的文件", + "Image desc": "图片描述", + reserve: "默认", + "remove all": "无", + "remove default": "移除image.png", + "Remote server mode": "远程服务器模式", + "Remote server mode desc": "如果你在服务器部署了piclist-core或者piclist", + "Can not find image file": "没有解析到图像文件", + "File has been changedd, upload failure": "当前文件已变更,上传失败", + "File has been changedd, download failure": "当前文件已变更,下载失败", + "Warning: upload files is different of reciver files from api": "警告:上传的文件与接口返回的文件数量不一致", +}; + +// 繁體中文 +var zhTW = {}; + +const localeMap = { + ar, + cs: cz, + da, + de, + en, + 'en-gb': enGB, + es, + fr, + hi, + id, + it, + ja, + ko, + nl, + nn: no, + pl, + pt, + 'pt-br': ptBR, + ro, + ru, + tr, + 'zh-cn': zhCN, + 'zh-tw': zhTW, +}; +const locale = localeMap[obsidian.moment.locale()]; +function t(str) { + return (locale && locale[str]) || en[str]; +} + +async function downloadAllImageFiles(plugin) { + const activeFile = plugin.app.workspace.getActiveFile(); + const folderPath = await plugin.app.fileManager.getAvailablePathForAttachment(""); + const fileArray = plugin.helper.getAllFiles(); + if (!(await plugin.app.vault.adapter.exists(folderPath))) { + await plugin.app.vault.adapter.mkdir(folderPath); + } + let imageArray = []; + for (const file of fileArray) { + if (!file.path.startsWith("http")) { + continue; + } + const url = file.path; + const asset = getUrlAsset(url); + let name = decodeURI(pathBrowserify.parse(asset).name).replaceAll(/[\\\\/:*?\"<>|]/g, "-"); + const response = await download(plugin, url, folderPath, name); + if (response.ok) { + const activeFolder = plugin.app.workspace.getActiveFile().parent.path; + imageArray.push({ + source: file.source, + name: name, + path: obsidian.normalizePath(pathBrowserify.relative(obsidian.normalizePath(activeFolder), obsidian.normalizePath(response.path))), + }); + } + } + let value = plugin.helper.getValue(); + imageArray.map(image => { + let name = plugin.handleName(image.name); + value = value.replace(image.source, `![${name}](${encodeURI(image.path)})`); + }); + const currentFile = plugin.app.workspace.getActiveFile(); + if (activeFile.path !== currentFile.path) { + new obsidian.Notice(t("File has been changedd, download failure")); + return; + } + plugin.helper.setValue(value); + new obsidian.Notice(`all: ${fileArray.length}\nsuccess: ${imageArray.length}\nfailed: ${fileArray.length - imageArray.length}`); +} +async function download(plugin, url, folderPath, name) { + const response = await obsidian.requestUrl({ url }); + if (response.status !== 200) { + return { + ok: false, + msg: "error", + }; + } + const type = await imageType(new Uint8Array(response.arrayBuffer)); + if (!type) { + return { + ok: false, + msg: "error", + }; + } + try { + let path = obsidian.normalizePath(pathBrowserify.join(folderPath, `${name}.${type.ext}`)); + // 如果文件名已存在,则用随机值替换,不对文件后缀进行判断 + if (await plugin.app.vault.adapter.exists(path)) { + path = obsidian.normalizePath(pathBrowserify.join(folderPath, `${uuid()}.${type.ext}`)); + } + plugin.app.vault.adapter.writeBinary(path, response.arrayBuffer); + return { + ok: true, + msg: "ok", + path: path, + type, + }; + } + catch (err) { + return { + ok: false, + msg: err, + }; + } +} + +class PicGoUploader { + settings; + plugin; + constructor(settings, plugin) { + this.settings = settings; + this.plugin = plugin; + } + async uploadFiles(fileList) { + let response; + let data; + if (this.settings.remoteServerMode) { + const files = []; + for (let i = 0; i < fileList.length; i++) { + const file = fileList[i]; + const buffer = await new Promise((resolve, reject) => { + require$$0.readFile(file, (err, data) => { + if (err) { + reject(err); + } + resolve(data); + }); + }); + const arrayBuffer = bufferToArrayBuffer(buffer); + files.push(new File([arrayBuffer], file)); + } + response = await this.uploadFileByData(files); + data = await response.json(); + } + else { + response = await obsidian.requestUrl({ + url: this.settings.uploadServer, + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ list: fileList }), + }); + data = await response.json; + } + // piclist + if (data.fullResult) { + const uploadUrlFullResultList = data.fullResult || []; + this.settings.uploadedImages = [ + ...(this.settings.uploadedImages || []), + ...uploadUrlFullResultList, + ]; + } + return data; + } + async uploadFileByData(fileList) { + const form = new FormData(); + for (let i = 0; i < fileList.length; i++) { + form.append("list", fileList[i]); + } + const options = { + method: "post", + body: form, + }; + const response = await fetch(this.settings.uploadServer, options); + console.log("response", response); + return response; + } + async uploadFileByClipboard(fileList) { + let data; + let res; + if (this.settings.remoteServerMode) { + res = await this.uploadFileByData(fileList); + data = await res.json(); + } + else { + res = await obsidian.requestUrl({ + url: this.settings.uploadServer, + method: "POST", + }); + data = await res.json; + } + if (res.status !== 200) { + return { + code: -1, + msg: data.msg, + data: "", + }; + } + // piclist + if (data.fullResult) { + const uploadUrlFullResultList = data.fullResult || []; + this.settings.uploadedImages = [ + ...(this.settings.uploadedImages || []), + ...uploadUrlFullResultList, + ]; + this.plugin.saveSettings(); + } + return { + code: 0, + msg: "success", + data: typeof data.result == "string" ? data.result : data.result[0], + }; + } +} +class PicGoCoreUploader { + settings; + plugin; + constructor(settings, plugin) { + this.settings = settings; + this.plugin = plugin; + } + async uploadFiles(fileList) { + const length = fileList.length; + let cli = this.settings.picgoCorePath || "picgo"; + let command = `${cli} upload ${fileList + .map(item => `"${item}"`) + .join(" ")}`; + const res = await this.exec(command); + const splitList = res.split("\n"); + const splitListLength = splitList.length; + const data = splitList.splice(splitListLength - 1 - length, length); + if (res.includes("PicGo ERROR")) { + console.log(command, res); + return { + success: false, + msg: "失败", + }; + } + else { + return { + success: true, + result: data, + }; + } + // {success:true,result:[]} + } + // PicGo-Core 上传处理 + async uploadFileByClipboard() { + const res = await this.uploadByClip(); + const splitList = res.split("\n"); + const lastImage = getLastImage(splitList); + if (lastImage) { + return { + code: 0, + msg: "success", + data: lastImage, + }; + } + else { + console.log(splitList); + // new Notice(`"Please check PicGo-Core config"\n${res}`); + return { + code: -1, + msg: `"Please check PicGo-Core config"\n${res}`, + data: "", + }; + } + } + // PicGo-Core的剪切上传反馈 + async uploadByClip() { + let command; + if (this.settings.picgoCorePath) { + command = `${this.settings.picgoCorePath} upload`; + } + else { + command = `picgo upload`; + } + const res = await this.exec(command); + // const res = await this.spawnChild(); + return res; + } + async exec(command) { + let { stdout } = await require$$0$2.exec(command); + const res = await streamToString(stdout); + return res; + } + async spawnChild() { + const { spawn } = require("child_process"); + const child = spawn("picgo", ["upload"], { + shell: true, + }); + let data = ""; + for await (const chunk of child.stdout) { + data += chunk; + } + let error = ""; + for await (const chunk of child.stderr) { + error += chunk; + } + const exitCode = await new Promise((resolve, reject) => { + child.on("close", resolve); + }); + if (exitCode) { + throw new Error(`subprocess error exit ${exitCode}, ${error}`); + } + return data; + } +} + +class PicGoDeleter { + plugin; + constructor(plugin) { + this.plugin = plugin; + } + async deleteImage(configMap) { + const response = await obsidian.requestUrl({ + url: this.plugin.settings.deleteServer, + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + list: configMap, + }), + }); + const data = response.json; + return data; + } +} + +// ![](./dsa/aa.png) local image should has ext, support ![](<./dsa/aa.png>), support ![](image.png "alt") +// ![](https://dasdasda) internet image should not has ext +const REGEX_FILE = /\!\[(.*?)\]\(<(\S+\.\w+)>\)|\!\[(.*?)\]\((\S+\.\w+)(?:\s+"[^"]*")?\)|\!\[(.*?)\]\((https?:\/\/.*?)\)/g; +const REGEX_WIKI_FILE = /\!\[\[(.*?)(\s*?\|.*?)?\]\]/g; +class Helper { + app; + constructor(app) { + this.app = app; + } + getFrontmatterValue(key, defaultValue = undefined) { + const file = this.app.workspace.getActiveFile(); + if (!file) { + return undefined; + } + const path = file.path; + const cache = this.app.metadataCache.getCache(path); + let value = defaultValue; + if (cache?.frontmatter && cache.frontmatter.hasOwnProperty(key)) { + value = cache.frontmatter[key]; + } + return value; + } + getEditor() { + const mdView = this.app.workspace.getActiveViewOfType(obsidian.MarkdownView); + if (mdView) { + return mdView.editor; + } + else { + return null; + } + } + getValue() { + const editor = this.getEditor(); + return editor.getValue(); + } + setValue(value) { + const editor = this.getEditor(); + const { left, top } = editor.getScrollInfo(); + const position = editor.getCursor(); + editor.setValue(value); + editor.scrollTo(left, top); + editor.setCursor(position); + } + // get all file urls, include local and internet + getAllFiles() { + const editor = this.getEditor(); + let value = editor.getValue(); + return this.getImageLink(value); + } + getImageLink(value) { + const matches = value.matchAll(REGEX_FILE); + const WikiMatches = value.matchAll(REGEX_WIKI_FILE); + let fileArray = []; + for (const match of matches) { + const source = match[0]; + let name = match[1]; + let path = match[2]; + if (name === undefined) { + name = match[3]; + } + if (path === undefined) { + path = match[4]; + } + fileArray.push({ + path: path, + name: name, + source: source, + }); + } + for (const match of WikiMatches) { + let name = pathBrowserify.parse(match[1]).name; + const path = match[1]; + const source = match[0]; + if (match[2]) { + name = `${name}${match[2]}`; + } + fileArray.push({ + path: path, + name: name, + source: source, + }); + } + return fileArray; + } + hasBlackDomain(src, blackDomains) { + if (blackDomains.trim() === "") { + return false; + } + const blackDomainList = blackDomains.split(",").filter(item => item !== ""); + let url = new URL(src); + const domain = url.hostname; + return blackDomainList.some(blackDomain => domain.includes(blackDomain)); + } +} + +const DEFAULT_SETTINGS = { + uploadByClipSwitch: true, + uploader: "PicGo", + uploadServer: "http://127.0.0.1:36677/upload", + deleteServer: "http://127.0.0.1:36677/delete", + imageSizeSuffix: "", + picgoCorePath: "", + workOnNetWork: false, + fixPath: false, + applyImage: true, + newWorkBlackDomains: "", + deleteSource: false, + imageDesc: "origin", + remoteServerMode: false, +}; +class SettingTab extends obsidian.PluginSettingTab { + plugin; + constructor(app, plugin) { + super(app, plugin); + this.plugin = plugin; + } + display() { + let { containerEl } = this; + const os = getOS(); + containerEl.empty(); + containerEl.createEl("h2", { text: t("Plugin Settings") }); + new obsidian.Setting(containerEl) + .setName(t("Auto pasted upload")) + .setDesc(t("If you set this value true, when you paste image, it will be auto uploaded(you should set the picGo server rightly)")) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.uploadByClipSwitch) + .onChange(async (value) => { + this.plugin.settings.uploadByClipSwitch = value; + await this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName(t("Default uploader")) + .setDesc(t("Default uploader")) + .addDropdown(cb => cb + .addOption("PicGo", "PicGo(app)") + .addOption("PicGo-Core", "PicGo-Core") + .setValue(this.plugin.settings.uploader) + .onChange(async (value) => { + this.plugin.settings.uploader = value; + this.display(); + await this.plugin.saveSettings(); + })); + if (this.plugin.settings.uploader === "PicGo") { + new obsidian.Setting(containerEl) + .setName(t("PicGo server")) + .setDesc(t("PicGo server desc")) + .addText(text => text + .setPlaceholder(t("Please input PicGo server")) + .setValue(this.plugin.settings.uploadServer) + .onChange(async (key) => { + this.plugin.settings.uploadServer = key; + await this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName(t("PicGo delete server")) + .setDesc(t("PicList desc")) + .addText(text => text + .setPlaceholder(t("Please input PicGo delete server")) + .setValue(this.plugin.settings.deleteServer) + .onChange(async (key) => { + this.plugin.settings.deleteServer = key; + await this.plugin.saveSettings(); + })); + } + new obsidian.Setting(containerEl) + .setName(t("Remote server mode")) + .setDesc(t("Remote server mode desc")) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.remoteServerMode) + .onChange(async (value) => { + this.plugin.settings.remoteServerMode = value; + if (value) { + this.plugin.settings.workOnNetWork = false; + } + this.display(); + await this.plugin.saveSettings(); + })); + if (this.plugin.settings.uploader === "PicGo-Core") { + new obsidian.Setting(containerEl) + .setName(t("PicGo-Core path")) + .setDesc(t("Please input PicGo-Core path, default using environment variables")) + .addText(text => text + .setPlaceholder("") + .setValue(this.plugin.settings.picgoCorePath) + .onChange(async (value) => { + this.plugin.settings.picgoCorePath = value; + await this.plugin.saveSettings(); + })); + if (os !== "Windows") { + new obsidian.Setting(containerEl) + .setName(t("fixPath")) + .setDesc(t("fixPathWarning")) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.fixPath) + .onChange(async (value) => { + this.plugin.settings.fixPath = value; + await this.plugin.saveSettings(); + })); + } + } + // image desc setting + new obsidian.Setting(containerEl) + .setName(t("Image desc")) + .setDesc(t("Image desc")) + .addDropdown(cb => cb + .addOption("origin", t("reserve")) // 保留全部 + .addOption("none", t("remove all")) // 移除全部 + .addOption("removeDefault", t("remove default")) // 只移除默认即 image.png + .setValue(this.plugin.settings.imageDesc) + .onChange(async (value) => { + this.plugin.settings.imageDesc = value; + this.display(); + await this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName(t("Image size suffix")) + .setDesc(t("Image size suffix Description")) + .addText(text => text + .setPlaceholder(t("Please input image size suffix")) + .setValue(this.plugin.settings.imageSizeSuffix) + .onChange(async (key) => { + this.plugin.settings.imageSizeSuffix = key; + await this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName(t("Work on network")) + .setDesc(t("Work on network Description")) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.workOnNetWork) + .onChange(async (value) => { + if (this.plugin.settings.remoteServerMode) { + new obsidian.Notice("Can only work when remote server mode is off."); + this.plugin.settings.workOnNetWork = false; + } + else { + this.plugin.settings.workOnNetWork = value; + } + this.display(); + await this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName(t("Network Domain Black List")) + .setDesc(t("Network Domain Black List Description")) + .addTextArea(textArea => textArea + .setValue(this.plugin.settings.newWorkBlackDomains) + .onChange(async (value) => { + this.plugin.settings.newWorkBlackDomains = value; + await this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName(t("Upload when clipboard has image and text together")) + .setDesc(t("When you copy, some application like Excel will image and text to clipboard, you can upload or not.")) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.applyImage) + .onChange(async (value) => { + this.plugin.settings.applyImage = value; + this.display(); + await this.plugin.saveSettings(); + })); + new obsidian.Setting(containerEl) + .setName(t("Delete source file after you upload file")) + .setDesc(t("Delete source file in ob assets after you upload file.")) + .addToggle(toggle => toggle + .setValue(this.plugin.settings.deleteSource) + .onChange(async (value) => { + this.plugin.settings.deleteSource = value; + this.display(); + await this.plugin.saveSettings(); + })); + } +} + +class imageAutoUploadPlugin extends obsidian.Plugin { + settings; + helper; + editor; + picGoUploader; + picGoDeleter; + picGoCoreUploader; + uploader; + async loadSettings() { + this.settings = Object.assign(DEFAULT_SETTINGS, await this.loadData()); + } + async saveSettings() { + await this.saveData(this.settings); + } + onunload() { } + async onload() { + await this.loadSettings(); + this.helper = new Helper(this.app); + this.picGoUploader = new PicGoUploader(this.settings, this); + this.picGoDeleter = new PicGoDeleter(this); + this.picGoCoreUploader = new PicGoCoreUploader(this.settings, this); + if (this.settings.uploader === "PicGo") { + this.uploader = this.picGoUploader; + } + else if (this.settings.uploader === "PicGo-Core") { + this.uploader = this.picGoCoreUploader; + if (this.settings.fixPath) { + fixPath(); + } + } + else { + new obsidian.Notice("unknown uploader"); + } + obsidian.addIcon("upload", ` + + `); + this.addSettingTab(new SettingTab(this.app, this)); + this.addCommand({ + id: "Upload all images", + name: "Upload all images", + checkCallback: (checking) => { + let leaf = this.app.workspace.activeLeaf; + if (leaf) { + if (!checking) { + this.uploadAllFile(); + } + return true; + } + return false; + }, + }); + this.addCommand({ + id: "Download all images", + name: "Download all images", + checkCallback: (checking) => { + let leaf = this.app.workspace.activeLeaf; + if (leaf) { + if (!checking) { + downloadAllImageFiles(this); + } + return true; + } + return false; + }, + }); + this.setupPasteHandler(); + this.registerFileMenu(); + this.registerSelection(); + } + registerSelection() { + this.registerEvent(this.app.workspace.on("editor-menu", (menu, editor, info) => { + if (this.app.workspace.getLeavesOfType("markdown").length === 0) { + return; + } + const selection = editor.getSelection(); + if (selection) { + const markdownRegex = /!\[.*\]\((.*)\)/g; + const markdownMatch = markdownRegex.exec(selection); + if (markdownMatch && markdownMatch.length > 1) { + const markdownUrl = markdownMatch[1]; + if (this.settings.uploadedImages.find((item) => item.imgUrl === markdownUrl)) { + this.addMenu(menu, markdownUrl, editor); + } + } + } + })); + } + addMenu = (menu, imgPath, editor) => { + menu.addItem((item) => item + .setIcon("trash-2") + .setTitle(t("Delete image using PicList")) + .onClick(async () => { + try { + const selectedItem = this.settings.uploadedImages.find((item) => item.imgUrl === imgPath); + if (selectedItem) { + const res = await this.picGoDeleter.deleteImage([selectedItem]); + if (res.success) { + new obsidian.Notice(t("Delete successfully")); + const selection = editor.getSelection(); + if (selection) { + editor.replaceSelection(""); + } + this.settings.uploadedImages = + this.settings.uploadedImages.filter((item) => item.imgUrl !== imgPath); + this.saveSettings(); + } + else { + new obsidian.Notice(t("Delete failed")); + } + } + } + catch { + new obsidian.Notice(t("Error, could not delete")); + } + })); + }; + registerFileMenu() { + this.registerEvent(this.app.workspace.on("file-menu", (menu, file, source, leaf) => { + if (source === "canvas-menu") + return false; + if (!isAssetTypeAnImage(file.path)) + return false; + menu.addItem((item) => { + item + .setTitle("Upload") + .setIcon("upload") + .onClick(() => { + if (!(file instanceof obsidian.TFile)) { + return false; + } + this.fileMenuUpload(file); + }); + }); + })); + } + fileMenuUpload(file) { + let content = this.helper.getValue(); + const basePath = this.app.vault.adapter.getBasePath(); + let imageList = []; + const fileArray = this.helper.getAllFiles(); + for (const match of fileArray) { + const imageName = match.name; + const encodedUri = match.path; + const fileName = pathBrowserify.basename(decodeURI(encodedUri)); + if (file && file.name === fileName) { + const abstractImageFile = pathBrowserify.join(basePath, file.path); + if (isAssetTypeAnImage(abstractImageFile)) { + imageList.push({ + path: abstractImageFile, + name: imageName, + source: match.source, + }); + } + } + } + if (imageList.length === 0) { + new obsidian.Notice(t("Can not find image file")); + return; + } + this.uploader.uploadFiles(imageList.map(item => item.path)).then(res => { + if (res.success) { + let uploadUrlList = res.result; + imageList.map(item => { + const uploadImage = uploadUrlList.shift(); + let name = this.handleName(item.name); + content = content.replaceAll(item.source, `![${name}](${uploadImage})`); + }); + this.helper.setValue(content); + if (this.settings.deleteSource) { + imageList.map(image => { + if (!image.path.startsWith("http")) { + require$$0.unlink(image.path, () => { }); + } + }); + } + } + else { + new obsidian.Notice("Upload error"); + } + }); + } + filterFile(fileArray) { + const imageList = []; + for (const match of fileArray) { + if (match.path.startsWith("http")) { + if (this.settings.workOnNetWork) { + if (!this.helper.hasBlackDomain(match.path, this.settings.newWorkBlackDomains)) { + imageList.push({ + path: match.path, + name: match.name, + source: match.source, + }); + } + } + } + else { + imageList.push({ + path: match.path, + name: match.name, + source: match.source, + }); + } + } + return imageList; + } + getFile(fileName, fileMap) { + if (!fileMap) { + fileMap = arrayToObject(this.app.vault.getFiles(), "name"); + } + return fileMap[fileName]; + } + // uploda all file + uploadAllFile() { + let content = this.helper.getValue(); + const basePath = this.app.vault.adapter.getBasePath(); + const activeFile = this.app.workspace.getActiveFile(); + const fileMap = arrayToObject(this.app.vault.getFiles(), "name"); + const filePathMap = arrayToObject(this.app.vault.getFiles(), "path"); + let imageList = []; + const fileArray = this.filterFile(this.helper.getAllFiles()); + for (const match of fileArray) { + const imageName = match.name; + const encodedUri = match.path; + if (encodedUri.startsWith("http")) { + imageList.push({ + path: match.path, + name: imageName, + source: match.source, + }); + } + else { + const fileName = pathBrowserify.basename(decodeURI(encodedUri)); + let file; + // 绝对路径 + if (filePathMap[decodeURI(encodedUri)]) { + file = filePathMap[decodeURI(encodedUri)]; + } + // 相对路径 + if ((!file && decodeURI(encodedUri).startsWith("./")) || + decodeURI(encodedUri).startsWith("../")) { + const filePath = pathBrowserify.resolve(pathBrowserify.join(basePath, pathBrowserify.dirname(activeFile.path)), decodeURI(encodedUri)); + if (require$$0.existsSync(filePath)) { + const path = obsidian.normalizePath(pathBrowserify.relative(obsidian.normalizePath(basePath), obsidian.normalizePath(pathBrowserify.resolve(pathBrowserify.join(basePath, pathBrowserify.dirname(activeFile.path)), decodeURI(encodedUri))))); + file = filePathMap[path]; + } + } + // 尽可能短路径 + if (!file) { + file = this.getFile(fileName, fileMap); + } + if (file) { + const abstractImageFile = pathBrowserify.join(basePath, file.path); + if (isAssetTypeAnImage(abstractImageFile)) { + imageList.push({ + path: abstractImageFile, + name: imageName, + source: match.source, + }); + } + } + } + } + if (imageList.length === 0) { + new obsidian.Notice(t("Can not find image file")); + return; + } + else { + new obsidian.Notice(`共找到${imageList.length}个图像文件,开始上传`); + } + this.uploader.uploadFiles(imageList.map(item => item.path)).then(res => { + if (res.success) { + let uploadUrlList = res.result; + if (imageList.length !== uploadUrlList.length) { + new obsidian.Notice(t("Warning: upload files is different of reciver files from api")); + } + imageList.map(item => { + const uploadImage = uploadUrlList.shift(); + let name = this.handleName(item.name); + content = content.replaceAll(item.source, `![${name}](${uploadImage})`); + }); + const currentFile = this.app.workspace.getActiveFile(); + if (activeFile.path !== currentFile.path) { + new obsidian.Notice(t("File has been changedd, upload failure")); + return; + } + this.helper.setValue(content); + if (this.settings.deleteSource) { + imageList.map(image => { + if (!image.path.startsWith("http")) { + require$$0.unlink(image.path, () => { }); + } + }); + } + } + else { + new obsidian.Notice("Upload error"); + } + }); + } + setupPasteHandler() { + this.registerEvent(this.app.workspace.on("editor-paste", (evt, editor, markdownView) => { + const allowUpload = this.helper.getFrontmatterValue("image-auto-upload", this.settings.uploadByClipSwitch); + evt.clipboardData.files; + if (!allowUpload) { + return; + } + // 剪贴板内容有md格式的图片时 + if (this.settings.workOnNetWork) { + const clipboardValue = evt.clipboardData.getData("text/plain"); + const imageList = this.helper + .getImageLink(clipboardValue) + .filter(image => image.path.startsWith("http")) + .filter(image => !this.helper.hasBlackDomain(image.path, this.settings.newWorkBlackDomains)); + if (imageList.length !== 0) { + this.uploader + .uploadFiles(imageList.map(item => item.path)) + .then(res => { + let value = this.helper.getValue(); + if (res.success) { + let uploadUrlList = res.result; + imageList.map(item => { + const uploadImage = uploadUrlList.shift(); + let name = this.handleName(item.name); + value = value.replaceAll(item.source, `![${name}](${uploadImage})`); + }); + this.helper.setValue(value); + } + else { + new obsidian.Notice("Upload error"); + } + }); + } + } + // 剪贴板中是图片时进行上传 + if (this.canUpload(evt.clipboardData)) { + this.uploadFileAndEmbedImgurImage(editor, async (editor, pasteId) => { + let res; + res = await this.uploader.uploadFileByClipboard(evt.clipboardData.files); + if (res.code !== 0) { + this.handleFailedUpload(editor, pasteId, res.msg); + return; + } + const url = res.data; + return url; + }, evt.clipboardData).catch(); + evt.preventDefault(); + } + })); + this.registerEvent(this.app.workspace.on("editor-drop", async (evt, editor, markdownView) => { + // when ctrl key is pressed, do not upload image, because it is used to set local file + if (evt.ctrlKey) { + return; + } + const allowUpload = this.helper.getFrontmatterValue("image-auto-upload", this.settings.uploadByClipSwitch); + let files = evt.dataTransfer.files; + if (!allowUpload) { + return; + } + if (files.length !== 0 && files[0].type.startsWith("image")) { + let sendFiles = []; + let files = evt.dataTransfer.files; + Array.from(files).forEach((item, index) => { + if (item.path) { + sendFiles.push(item.path); + } + else { + const { webUtils } = require("electron"); + const path = webUtils.getPathForFile(item); + sendFiles.push(path); + } + }); + evt.preventDefault(); + const data = await this.uploader.uploadFiles(sendFiles); + if (data.success) { + data.result.map((value) => { + let pasteId = (Math.random() + 1).toString(36).substr(2, 5); + this.insertTemporaryText(editor, pasteId); + this.embedMarkDownImage(editor, pasteId, value, files[0].name); + }); + } + else { + new obsidian.Notice("Upload error"); + } + } + })); + } + canUpload(clipboardData) { + this.settings.applyImage; + const files = clipboardData.files; + const text = clipboardData.getData("text"); + const hasImageFile = files.length !== 0 && files[0].type.startsWith("image"); + if (hasImageFile) { + if (!!text) { + return this.settings.applyImage; + } + else { + return true; + } + } + else { + return false; + } + } + async uploadFileAndEmbedImgurImage(editor, callback, clipboardData) { + let pasteId = (Math.random() + 1).toString(36).substr(2, 5); + this.insertTemporaryText(editor, pasteId); + const name = clipboardData.files[0].name; + try { + const url = await callback(editor, pasteId); + this.embedMarkDownImage(editor, pasteId, url, name); + } + catch (e) { + this.handleFailedUpload(editor, pasteId, e); + } + } + insertTemporaryText(editor, pasteId) { + let progressText = imageAutoUploadPlugin.progressTextFor(pasteId); + editor.replaceSelection(progressText + "\n"); + } + static progressTextFor(id) { + return `![Uploading file...${id}]()`; + } + embedMarkDownImage(editor, pasteId, imageUrl, name = "") { + let progressText = imageAutoUploadPlugin.progressTextFor(pasteId); + name = this.handleName(name); + let markDownImage = `![${name}](${imageUrl})`; + imageAutoUploadPlugin.replaceFirstOccurrence(editor, progressText, markDownImage); + } + handleFailedUpload(editor, pasteId, reason) { + new obsidian.Notice(reason); + console.error("Failed request: ", reason); + let progressText = imageAutoUploadPlugin.progressTextFor(pasteId); + imageAutoUploadPlugin.replaceFirstOccurrence(editor, progressText, "⚠️upload failed, check dev console"); + } + handleName(name) { + const imageSizeSuffix = this.settings.imageSizeSuffix || ""; + if (this.settings.imageDesc === "origin") { + return `${name}${imageSizeSuffix}`; + } + else if (this.settings.imageDesc === "none") { + return ""; + } + else if (this.settings.imageDesc === "removeDefault") { + if (name === "image.png") { + return ""; + } + else { + return `${name}${imageSizeSuffix}`; + } + } + else { + return `${name}${imageSizeSuffix}`; + } + } + static replaceFirstOccurrence(editor, target, replacement) { + let lines = editor.getValue().split("\n"); + for (let i = 0; i < lines.length; i++) { + let ch = lines[i].indexOf(target); + if (ch != -1) { + let from = { line: i, ch: ch }; + let to = { line: i, ch: ch + target.length }; + editor.replaceRange(replacement, from, to); + break; + } + } + } +} + +module.exports = imageAutoUploadPlugin; + + +/* nosourcemap */ \ No newline at end of file diff --git a/.obsidian/plugins/obsidian-image-auto-upload-plugin/manifest.json b/.obsidian/plugins/obsidian-image-auto-upload-plugin/manifest.json new file mode 100644 index 00000000..abbadb89 --- /dev/null +++ b/.obsidian/plugins/obsidian-image-auto-upload-plugin/manifest.json @@ -0,0 +1,11 @@ +{ + "id": "obsidian-image-auto-upload-plugin", + "name": "Image auto upload", + "version": "4.1.0", + "minAppVersion": "0.10.7", + "description": "This plugin uploads images from your clipboard by PicGo", + "author": "renmu", + "authorUrl": "https://github.com/renmu123/obsidian-image-auto-upload-plugin", + "isDesktopOnly": true, + "fundingUrl": "https://afdian.com/a/renmu123" +}