vite-plugin-monkey
Version:
A vite plugin server and build your.user.js for userscript engine like Tampermonkey and Violentmonkey and Greasemonkey
1 lines • 149 kB
Source Map (JSON)
{"version":3,"sources":["../../src/node/_logger.ts","../../src/node/_util.ts","../../src/node/gm_api.ts","../../src/node/cdn.ts","../../src/node/inject_template.ts","../../src/node/option.ts","../../src/node/plugins/server.ts","../../src/node/open_browser.ts","../../src/node/userscript/index.ts","../../src/node/plugins/virtualHtml.ts","../../src/node/plugins/fixViteAsset.ts","../../src/node/plugins/fixViteClient.ts","../../src/node/plugins/externalGlobals.ts","../../src/node/plugins/externalLoader.ts","../../src/node/plugins/externalResource.ts","../../src/node/plugins/finalBundle.ts","../../src/node/_lazy.ts","../../src/node/systemjs.ts","../../src/node/util.ts","../../src/node/unimport.ts","../../src/node/topLevelAwait.ts","../../src/node/plugins/perview.ts","../../src/node/plugins/redirectClient.ts","../../src/node/plugins/index.ts","../../src/node/index.ts"],"sourcesContent":["import pc from 'picocolors';\n\nexport type Message = string | number | null | undefined;\ninterface OutputOptions {\n /**\n * @default false\n */\n time?: boolean;\n}\n\nconst log = (tag: string, message: Message, options?: OutputOptions) => {\n console.log(\n [\n (options?.time ?? false) === true\n ? pc.dim(new Date().toLocaleTimeString())\n : '',\n pc.bold(pc.blue(`[${tag}]`)),\n message,\n ]\n .filter((s) => s)\n .join('\\x20'),\n );\n};\nconst info = (tag: string, message: Message, options?: OutputOptions) => {\n log(tag, pc.white(message), options);\n};\nconst warn = (tag: string, message: Message, options?: OutputOptions) => {\n log(tag, pc.yellow(message), options);\n};\nconst error = (tag: string, message: Message, options?: OutputOptions) => {\n log(tag, pc.red(message), options);\n};\nexport const createLogger = (tag: string) => ({\n info: async (message: Message, options?: OutputOptions) => {\n info(tag, message, options);\n },\n warn: async (message: Message, options?: OutputOptions) => {\n warn(tag, message, options);\n },\n error: async (message: Message, options?: OutputOptions) => {\n error(tag, message, options);\n },\n});\n\n/**\n * default logger\n */\nexport const logger = createLogger('plugin-monkey');\n\nexport type Logger = ReturnType<typeof createLogger>;\n","import * as acornWalk from 'acorn-walk';\nimport { resolve } from 'import-meta-resolve';\nimport { readFileSync } from 'node:fs';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { OutputBundle, PluginContext, OutputChunk } from 'rollup';\nimport { normalizePath, transformWithEsbuild } from 'vite';\nimport { logger } from './_logger';\nimport { FinalMonkeyOption } from './types';\nimport { grantNames } from './gm_api';\n\nexport const delay = async (n = 0) => {\n await new Promise<void>((res) => {\n setTimeout(res, n);\n });\n};\n\nconst get_vite_start_time = () => {\n // @see https://github.com/vitejs/vite/blob/c703a3348adeaad9dc92d805a381866917f2a03b/packages/vite/src/node/server/index.ts#L741\n const n: unknown = Reflect.get(globalThis, '__vite_start_time') ?? 0;\n if (typeof n != 'number') {\n return 0;\n } else {\n return n;\n }\n};\n\nexport const isFirstBoot = (n = 1000) => get_vite_start_time() < n;\n\ninterface RawPackageJson {\n name?: string;\n version?: string;\n description?: string;\n license?: string;\n author?: string | { name: string };\n homepage?: string;\n repository?: string | { url?: string };\n bugs?: string | { url?: string };\n}\ninterface PackageJson {\n name: string;\n version?: string;\n description?: string;\n license?: string;\n author?: string;\n homepage?: string;\n repository?: string;\n bugs?: string;\n}\n\nexport const projectPkg = (() => {\n let rawTarget: RawPackageJson = {};\n try {\n rawTarget = JSON.parse(\n readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),\n );\n } catch {\n rawTarget = {};\n }\n\n const target: PackageJson = {\n name: 'monkey',\n };\n Object.entries(rawTarget).forEach(([k, v]) => {\n if (typeof v == 'string') {\n Reflect.set(target, k, v);\n }\n });\n if (\n rawTarget.author instanceof Object &&\n typeof rawTarget.author?.name == 'string'\n ) {\n target.author = rawTarget.author?.name;\n }\n if (\n rawTarget.bugs instanceof Object &&\n typeof rawTarget.bugs?.url == 'string'\n ) {\n target.bugs = rawTarget.bugs?.url;\n }\n if (\n rawTarget.repository instanceof Object &&\n typeof rawTarget.repository?.url == 'string'\n ) {\n const { url } = rawTarget.repository;\n if (url.startsWith('http')) {\n target.repository = url;\n } else if (url.startsWith('git+http')) {\n target.repository = url.slice(4);\n }\n }\n return target;\n})();\n\nexport const compatResolve = (id: string) => {\n return resolve(id, pathToFileURL(process.cwd() + '/any.js').href);\n};\n\nexport const existFile = async (path: string) => {\n try {\n return (await fs.stat(path)).isFile();\n } catch {\n return false;\n }\n};\n\nconst resolveModuleFromPath = async (subpath: string) => {\n const p = normalizePath(process.cwd()).split('/');\n for (let i = p.length; i > 0; i--) {\n const p2 = `${p.slice(0, i).join('/')}/node_modules/${subpath}`;\n if (await existFile(p2)) {\n return p2;\n }\n }\n};\n\nconst compatResolveModulePath = async (id: string): Promise<string> => {\n try {\n return compatResolve(id);\n } catch (e) {\n // not defined in pkg/package.json but exist in pkg/subpath\n // https://github.com/lisonge/vite-plugin-monkey/issues/169\n const r = await resolveModuleFromPath(id);\n if (!r) {\n throw e;\n }\n return r;\n }\n};\n\nconst isScopePkg = (name: string) => name.startsWith('@');\n\nexport const getModuleRealInfo = async (importName: string) => {\n const nameNoQuery = normalizePath(importName.split('?')[0]);\n const resolveName = await (async () => {\n const n = normalizePath(await compatResolveModulePath(nameNoQuery)).replace(\n /.*\\/node_modules\\/[^/]+\\//,\n '',\n );\n if (isScopePkg(importName)) {\n return n.split('/').slice(1).join('/');\n }\n return n;\n })();\n let version: string | undefined = undefined;\n const nameList = nameNoQuery.split('/');\n let pkgName = nameNoQuery;\n while (nameList.length > 0) {\n pkgName = nameList.join('/');\n const filePath = await (async () => {\n const p = await resolveModuleFromPath(`${pkgName}/package.json`);\n if (p) {\n return p;\n }\n try {\n return compatResolve(`${pkgName}/package.json`);\n } catch {\n return undefined;\n }\n })();\n if (filePath === undefined || !(await existFile(filePath))) {\n nameList.pop();\n continue;\n }\n const modulePack: PackageJson = JSON.parse(\n await fs.readFile(filePath, 'utf-8'),\n );\n version = modulePack.version;\n break;\n }\n if (version === undefined) {\n logger.warn(\n `not found module ${nameNoQuery} version, use ${nameNoQuery}@latest`,\n );\n pkgName = nameNoQuery;\n version = 'latest';\n }\n return { version, name: pkgName, resolveName };\n};\n\nexport const mergeObj = <T, S>(target: T | undefined, source: S) => {\n if (target === undefined) return { ...source } as T & S;\n const obj = { ...target };\n for (const k in source) {\n // @ts-ignore\n if (obj[k] === undefined) {\n // @ts-ignore\n obj[k] = source[k];\n }\n }\n return obj as T & S;\n};\n\nexport const miniCode = async (code: string, type: 'css' | 'js' = 'js') => {\n return (\n await transformWithEsbuild(code, 'any_name.' + type, {\n minify: true,\n sourcemap: false,\n legalComments: 'none',\n })\n ).code.trimEnd();\n};\n\nexport const toValidURL = (url: unknown) => {\n if (typeof url != 'string') return;\n try {\n return new URL(url);\n } catch {}\n};\n\nexport const isTopLevelAwaitAvailableTarget = async (\n target?: string | string[],\n) => {\n target = getFinalTarget(target);\n return transformWithEsbuild(`await 1`, 'any.js', {\n target,\n logLevel: 'silent',\n })\n .then(() => true)\n .catch(() => false);\n};\n\n// https://github.com/vitejs/vite/blob/b9511f1ed8e36a618214944c69e2de6504ebcb3c/packages/vite/src/node/constants.ts#L20\nexport const ESBUILD_MODULES_TARGET = [\n 'es2020',\n 'edge88',\n 'firefox78',\n 'chrome87',\n 'safari14',\n];\n\nexport const getFinalTarget = (target?: string | string[]) => {\n if (target === 'modules') {\n target = ESBUILD_MODULES_TARGET;\n }\n return target;\n};\n\nexport const moduleExportExpressionWrapper = (expression: string) => {\n let n = 0;\n let identifier = ``;\n while (expression.includes(identifier)) {\n identifier = `_${(n || ``).toString(16)}`;\n n++;\n }\n // https://github.com/lisonge/vite-plugin-monkey/issues/70\n return `(()=>{const ${identifier}=${expression};('default' in ${identifier})||(${identifier}.default=${identifier});return ${identifier}})()`;\n};\n\n// https://stackoverflow.com/questions/7616461\nexport const cyrb53hash = (str = ``, seed = 0) => {\n let h1 = 0xdeadbeef ^ seed,\n h2 = 0x41c6ce57 ^ seed;\n for (let i = 0, ch; i < str.length; i++) {\n ch = str.charCodeAt(i);\n h1 = Math.imul(h1 ^ ch, 2654435761);\n h2 = Math.imul(h2 ^ ch, 1597334677);\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n\n return (4294967296 * (2097151 & h2) + (h1 >>> 0))\n .toString(36)\n .substring(0, 8);\n};\n\nexport async function* walk(dirPath: string) {\n const pathnames = (await fs.readdir(dirPath)).map((s) =>\n path.join(dirPath, s),\n );\n while (pathnames.length > 0) {\n const pathname = pathnames.pop()!;\n const state = await fs.lstat(pathname);\n if (state.isFile()) {\n yield pathname;\n } else if (state.isDirectory()) {\n pathnames.push(\n ...(await fs.readdir(pathname)).map((s) => path.join(pathname, s)),\n );\n }\n }\n}\n\nexport const collectGrant = (\n context: PluginContext,\n chunks: OutputChunk[],\n injectCssCode: string | undefined,\n minify: boolean,\n): Set<string> => {\n const codes = new Set<string>();\n if (injectCssCode) {\n codes.add(injectCssCode);\n }\n for (const chunk of chunks) {\n if (minify) {\n // https://github.com/lisonge/vite-plugin-monkey/issues/166\n const modules = Object.values(chunk.modules);\n modules.forEach((m) => {\n const code = m.code;\n if (code) {\n codes.add(code);\n }\n });\n }\n codes.add(chunk.code);\n }\n const unusedMembers = new Set<string>(\n grantNames.filter((s) => s.includes(`.`)),\n );\n const endsWithWin = (a: string, b: string): boolean => {\n if (a.endsWith(b)) {\n return a === 'monkeyWindow.' + b || a === '_monkeyWindow.' + b;\n }\n return false;\n };\n const memberHandleMap = Object.fromEntries(\n grantNames\n .filter((s) => s.startsWith('window.'))\n .map((name) => [name, (v: string) => endsWithWin(v, name.split('.')[1])]),\n );\n const unusedIdentifiers = new Set<string>(\n grantNames.filter((s) => !s.includes(`.`)),\n );\n const usedGm = new Set<string>();\n const matchIdentifier = (name: string): boolean => {\n if (unusedIdentifiers.has(name)) {\n usedGm.add(name);\n unusedIdentifiers.delete(name);\n return true;\n }\n return false;\n };\n const matchMember = (name: string): boolean => {\n for (const unusedName of unusedMembers.values()) {\n if (name.endsWith(unusedName) || memberHandleMap[unusedName]?.(name)) {\n usedGm.add(unusedName);\n unusedMembers.delete(unusedName);\n return true;\n }\n }\n return false;\n };\n for (const code of codes) {\n if (!code.trim()) continue;\n const ast = context.parse(code);\n acornWalk.simple(\n ast,\n {\n MemberExpression(node) {\n if (unusedMembers.size === 0) return;\n if (\n node.computed ||\n node.object.type !== 'Identifier' ||\n node.property.type !== 'Identifier'\n ) {\n return;\n }\n if (\n node.object.name === 'monkeyWindow' ||\n node.object.name === '_monkeyWindow'\n ) {\n if (matchIdentifier(node.property.name)) {\n return;\n }\n }\n const name = node.object.name + '.' + node.property.name;\n matchMember(name);\n },\n Identifier(node) {\n // only one layer\n matchIdentifier(node.name);\n },\n },\n { ...acornWalk.base },\n );\n if (unusedMembers.size == 0 && unusedIdentifiers.size == 0) {\n break;\n }\n }\n return usedGm;\n};\n\nexport const getInjectCssCode = async (\n finalOption: FinalMonkeyOption,\n rawBundle: OutputBundle,\n) => {\n const cssTexts: string[] = [];\n Object.entries(rawBundle).forEach(([k, v]) => {\n if (v.type == 'asset' && k.endsWith('.css')) {\n cssTexts.push(v.source.toString());\n delete rawBundle[k];\n }\n });\n const css = cssTexts.join('').trim();\n if (css) {\n // use \\x20 to compat unocss, see https://github.com/lisonge/vite-plugin-monkey/issues/45\n return await finalOption.cssSideEffects(`\\x20` + css + `\\x20`);\n }\n};\n","export const gmIdentifiers = [\n 'GM_addElement',\n 'GM_addStyle',\n 'GM_addValueChangeListener',\n 'GM_cookie',\n 'GM_deleteValue',\n 'GM_deleteValues',\n 'GM_download',\n 'GM_getResourceText',\n 'GM_getResourceURL',\n 'GM_getTab',\n 'GM_getTabs',\n 'GM_getValue',\n 'GM_getValues',\n 'GM_info',\n 'GM_listValues',\n 'GM_log',\n 'GM_notification',\n 'GM_openInTab',\n 'GM_registerMenuCommand',\n 'GM_removeValueChangeListener',\n 'GM_saveTab',\n 'GM_setClipboard',\n 'GM_setValue',\n 'GM_setValues',\n 'GM_unregisterMenuCommand',\n 'GM_webRequest',\n 'GM_xmlhttpRequest',\n] as const;\n\nconst gmMembers = [\n 'GM.addElement',\n 'GM.addStyle',\n 'GM.addValueChangeListener',\n 'GM.cookie',\n 'GM.deleteValue',\n 'GM.deleteValues',\n 'GM.download',\n 'GM.getResourceText',\n // https://www.tampermonkey.net/documentation.php#api:GM_getResourceURL\n 'GM.getResourceUrl',\n 'GM.getTab',\n 'GM.getTabs',\n 'GM.getValue',\n 'GM.getValues',\n 'GM.info',\n 'GM.listValues',\n 'GM.log',\n 'GM.notification',\n 'GM.openInTab',\n 'GM.registerMenuCommand',\n 'GM.removeValueChangeListener',\n 'GM.saveTab',\n 'GM.setClipboard',\n 'GM.setValue',\n 'GM.setValues',\n 'GM.unregisterMenuCommand',\n 'GM.webRequest',\n 'GM.xmlHttpRequest',\n] as const;\n\nconst othersGrantNames = [\n 'unsafeWindow',\n 'window.close',\n 'window.focus',\n 'window.onurlchange',\n] as const;\n\nexport type GrantType =\n | (typeof gmIdentifiers)[number]\n | (typeof gmMembers)[number]\n | (typeof othersGrantNames)[number];\n\nexport const grantNames = [...gmMembers, ...gmIdentifiers, ...othersGrantNames];\n","import type { ModuleToUrlFc } from './types';\n\n/**\n * `https://cdn.jsdelivr.net/npm/${name}@${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n * @param pathname jsdelivr support file combine, normally you don't need set pathname\n * @see https://www.jsdelivr.com/features\n */\nexport const jsdelivr = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n if (p) {\n return `https://cdn.jsdelivr.net/npm/${name}@${version}/${p}`;\n } else {\n return `https://cdn.jsdelivr.net/npm/${name}@${version}`;\n }\n },\n ];\n};\n\n/**\n * `https://fastly.jsdelivr.net/npm/${name}@${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n * @param pathname jsdelivr support file combine, normally you don't need set pathname\n * @see https://www.jsdelivr.com/features\n */\nexport const jsdelivrFastly = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n if (p) {\n return `https://fastly.jsdelivr.net/npm/${name}@${version}/${p}`;\n } else {\n return `https://fastly.jsdelivr.net/npm/${name}@${version}`;\n }\n },\n ];\n};\n\n/**\n * `https://unpkg.com/${name}@${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n * @see https://unpkg.com/\n */\nexport const unpkg = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n if (p) {\n return `https://unpkg.com/${name}@${version}/${p}`;\n } else {\n return `https://unpkg.com/${name}@${version}`;\n }\n },\n ];\n};\n\n/**\n * `https://lf9-cdn-tos.bytecdntp.com/cdn/expire-10-y/${name}/${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n * @see https://cdn.bytedance.com/\n */\nexport const bytecdntp = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://lf9-cdn-tos.bytecdntp.com/cdn/expire-10-y/${name}/${version}/${p}`;\n },\n ];\n};\n\n/**\n * `https://fastly.jsdelivr.net/npm/${name}@${version}/${pathname}`\n * @deprecated bootcdn will return virus-infected code. Please stop using it and switch to other sources\n */\nexport const bootcdn = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n console.warn(\n 'bootcdn will return virus-infected code. Please stop using it and switch to other sources. now it will return jsdelivr url.',\n );\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://fastly.jsdelivr.net/npm/${name}@${version}/${p}`;\n },\n ];\n};\n\n/**\n * `https://lib.baomitu.com/${name}/${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n * @see https://cdn.baomitu.com/\n */\nexport const baomitu = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://lib.baomitu.com/${name}/${version}/${p}`;\n },\n ];\n};\n\n/**\n * `https://fastly.jsdelivr.net/npm/${name}@${version}/${pathname}`\n * @deprecated staticfile will return virus-infected code. Please stop using it and switch to other sources\n */\nexport const staticfile = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n console.warn(\n 'staticfile will return virus-infected code. Please stop using it and switch to other sources. now it will return jsdelivr url.',\n );\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://fastly.jsdelivr.net/npm/${name}@${version}/${p}`;\n },\n ];\n};\n/**\n * `https://cdnjs.cloudflare.com/ajax/libs/${name}/${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n * @see https://cdnjs.com/libraries\n */\nexport const cdnjs = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://cdnjs.cloudflare.com/ajax/libs/${name}/${version}/${p}`;\n },\n ];\n};\n\n/**\n * `https://unpkg.zhimg.com/${name}/${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n * @link https://unpkg.zhimg.com/\n */\nexport const zhimg = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://unpkg.zhimg.com/${name}@${version}/${p}`;\n },\n ];\n};\n\n/**\n * `https://npm.elemecdn.com/${name}@${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n */\nexport const elemecdn = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://npm.elemecdn.com/${name}@${version}/${p}`;\n },\n ];\n};\n\n/**\n * `https://code.bdstatic.com/npm/${name}@${version}/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n */\nexport const bdstatic = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n return `https://code.bdstatic.com/npm/${name}@${version}/${p}`;\n },\n ];\n};\n\n/**\n * `https://registry.npmmirror.com/${name}/${version}/files/${pathname}`\n * @param exportVarName cdn-exportVarName or resourceName\n */\nexport const npmmirror = (\n exportVarName = '',\n pathname = '',\n): [string, ModuleToUrlFc] => {\n return [\n exportVarName,\n (version, name, _importName = '', resolveName = '') => {\n const p = pathname || resolveName;\n if (p) {\n return `https://registry.npmmirror.com/${name}/${version}/files/${p}`;\n } else {\n return `https://registry.npmmirror.com/${name}/${version}/files`;\n }\n },\n ];\n};\n","export const fn2string = <T extends (...args: any[]) => any>(\n fn: T,\n ...args: Parameters<T>\n) => {\n return `;(${fn})(...${JSON.stringify(args, undefined, 2)});`;\n};\n\nconst htmlText = /* html */ `\n<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"https://vitejs.dev/logo.svg\" />\n <title>Vite</title>\n </head>\n <script type=\"module\" data-source=\"vite-plugin-monkey\">\n __CODE__\n </script>\n</html>\n`.trimStart();\n\nexport const fcToHtml = <T extends (...args: any[]) => any>(\n fn: T,\n ...args: Parameters<T>\n) => {\n return htmlText.replace(\n `__CODE__`,\n `;(${fn})(...${JSON.stringify(args, void 0, 2)});`,\n );\n};\n\nexport interface ScriptOptions {\n entrySrc: string;\n}\nexport const serverInjectFn = ({ entrySrc }: ScriptOptions) => {\n /// https://github.com/Tampermonkey/tampermonkey/issues/1567\n // @ts-ignore\n // eslint-disable-next-line @typescript-eslint/no-unused-expressions\n window.GM; // must exist, see https://github.com/Tampermonkey/tampermonkey/issues/1567\n\n const key = `__monkeyWindow-` + new URL(entrySrc).origin;\n // @ts-ignore\n document[key] = window;\n console.log(`[vite-plugin-monkey] mount monkeyWindow to document`);\n // @ts-ignore\n if (typeof GM_addElement === 'function') {\n // @ts-ignore\n GM_addElement(document.head, 'script', {\n type: 'module',\n src: entrySrc,\n });\n } else {\n const script = document.createElement('script');\n script.type = 'module';\n // @ts-ignore\n if (window.trustedTypes) {\n // https://github.com/lisonge/vite-plugin-monkey/issues/205\n // @ts-ignore\n const policy = window.trustedTypes.createPolicy(key, {\n createScriptURL: (input: unknown) => input,\n });\n const trustedScriptURL = policy.createScriptURL(entrySrc);\n script.src = trustedScriptURL;\n } else {\n script.src = entrySrc;\n }\n document.head.append(script);\n }\n console.log(`[vite-plugin-monkey] mount entry module to document.head`);\n};\n\nexport const mountGmApiFn = (meta: ImportMeta, apiNames: string[] = []) => {\n const key = `__monkeyWindow-` + new URL(meta.url).origin;\n // @ts-ignore\n const monkeyWindow: Window = document[key];\n if (!monkeyWindow) {\n console.log(`[vite-plugin-monkey] not found monkeyWindow`);\n return;\n }\n\n // @ts-ignore\n window.unsafeWindow = window;\n console.log(`[vite-plugin-monkey] mount unsafeWindow to unsafeWindow`);\n\n apiNames.push('GM');\n let mountedApiSize = 0;\n apiNames.forEach((apiName) => {\n // @ts-ignore\n const fn = monkeyWindow[apiName];\n if (fn) {\n // @ts-ignore\n window[apiName] = monkeyWindow[apiName];\n mountedApiSize++;\n }\n });\n console.log(\n `[vite-plugin-monkey] mount ${mountedApiSize}/${apiNames.length} GM api to unsafeWindow`,\n );\n};\n\nexport const virtualHtmlTemplate = async (url: string) => {\n const delay = (n = 0) => new Promise<void>((res) => setTimeout(res, n));\n await delay();\n const u = new URL(url, location.origin);\n u.searchParams.set('origin', u.origin);\n if (window == window.parent) {\n location.href = u.href;\n await delay(500);\n window.close();\n return;\n }\n // if in iframe, like codesandbox\n const style = document.createElement('style');\n document.head.append(style);\n style.innerText = /* css */ `\n body {\n font-family: Arial, sans-serif;\n margin: 0;\n }\n .App {\n margin: 25px;\n }\n p {\n font-size: 1.5em;\n }\n a {\n color: blue;\n text-decoration: none;\n font-size: 1.5em;\n }\n a:hover {\n text-decoration: underline;\n }\n`.trim();\n document.body.innerHTML = /* html */ `\n <div class=\"App\">\n <h1>PREVIEW PAGE</h1>\n <p>Click the links below to install userscripts:</p>\n <a target=\"_blank\"></a></th>\n </div>\n `.trim();\n await delay();\n const a = document.querySelector('a')!;\n a.href = location.href;\n a.text = location.href;\n};\n\nexport const previewTemplate = async (urls: string[]) => {\n const delay = (n = 0) => new Promise<void>((res) => setTimeout(res, n));\n await delay();\n const style = document.createElement('style');\n document.head.append(style);\n style.innerText = /* css */ `\n body {\n font-family: Arial, sans-serif;\n margin: 0;\n }\n .App {\n margin: 25px;\n }\n p {\n font-size: 1.5em;\n }\n table {\n width: 100%;\n border-collapse: collapse;\n font-size: 1.5em;\n }\n th, td {\n border: 1px solid black;\n padding: 8px;\n text-align: left;\n }\n th {\n background-color: #f2f2f2;\n }\n a {\n color: blue;\n text-decoration: none;\n }\n a:hover {\n text-decoration: underline;\n }\n`.trim();\n if (window == window.parent && urls.length == 1) {\n const u = new URL(urls[0], location.origin);\n location.href = u.href;\n await delay(500);\n window.close();\n return;\n } else if (urls.length == 0) {\n document.body.innerHTML = /* html */ `\n <div class=\"App\">\n <h1> There is no script to install </h1>\n </div>\n `.trim();\n return;\n } else {\n document.body.innerHTML = /* html */ `\n <div class=\"App\">\n <h1>PREVIEW PAGE</h1>\n <p>Click the links below to install userscripts:</p>\n <table>\n <tr>\n <th>No.</th>\n <th>Install Link</th>\n </tr>\n </table>\n </div>\n `.trim();\n await delay();\n const table = document.querySelector<HTMLElement>(`table`)!;\n urls.sort().forEach((u, index) => {\n const tr = document.createElement('tr');\n const td1 = document.createElement('td');\n const td2 = document.createElement('td');\n const a = document.createElement('a');\n td1.innerText = `${index + 1}`;\n if (window != window.parent) {\n a.target = '_blank';\n }\n a.href = u;\n a.textContent = new URL(u, location.origin).href;\n td2.append(a);\n tr.append(td1);\n tr.append(td2);\n table.append(tr);\n });\n }\n};\n","import { logger } from './_logger';\nimport { miniCode, projectPkg } from './_util';\nimport { jsdelivr } from './cdn';\nimport { fn2string } from './inject_template';\nimport type {\n FinalMonkeyOption,\n IArray,\n IPromise,\n Mod2UrlFn,\n MonkeyOption,\n Pkg2UrlFn,\n PkgOptions,\n} from './types';\n\nexport const resolvedOption = (\n pluginOption: MonkeyOption,\n): FinalMonkeyOption => {\n const build = pluginOption.build ?? {};\n\n if (build.minifyCss !== undefined) {\n logger.warn(\n `monkeyConfig.build.minifyCss is deprecated, use viteConfig.build.cssMinify in vite>=4.2.0`,\n { time: false },\n );\n }\n\n const { externalResource = {} } = build;\n const externalResource2: Record<\n string,\n {\n resourceUrl: Pkg2UrlFn;\n resourceName: Pkg2UrlFn;\n loader?: (pkgOptions: PkgOptions) => unknown;\n nodeLoader?: (pkgOptions: PkgOptions) => IPromise<string>;\n }\n > = {};\n\n for (const [k, v] of Object.entries(externalResource)) {\n if (typeof v == 'string') {\n externalResource2[k] = {\n resourceName: () => k,\n resourceUrl: () => v,\n };\n } else if (typeof v == 'function') {\n externalResource2[k] = {\n resourceName: () => k,\n resourceUrl: v,\n };\n } else if (v instanceof Array) {\n let resourceUrl2: Pkg2UrlFn;\n let resourceName2: Pkg2UrlFn = () => k;\n const [resourceName, resourceUrl] = v;\n if (typeof resourceName == 'string') {\n resourceName2 = (pkg) => resourceName || pkg.importName;\n } else {\n resourceName2 = (pkg) =>\n resourceName(pkg.version, pkg.name, pkg.importName, pkg.resolveName);\n }\n if (typeof resourceUrl == 'string') {\n resourceUrl2 = () => resourceUrl;\n } else {\n resourceUrl2 = (pkg) =>\n resourceUrl(pkg.version, pkg.name, pkg.importName, pkg.resolveName);\n }\n externalResource2[k] = {\n resourceName: resourceName2,\n resourceUrl: resourceUrl2,\n };\n } else {\n const { resourceUrl, loader, nodeLoader, resourceName } = v;\n let resourceUrl2: Pkg2UrlFn;\n let resourceName2: Pkg2UrlFn = () => k;\n let nodeLoader2:\n | undefined\n | ((pkgOptions: PkgOptions) => IPromise<string>) = undefined;\n if (typeof resourceUrl == 'string') {\n resourceUrl2 = () => resourceUrl;\n } else {\n resourceUrl2 = resourceUrl;\n }\n if (typeof resourceName == 'string') {\n resourceName2 = () => resourceName;\n } else if (typeof resourceName == 'function') {\n resourceName2 = resourceName;\n }\n if (typeof nodeLoader == 'function') {\n nodeLoader2 = nodeLoader;\n } else if (typeof nodeLoader == 'string') {\n nodeLoader2 = () => nodeLoader;\n }\n externalResource2[k] = {\n resourceName: resourceName2,\n resourceUrl: resourceUrl2,\n loader,\n nodeLoader: nodeLoader2,\n };\n }\n }\n\n const server = pluginOption.server ?? {};\n\n const { prefix } = server;\n let prefix2: (name: string) => string = (s) => 'server:' + s;\n if (typeof prefix == 'function') {\n prefix2 = prefix;\n } else if (typeof prefix == 'string') {\n prefix2 = () => prefix;\n } else if (prefix === false) {\n prefix2 = (name: string) => name;\n }\n const externalGlobals2 = build?.externalGlobals ?? {};\n const externalGlobals: [string, IArray<string | Mod2UrlFn>][] = [];\n if (externalGlobals2 instanceof Array) {\n externalGlobals2.forEach((s) => externalGlobals.push(s));\n } else {\n Object.entries(externalGlobals2).forEach((s) => externalGlobals.push(s));\n }\n\n const { grant = [], $extra = [] } = pluginOption.userscript ?? {};\n let {\n name = {},\n description = {},\n 'exclude-match': excludeMatch = [],\n match = [],\n exclude = [],\n include = [],\n antifeature = [],\n require = [],\n connect = [],\n webRequest = [],\n } = pluginOption.userscript ?? {};\n if (typeof name == 'string') {\n name = { '': name };\n } else if (!('' in name)) {\n name = { '': projectPkg.name, ...name };\n }\n if (typeof description == 'string') {\n description = {\n '': description,\n };\n } else if (!('' in description) && projectPkg.description) {\n description = { '': projectPkg.description, ...description };\n }\n if (!(excludeMatch instanceof Array)) {\n excludeMatch = [excludeMatch];\n }\n if (!(match instanceof Array)) {\n match = [match];\n }\n if (!(exclude instanceof Array)) {\n exclude = [exclude];\n }\n if (!(include instanceof Array)) {\n include = [include];\n }\n if (!(antifeature instanceof Array)) {\n antifeature = [antifeature];\n }\n if (!(require instanceof Array)) {\n require = [require];\n }\n if (!(connect instanceof Array)) {\n connect = [connect];\n }\n if (!(webRequest instanceof Array)) {\n webRequest = [webRequest];\n }\n\n const grantSet = new Set<string>();\n if (typeof grant == 'string') {\n grantSet.add(grant);\n } else if (grant instanceof Array) {\n grant.forEach((s) => grantSet.add(s));\n }\n\n const extra: [string, ...string[]][] = [];\n ($extra instanceof Array ? $extra : Object.entries($extra)).forEach(\n ([k, v]) => {\n extra.push([k, ...(v instanceof Array ? v : [v])]);\n },\n );\n\n const {\n icon64,\n icon64URL,\n icon,\n iconURL,\n namespace,\n version = projectPkg.version,\n author = projectPkg.author,\n copyright,\n downloadURL,\n defaulticon,\n contributionURL,\n updateURL,\n supportURL = projectPkg.bugs,\n homepageURL = projectPkg.homepage,\n homepage = projectPkg.homepage,\n website,\n license = projectPkg.license,\n incompatible,\n source = projectPkg.repository,\n resource = {},\n noframes = false,\n 'run-at': runAt,\n 'inject-into': injectInto,\n contributionAmount,\n compatible,\n sandbox,\n tag,\n unwrap = false,\n } = pluginOption.userscript ?? {};\n\n const { fileName = projectPkg.name + '.user.js' } = build;\n let { metaFileName } = build;\n if (typeof metaFileName == 'string') {\n const t = metaFileName;\n metaFileName = () => t;\n } else if (metaFileName === true) {\n metaFileName = () => fileName.replace(/\\.user\\.js$/, '.meta.js');\n } else if (metaFileName === false) {\n metaFileName = undefined;\n }\n\n const metaFileFc = metaFileName;\n\n const cssSideEffects =\n build.cssSideEffects ||\n (() => {\n return (e: string) => {\n // @ts-ignore\n if (typeof GM_addStyle == 'function') {\n // @ts-ignore\n GM_addStyle(e);\n return;\n }\n const o = document.createElement('style');\n o.textContent = e;\n document.head.append(o);\n };\n });\n\n const config: FinalMonkeyOption = {\n userscript: {\n name,\n namespace,\n version,\n icon64,\n icon64URL,\n icon,\n iconURL,\n author,\n copyright,\n downloadURL,\n defaulticon,\n contributionURL,\n updateURL,\n supportURL,\n homepageURL,\n homepage,\n website,\n license,\n incompatible,\n source,\n resource,\n noframes,\n 'run-at': runAt,\n 'inject-into': injectInto,\n contributionAmount,\n compatible,\n 'exclude-match': excludeMatch.map((s) => String(s)),\n match: match.map((s) => String(s)),\n include: include.map((s) => String(s)),\n exclude: exclude.map((s) => String(s)),\n antifeature,\n require,\n connect,\n description,\n $extra: extra,\n grant: grantSet,\n sandbox,\n tag: tag ? (tag instanceof Array ? tag : [tag]) : [],\n unwrap,\n webRequest: webRequest.map((w) => JSON.stringify(w)),\n },\n clientAlias: pluginOption.clientAlias ?? '$',\n entry: pluginOption.entry,\n format: {\n align: pluginOption.format?.align ?? 2,\n generate: pluginOption.format?.generate ?? ((o) => o.userscript),\n },\n server: {\n mountGmApi: server.mountGmApi ?? false,\n open:\n server.open ??\n (process.platform == 'win32' || process.platform == 'darwin'),\n prefix: prefix2,\n },\n build: {\n fileName,\n metaFileName: metaFileFc ? () => metaFileFc(fileName) : undefined,\n autoGrant: build.autoGrant ?? true,\n externalGlobals: externalGlobals,\n externalResource: externalResource2,\n },\n collectRequireUrls: [],\n collectResource: {},\n globalsPkg2VarName: {},\n requirePkgList: [],\n systemjs: build.systemjs ?? jsdelivr()[1],\n cssSideEffects: async (css) => {\n const codeOrFc = await cssSideEffects(css);\n if (typeof codeOrFc == 'string') {\n return codeOrFc;\n }\n return miniCode(fn2string(codeOrFc, css), 'js');\n },\n };\n\n return config;\n};\n","import { DomUtils, ElementType, parseDocument } from 'htmlparser2';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { Plugin, ResolvedConfig, normalizePath } from 'vite';\nimport { logger } from '../_logger';\nimport { cyrb53hash, existFile, isFirstBoot, toValidURL } from '../_util';\nimport { fn2string, mountGmApiFn, serverInjectFn } from '../inject_template';\nimport { openBrowser } from '../open_browser';\nimport type { FinalMonkeyOption } from '../types';\nimport { gmIdentifiers } from '../gm_api';\nimport { finalMonkeyOptionToComment } from '../userscript';\n\nexport const installUserPath = '/__vite-plugin-monkey.install.user.js';\nconst gmApiPath = '/__vite-plugin-monkey.gm.api.js';\nconst entryPath = '/__vite-plugin-monkey.entry.js';\nconst pullPath = '/__vite-plugin-monkey.pull.js';\nconst restartStoreKey = '__vite_plugin_monkey_install_url';\n\nexport const serverPlugin = (finalOption: FinalMonkeyOption): Plugin => {\n let viteConfig: ResolvedConfig;\n return {\n name: 'monkey:server',\n apply: 'serve',\n async config(userConfig) {\n return {\n preview: {\n host: userConfig.preview?.host ?? '127.0.0.1',\n cors: true,\n },\n server: {\n host: userConfig.server?.host ?? '127.0.0.1',\n open: userConfig.server?.open ?? finalOption.server.open,\n cors: true,\n },\n };\n },\n async configResolved(resolvedConfig) {\n viteConfig = resolvedConfig;\n },\n async configureServer(server) {\n for (const [k, v] of Object.entries(finalOption.userscript.name)) {\n Reflect.set(\n finalOption.userscript.name,\n k,\n finalOption.server.prefix(v),\n );\n }\n\n // support dev env\n finalOption.userscript.grant.add('*');\n\n // https://github.com/lisonge/vite-plugin-monkey/issues/205\n server.middlewares.use((_, res, next) => {\n if (\n res.getHeader('Access-Control-Allow-Private-Network') === undefined\n ) {\n res.setHeader('Access-Control-Allow-Private-Network', 'true');\n }\n next();\n });\n\n server.middlewares.use(async (req, res, next) => {\n const reqUrl = req.url;\n if (\n req.method === 'GET' &&\n reqUrl &&\n [installUserPath, entryPath, pullPath, gmApiPath].some((u) =>\n reqUrl.startsWith(u),\n )\n ) {\n Object.entries({\n 'access-control-allow-origin': '*',\n 'content-type': 'application/javascript',\n }).forEach(([k, v]) => {\n res.setHeader(k, v);\n });\n const usp = new URLSearchParams(reqUrl.split('?', 2)[1]);\n\n if (reqUrl.startsWith(installUserPath)) {\n let origin = toValidURL(usp.get(`origin`))?.origin;\n if (!origin) {\n const { https, port } = viteConfig.server;\n let { host } = viteConfig.server;\n if (host == '0.0.0.0') {\n host = '127.0.0.1';\n }\n origin = `${https ? 'https' : 'http'}://${host}:${port}`;\n logger.warn(\n `can not get origin from install url query parameter, use ${origin}`,\n {\n time: true,\n },\n );\n }\n Reflect.set(globalThis, restartStoreKey, origin);\n const u = new URL(entryPath, origin);\n res.end(\n [\n await finalMonkeyOptionToComment(\n finalOption,\n new Set(),\n 'serve',\n ),\n fn2string(serverInjectFn, {\n entrySrc: u.href,\n }),\n '',\n ].join('\\n\\n'),\n );\n } else if (reqUrl.startsWith(entryPath)) {\n const htmlText = await server.transformIndexHtml(\n '/',\n `<html><head></head></html>`,\n req.originalUrl,\n );\n\n const doc = parseDocument(htmlText);\n type Element =\n ReturnType<typeof DomUtils.findAll> extends Array<infer T>\n ? T\n : never;\n const scriptList = DomUtils.getElementsByTagType(\n ElementType.Script,\n doc,\n ) as Element[];\n\n const entryList: string[] = finalOption.server.mountGmApi\n ? [gmApiPath]\n : [];\n scriptList.forEach((p) => {\n const src = p.attribs.src ?? '';\n const textNode = p.firstChild;\n let text = '';\n if (textNode?.type == ElementType.Text) {\n text = textNode.data ?? '';\n }\n if (src) {\n entryList.push(src);\n } else {\n const usp = new URLSearchParams({\n text: Buffer.from(text, 'utf-8').toString('base64url'),\n });\n entryList.push(pullPath + `?` + usp.toString());\n }\n });\n\n let realEntry = finalOption.entry;\n if (path.isAbsolute(realEntry)) {\n realEntry = normalizePath(\n path.relative(viteConfig.root, realEntry),\n );\n }\n const entryUrl = new URL(realEntry, 'http://127.0.0.1');\n entryList.push(entryUrl.pathname + entryUrl.search);\n res.end(\n entryList.map((s) => `import ${JSON.stringify(s)};`).join('\\n'),\n );\n } else if (reqUrl.startsWith(pullPath)) {\n res.end(\n Buffer.from(usp.get('text') ?? '', 'base64url').toString('utf-8'),\n );\n } else if (reqUrl.startsWith(gmApiPath)) {\n if (finalOption.server.mountGmApi) {\n res.end(\n `;(${mountGmApiFn})(import.meta, ${JSON.stringify(gmIdentifiers)});`,\n );\n } else {\n res.end('');\n }\n }\n return;\n }\n\n next();\n });\n\n if (finalOption.server.open) {\n const cacheUserPath = `node_modules/.vite/__vite-plugin-monkey.cache.${cyrb53hash(\n viteConfig.configFile,\n )}.user.js`;\n let cacheComment = '';\n if (await existFile(cacheUserPath)) {\n cacheComment = (await fs.readFile(cacheUserPath)).toString('utf-8');\n } else {\n await fs.mkdir(path.dirname(cacheUserPath)).catch(() => {});\n }\n const newComment = await finalMonkeyOptionToComment(\n finalOption,\n new Set(),\n 'serve',\n );\n const installUrl = Reflect.get(globalThis, restartStoreKey);\n if (!isFirstBoot() && cacheComment != newComment && installUrl) {\n openBrowser(installUrl, true, logger);\n logger.info('reopen, config comment has changed', { time: true });\n }\n await fs.writeFile(cacheUserPath, newComment).catch(() => {});\n }\n },\n };\n};\n","import spawn from 'cross-spawn';\nimport { execSync } from 'node:child_process';\nimport path from 'node:path';\nimport type { Options } from 'open';\nimport colors from 'picocolors';\nimport type { Logger } from './_logger';\nimport { compatResolve } from './_util';\n\n// https://github.com/sindresorhus/open#app\nconst OSX_CHROME = 'google chrome';\n\n/**\n * Reads the BROWSER environment variable and decides what to do with it.\n * Returns true if it opened a browser or ran a node.js script, otherwise false.\n */\nexport function openBrowser(\n url: string,\n opt: string | true,\n logger: Logger,\n): boolean {\n // The browser executable to open.\n // See https://github.com/sindresorhus/open#app for documentation.\n const browser = typeof opt === 'string' ? opt : process.env.BROWSER || '';\n if (browser.toLowerCase().endsWith('.js')) {\n return executeNodeScript(browser, url, logger);\n } else if (browser.toLowerCase() !== 'none') {\n return startBrowserProcess(browser, url);\n }\n return false;\n}\n\nfunction executeNodeScript(scriptPath: string, url: string, logger: Logger) {\n const extraArgs = process.argv.slice(2);\n const child = spawn(process.execPath, [scriptPath, ...extraArgs, url], {\n stdio: 'inherit',\n });\n child.on('close', (code: unknown) => {\n if (code !== 0) {\n logger.error(\n colors.red(\n `\\nThe script specified as BROWSER environment variable failed.\\n\\n${colors.cyan(\n scriptPath,\n )} exited with code ${code}.`,\n ),\n { time: true },\n );\n }\n });\n return true;\n}\n\nfunction startBrowserProcess(browser: string | undefined, url: string) {\n // If we're on OS X, the user hasn't specifically\n // requested a different browser, we can try opening\n // Chrome with AppleScript. This lets us reuse an\n // existing tab when possible instead of creating a new one.\n const shouldTryOpenChromeWithAppleScript =\n process.platform === 'darwin' && (browser === '' || browser === OSX_CHROME);\n\n if (shouldTryOpenChromeWithAppleScript) {\n try {\n // Try our best to reuse existing tab\n // on OS X Google Chrome with AppleScript\n execSync('ps cax | grep \"Google Chrome\"');\n execSync('osascript openChrome.applescript \"' + encodeURI(url) + '\"', {\n cwd: path.dirname(compatResolve('vite/bin/openChrome.applescript')),\n stdio: 'ignore',\n });\n return true;\n } catch {\n // Ignore errors\n }\n }\n\n // Another special case: on OS X, check if BROWSER has been set to \"open\".\n // In this case, instead of passing the string `open` to `open` function (which won't work),\n // just ignore it (thus ensuring the intended behavior, i.e. opening the system browser):\n // https://github.com/facebook/create-react-app/pull/1690#issuecomment-283518768\n if (process.platform === 'darwin' && browser === 'open') {\n browser = undefined;\n }\n\n // Fallback to open\n // (It will always open new tab)\n try {\n const options: Options = browser ? { app: { name: browser } } : {};\n // open is esm only, cjs need\n import('open').then(({ default: open }) => {\n open(url, options).catch(() => {}); // Prevent `unhandledRejection` error.\n });\n return true;\n } catch {\n return false;\n }\n}\n","import { grantNames, type GrantType } from '../gm_api';\nimport type { FinalMonkeyOption, IArray, LocaleType } from '../types';\nimport type { Format } from './common';\nimport type { GreasemonkeyUserScript, GreaseRunAt } from './greasemonkey';\nimport type {\n AntifeatureType,\n TampermonkeyUserScript,\n TamperRunAt,\n} from './tampermonkey';\nimport type { ViolentmonkeyUserScript, ViolentRunAt } from './violentmonkey';\nimport { ViolentInjectInto } from './violentmonkey';\n\nexport type {\n Format,\n GreasemonkeyUserScript,\n TampermonkeyUserScript,\n ViolentmonkeyUserScript,\n};\n\n/**\n * @see https://greasyfork.org/help/meta-keys\n */\ninterface GreasyforkUserScript {\n /**\n * @see https://greasyfork.org/help/meta-keys\n * @default package.json.license\n */\n license?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n contributionURL?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n contributionAmount?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n compatible?: string;\n\n /**\n * @see https://greasyfork.org/help/meta-keys\n */\n incompatible?: string;\n}\n\ninterface MergemonkeyUserScript {\n /**\n * @default package.json.name??'monkey'\n * @default {...{'':package.json.name??'monkey'},...name} // if name is object\n */\n name?: string | LocaleType<string>;\n\n namespace?: string;\n\n /**\n * @default package.json.version\n */\n version?: string;\n\n /**\n * @default package.json.description\n * @default {...{'':package.json.description},...description} // if description is object\n */\n description?: string | LocaleType<string>;\n\n /**\n * @default package.json.author\n */\n author?: string;\n\n /**\n * @default package.json.homepage\n */\n homepage?: string;\n\n /**\n * @default package.json.homepage\n */\n homepageURL?: string;\n\n /**\n * @default package.json.repository\n */\n source?: string;\n\n /**\n * @default package.json.bugs\n */\n supportURL?: string;\n\n /**\n * @see https://wiki.greasespot.net/Metadata_Block#.40run-at\n *\n * @see https://www.ta