diff options
author | 2025-02-24 01:09:00 +0100 | |
---|---|---|
committer | 2025-02-24 01:09:00 +0100 | |
commit | 54a41f2431c3d60f5845a15447f13413299e41f2 (patch) | |
tree | f9395bb35ae4223a8ee944299ce430168de4d657 | |
download | httptool-54a41f2431c3d60f5845a15447f13413299e41f2.tar.gz httptool-54a41f2431c3d60f5845a15447f13413299e41f2.tar.bz2 httptool-54a41f2431c3d60f5845a15447f13413299e41f2.tar.lz httptool-54a41f2431c3d60f5845a15447f13413299e41f2.zip |
feat: da extension
-rw-r--r-- | .gitignore | 24 | ||||
-rw-r--r-- | .npmrc | 1 | ||||
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | adapter/index.d.ts | 17 | ||||
-rw-r--r-- | adapter/index.js | 192 | ||||
-rwxr-xr-x | browser-launcher | 36 | ||||
-rwxr-xr-x | do-webext-dev | 3 | ||||
-rw-r--r-- | examples/breeze-wiki.ts | 20 | ||||
-rw-r--r-- | examples/i2p.ts | 48 | ||||
-rw-r--r-- | examples/imgur-redirect.ts | 15 | ||||
-rw-r--r-- | manifest.json | 30 | ||||
-rwxr-xr-x | on-change | 15 | ||||
-rw-r--r-- | package.json | 44 | ||||
-rw-r--r-- | pnpm-lock.yaml | 4482 | ||||
-rw-r--r-- | src/app.css | 9 | ||||
-rw-r--r-- | src/app.d.ts | 17 | ||||
-rw-r--r-- | src/app.html | 12 | ||||
-rw-r--r-- | src/background.ts | 67 | ||||
-rw-r--r-- | src/lib/index.ts | 1 | ||||
-rw-r--r-- | src/routes/+layout.svelte | 20 | ||||
-rw-r--r-- | src/routes/+layout.ts | 2 | ||||
-rw-r--r-- | src/routes/+page.svelte | 42 | ||||
-rw-r--r-- | src/routes/Monaco.svelte | 144 | ||||
-rw-r--r-- | src/routes/browser.d.ts | 4 | ||||
-rw-r--r-- | src/routes/userland.d.ts | 4 | ||||
-rw-r--r-- | static/favicon.png | bin | 0 -> 1571 bytes | |||
l--------- | static/manifest.json | 1 | ||||
-rw-r--r-- | static/proxy.html | 30 | ||||
-rw-r--r-- | static/proxy.js | 6 | ||||
-rw-r--r-- | svelte.config.js | 53 | ||||
-rw-r--r-- | tsconfig.json | 19 | ||||
-rw-r--r-- | vite.config.background.ts | 20 | ||||
-rw-r--r-- | vite.config.ts | 16 |
33 files changed, 5401 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..55b7ad4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build +/static/generated + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* @@ -0,0 +1 @@ +engine-strict=true diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ea519f --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# httptool + +An extension that simply exposes the [browser.webRequest](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/webRequest) APIs. + +## Applying Changes + +Changes are autosaved on edit, however to apply them, you need to disable and re-enable the addon, or restart the browser. This is to prevent us from needing to handle unregistering & re-registering anything. diff --git a/adapter/index.d.ts b/adapter/index.d.ts new file mode 100644 index 0000000..19e34f6 --- /dev/null +++ b/adapter/index.d.ts @@ -0,0 +1,17 @@ +import { Adapter } from '@sveltejs/kit'; + +interface AdapterOptions { + pages?: string; + assets?: string; + fallback?: string; + precompress?: boolean; + manifest?: string; + emptyOutDir?: boolean; + paths?: { + base?: string; + app?: string; + }; +} + +declare function plugin(options?: AdapterOptions): Adapter; +export = plugin; diff --git a/adapter/index.js b/adapter/index.js new file mode 100644 index 0000000..03e2730 --- /dev/null +++ b/adapter/index.js @@ -0,0 +1,192 @@ +// https://github.com/michmich112/sveltekit-adapter-chrome-extension +import staticAdapter from '@sveltejs/adapter-static'; +import { load } from 'cheerio'; +import { + createReadStream, + createWriteStream, + existsSync, + readFileSync, + statSync, + unlinkSync, + writeFileSync, +} from 'fs'; +import { join } from 'path'; +import { pipeline } from 'stream'; +import glob from 'tiny-glob'; +import { promisify } from 'util'; +import zlib from 'zlib'; + +const pipe = promisify(pipeline); + +/** @type {import('.')} */ +export default function (options) { + return { + name: 'sveltekit-adapter-chrome-extension', + + async adapt(builder) { + staticAdapter(options).adapt(builder); + + /* extension */ + const pages = options?.pages ?? 'build'; + const assets = options?.assets ?? pages; + const manifest = options?.manifest ?? 'manifest.json'; + + await removeInlineScripts(options, assets, builder.log); + + await removeAppManifest(assets, builder.config.kit.appDir, builder.log); + await removeAppManifest('.', assets, builder.log); + + // operation required since generated app manifest will overwrite the static extension manifest.json + reWriteExtensionManifest(assets, manifest, builder); + }, + }; +} + +/** + * Hash using djb2 + * @param {import('types/hooks').StrictBody} value + */ +function hash(value) { + let hash = 5381; + let i = value.length; + + if (typeof value === 'string') { + while (i) hash = (hash * 33) ^ value.charCodeAt(--i); + } else { + while (i) hash = (hash * 33) ^ value[--i]; + } + + return (hash >>> 0).toString(36); +} + +async function removeAppManifest(directory, appDir, log) { + log('Removing App Manifest'); + const files = await glob(`**/${appDir}/*manifest*.json`, { + cwd: directory, + dot: true, + absolute: true, + filesOnly: true, + }); + + files.forEach((path) => { + try { + unlinkSync(path); + log.success(`Removed app manifest file at path: ${path}`); + } catch (err) { + log.warn( + `Error removing app manifest file at path: ${path}. You may have to delete it manually before submitting you extension.\nError: ${err}` + ); + } + }); +} + +async function removeInlineScripts(options, directory, log) { + log('Removing Inline Scripts'); + const files = await glob('**/*.{html}', { + cwd: directory, + dot: true, + aboslute: true, + filesOnly: true, + }); + + files + .map((f) => join(directory, f)) + .forEach((file) => { + log.minor(`file: ${file}`); + const f = readFileSync(file); + const $ = load(f.toString()); + const node = $('script').get()[0]; + + if (!node) return; + if (Object.keys(node.attribs).includes('src')) return; // if there is a src, it's not an inline script + + const attribs = Object.keys(node.attribs).reduce( + (a, c) => a + `${c}="${node.attribs[c]}" `, + '' + ); + let innerScript = node.children[0].data; + const fullTag = $('script').toString(); + //get new filename + const fn = `${ + options?.paths?.app ? '/' + options.paths.app + '/immutable' : '.' + }/inline-${hash(innerScript)}.js`; + if (options?.paths?.app) + innerScript = innerScript.replaceAll(options.paths.app + '/', '../'); + //remove from orig html file and replace with new script tag + const newHtml = f + .toString() + .replace( + fullTag, + `<script ${attribs} src=${JSON.stringify( + (options?.paths?.base ?? '/') + fn + )}></script>` + ); + writeFileSync(file, newHtml); + log.minor(`Rewrote ${file}`); + + const p = `${directory}${fn}`; + writeFileSync(p, innerScript); + log.success(`Inline script extracted and saved at: ${p}`); + }); +} + +function reWriteExtensionManifest(directory, manifest, builder) { + const { log, getStaticDirectory, getClientDirectory, copy } = builder; + log('Re-writing extension manifest'); + let sourceFilePath; + if (typeof getStaticDirectory !== 'undefined') { + sourceFilePath = join(getStaticDirectory(), manifest); + } else { + sourceFilePath = join(getClientDirectory(), manifest); + } + if (existsSync(sourceFilePath)) { + log.info('Extension manifest found'); + const res = copy(sourceFilePath, join(directory, 'manifest.json')); + log.success('Successfully re-wrote extension manifest'); + } else { + log.error( + `Extension manifest not found. Make sure you've added your extension manifest in your statics directory with the name ${manifest}` + ); + } +} + +/** + * @param {string} directory + */ +async function compress(directory) { + const files = await glob('**/*.{html,js,json,css,svg,xml}', { + cwd: directory, + dot: true, + absolute: true, + filesOnly: true, + }); + + await Promise.all( + files.map((file) => + Promise.all([compress_file(file, 'gz'), compress_file(file, 'br')]) + ) + ); +} + +/** + * @param {string} file + * @param {'gz' | 'br'} format + */ +async function compress_file(file, format = 'gz') { + const compress = + format == 'br' + ? zlib.createBrotliCompress({ + params: { + [zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT, + [zlib.constants.BROTLI_PARAM_QUALITY]: + zlib.constants.BROTLI_MAX_QUALITY, + [zlib.constants.BROTLI_PARAM_SIZE_HINT]: statSync(file).size, + }, + }) + : zlib.createGzip({ level: zlib.constants.Z_BEST_COMPRESSION }); + + const source = createReadStream(file); + const destination = createWriteStream(`${file}.${format}`); + + await pipe(source, compress, destination); +} diff --git a/browser-launcher b/browser-launcher new file mode 100755 index 0000000..0213cbb --- /dev/null +++ b/browser-launcher @@ -0,0 +1,36 @@ +#!/bin/bash + +POSITIONAL_ARGS=() + +while [[ $# -gt 0 ]]; do + case $1 in + --*) + POSITIONAL_ARGS+=("$1") + shift + ;; + -*) + POSITIONAL_ARGS+=("-$1") + shift + ;; + *) + POSITIONAL_ARGS+=("$1") + shift + ;; + esac +done + +POSITIONAL_ARGS+=( + "--marionette" + "--preferences" +) +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +if which firefox >/dev/null 2>/dev/null; then + exec firefox "$@" +elif which librewolf >/dev/null 2>/dev/null; then + # not first pick due to it dying somehow randomly + exec librewolf "$@" +else + echo "No Supported Browser!" 1>&2; + exit 1 +fi diff --git a/do-webext-dev b/do-webext-dev new file mode 100755 index 0000000..72ebb42 --- /dev/null +++ b/do-webext-dev @@ -0,0 +1,3 @@ +#!/bin/bash +set -euo pipefail +./on-change r | socat - EXEC:'web-ext run -p ff-ext --reload -f ./browser-launcher -s ./build',pty,setsid,ctty diff --git a/examples/breeze-wiki.ts b/examples/breeze-wiki.ts new file mode 100644 index 0000000..7e0d5d6 --- /dev/null +++ b/examples/breeze-wiki.ts @@ -0,0 +1,20 @@ +// This example redirects all fandom pages to breezewiki (or for minecraft.fandom.com, the minecraft wiki) + +import type Browser from 'webextension-polyfill'; +declare const browser: typeof Browser; +// ^ above 2 lines are optional, and only useful to allow the examples directory to not complain. The extension's monaco already defines the type of the browser global. + +browser.webRequest.onBeforeRequest.addListener( + (requestDetails) => { + const url = new URL(requestDetails.url); + url.host = + url.hostname === 'minecraft.fandom.com' + ? 'minecraft.wiki' + : url.hostname.replace('fandom.com', 'breezewiki.com'); + return { + redirectUrl: url.href, + }; + }, + { urls: ['https://fandom.com/*', 'https://*.fandom.com/*'] }, + ['blocking'] +); diff --git a/examples/i2p.ts b/examples/i2p.ts new file mode 100644 index 0000000..b9b29c4 --- /dev/null +++ b/examples/i2p.ts @@ -0,0 +1,48 @@ +// This example allows entering i2p eepsites into your URL bar - and assuming you're using duckduckgo, it will send you to the eepsite instead. + +import type Browser from 'webextension-polyfill'; +declare const browser: typeof Browser; +// ^ above 2 lines are optional, and only useful to allow the examples directory to not complain. The extension's monaco already defines the type of the browser global. + +browser.webRequest.onBeforeRequest.addListener( + (requestDetails) => { + const url = new URL(requestDetails.url); + const params = url.searchParams; + let query = params.get('q'); + if (!query || !query.includes('.i2p')) { + return; + } else { + try { + query = query.trim(); + if (!query.startsWith('http://') && !query.startsWith('https://')) + query = `https://${query}`; + let url = new URL(query); + if (url.hostname === 'uwu' && url.protocol === 'https') { + url.protocol = 'http'; + url = new URL(url.href.replace('bad.b32.i2p/', '')); + } + if (url.hostname.endsWith('.i2p')) + return { + redirectUrl: url.href, + }; + else return; + } catch (error) { + console.warn( + 'Failed to get i2p link for', + query, + '-', + error, + '\nThis may be someone just searching a genuine search.' + ); + // Comment the below out if you don't want to cancel failed attempts at loading eepsites. Note that continuing as if nothing happened can leak information. It's recommended to only ever temporarily comment it out. + return { + cancel: true, + }; + } + } + }, + { + urls: ['https://duckduckgo.com/*q=*', 'https://*.duckduckgo.com/*q=*'], + }, + ['blocking'] +); diff --git a/examples/imgur-redirect.ts b/examples/imgur-redirect.ts new file mode 100644 index 0000000..97d7c81 --- /dev/null +++ b/examples/imgur-redirect.ts @@ -0,0 +1,15 @@ +import type Browser from 'webextension-polyfill'; +declare const browser: typeof Browser; +// ^ above 2 lines are optional, and only useful to allow the examples directory to not complain. The extension's monaco already defines the type of the browser global. + +browser.webRequest.onBeforeRequest.addListener( + (requestDetails) => { + const url = new URL(requestDetails.url); + url.host = 'imgur.010032.xyz'; + return { + redirectUrl: url.href, + }; + }, + { urls: ['https://imgur.com/*', 'https://www.imgur.com/*'] }, + ['blocking'] +); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..030c757 --- /dev/null +++ b/manifest.json @@ -0,0 +1,30 @@ +{ + "description": "A minimal extension for manipulation of HTTP Requests and Responses; a fancy wrapper around browser.webRequest", + "manifest_version": 2, + "name": "httptool", + "version": "1.0", + + "permissions": [ + "webRequest", + "webRequestBlocking", + "storage", + "theme", + "<all_urls>", + "unlimitedStorage" + ], + "optional_permissions": [], + + "background": { + "scripts": ["generated/background.js"], + "type": "module" + }, + "options_ui": { + "page": "proxy.html" + }, + "browser_specific_settings": { + "gecko": { + "id": "redir@git.estrogen.zone" + } + }, + "content_security_policy": "script-src 'unsafe-eval' 'self'" +} diff --git a/on-change b/on-change new file mode 100755 index 0000000..19d47f0 --- /dev/null +++ b/on-change @@ -0,0 +1,15 @@ +#!/bin/bash +sleep 5; +r() { + local V="{}" + while true; do + local NV="$(cat build/uwu/version.json build/generated/background.iife.js)" + if [[ "$V" != "$NV" ]]; then + echo -ne "\x1b[2K\x1b[1GRequesting reload\x1b[1G" 1>&2; + echo "$1" + V="$NV" + fi + sleep 0.5; + done +} +r "$@"
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..9a49872 --- /dev/null +++ b/package.json @@ -0,0 +1,44 @@ +{ + "name": "redirext", + "private": true, + "version": "0.0.1", + "type": "module", + "scripts": { + "dev": "concurrently -k --restart-tries -1 --restart-after 1000 \"vite build --mode development --watch\" \"vite build --mode development --watch --config vite.config.background.ts\" \"./do-webext-dev\"", + "build": "concurrently --kill-others-on-fail \"vite build\" \"vite build --config vite.config.background.ts\"", + "preview": "web-ext run -p ff-ext --reload -f ./browser-launcher -s ./build", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "install": "mkdir -p static/generated && cp node_modules/esbuild-wasm/esbuild.wasm static/generated/esbuild.wasm" + }, + "devDependencies": { + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tailwindcss/vite": "^4.0.0", + "@types/webextension-polyfill": "0.12.1", + "autoprefixer": "^10.4.20", + "cheerio": "^1.0.0", + "concurrently": "9.1.2", + "esbuild": "^0.25.0", + "monaco-editor": "^0.52.2", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "svelte-preprocess": "^6.0.3", + "tailwindcss": "^4.0.0", + "tiny-glob": "^0.2.9", + "typescript": "^5.0.0", + "vite": "^6.0.0", + "web-ext": "^8.4.0", + "webextension-polyfill": "^0.12.0" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild" + ] + }, + "dependencies": { + "esbuild-wasm": "^0.25.0" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..5af7044 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,4482 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + esbuild-wasm: + specifier: ^0.25.0 + version: 0.25.0 + devDependencies: + '@sveltejs/adapter-static': + specifier: ^3.0.8 + version: 3.0.8(@sveltejs/kit@2.17.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)))(svelte@5.20.2)(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1))) + '@sveltejs/kit': + specifier: ^2.16.0 + version: 2.17.2(@sveltejs/vite-plugin-svelte@5.0.3(svelte@5.20.2)(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)))(svelte@5.20.2)(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)) + '@sveltejs/vite-plugin-svelte': + specifier: ^5.0.0 + version: 5.0.3(svelte@5.20.2)(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)) + '@tailwindcss/vite': + specifier: ^4.0.0 + version: 4.0.8(vite@6.1.1(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1)) + '@types/webextension-polyfill': + specifier: 0.12.1 + version: 0.12.1 + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.5.3) + cheerio: + specifier: ^1.0.0 + version: 1.0.0 + concurrently: + specifier: 9.1.2 + version: 9.1.2 + esbuild: + specifier: ^0.25.0 + version: 0.25.0 + monaco-editor: + specifier: ^0.52.2 + version: 0.52.2 + svelte: + specifier: ^5.0.0 + version: 5.20.2 + svelte-check: + specifier: ^4.0.0 + version: 4.1.4(svelte@5.20.2)(typescript@5.7.3) + svelte-preprocess: + specifier: ^6.0.3 + version: 6.0.3(postcss@8.5.3)(svelte@5.20.2)(typescript@5.7.3) + tailwindcss: + specifier: ^4.0.0 + version: 4.0.8 + tiny-glob: + specifier: ^0.2.9 + version: 0.2.9 + typescript: + specifier: ^5.0.0 + version: 5.7.3 + vite: + specifier: ^6.0.0 + version: 6.1.1(@types/node@22.13.4)(jiti@2.4.2)(lightningcss@1.29.1) + web-ext: + specifier: ^8.4.0 + version: 8.4.0 + webextension-polyfill: + specifier: ^0.12.0 + version: 0.12.0 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.26.7': + resolution: {integrity: sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==} + engines: {node: '>=6.9.0'} + + '@devicefarmer/adbkit-logcat@2.1.3': + resolution: {integrity: sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==} + engines: {node: '>= 4'} + + '@devicefarmer/adbkit-monkey@1.2.1': + resolution: {integrity: sha512-ZzZY/b66W2Jd6NHbAhLyDWOEIBWC11VizGFk7Wx7M61JZRz7HR9Cq5P+65RKWUU7u6wgsE8Lmh9nE4Mz+U2eTg==} + engines: {node: '>= 0.10.4'} + + '@devicefarmer/adbkit@3.3.8': + resolution: {integrity: sha512-7rBLLzWQnBwutH2WZ0EWUkQdihqrnLYCUMaB44hSol9e0/cdIhuNFcqZO0xNheAU6qqHVA8sMiLofkYTgb+lmw==} + engines: {node: '>= 0.10.4'} + hasBin: true + + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.25.0': + resolution: {integrity: sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.25.0': + resolution: {integrity: sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.25.0': + resolution: {integrity: sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.25.0': + resolution: {integrity: sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.25.0': + resolution: {integrity: sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.0': + resolution: {integrity: sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.25.0': + resolution: {integrity: sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.0': + resolution: {integrity: sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.25.0': + resolution: {integrity: sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.25.0': + resolution: {integrity: sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.25.0': + resolution: {integrity: sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.25.0': + resolution: {integrity: sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.25.0': + resolution: {integrity: sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.25.0': + resolution: {integrity: sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + |