From 6bf306d0344a37f66d9f66002162dad8e0d0deae Mon Sep 17 00:00:00 2001 From: mtvpls Date: Thu, 18 Jun 2026 15:38:24 +0800 Subject: [PATCH] =?UTF-8?q?edgeone=20middleware=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F=E6=AD=A3=E7=A1=AE=E8=AF=BB=E5=8F=96=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/edgeone-build.mjs | 236 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) diff --git a/scripts/edgeone-build.mjs b/scripts/edgeone-build.mjs index 0846714..6d01a84 100644 --- a/scripts/edgeone-build.mjs +++ b/scripts/edgeone-build.mjs @@ -1,5 +1,226 @@ #!/usr/bin/env node import { spawn } from 'node:child_process'; +import { copyFileSync, existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; + +function loadLocalEnvFile() { + const envPath = join(process.cwd(), '.env'); + if (!existsSync(envPath)) { + return; + } + + const content = readFileSync(envPath, 'utf8'); + for (const rawLine of content.split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line || line.startsWith('#')) { + continue; + } + + const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/); + if (!match || process.env[match[1]] !== undefined) { + continue; + } + + let value = match[2].trim(); + const quote = value[0]; + if ((quote === '"' || quote === "'") && value.endsWith(quote)) { + value = value.slice(1, -1); + } + if (quote === '"') { + value = value.replace(/\\n/g, '\n').replace(/\\"/g, '"').replace(/\\\\/g, '\\'); + } + + process.env[match[1]] = value; + } +} + +loadLocalEnvFile(); + +const runtimeEnvKeys = [ + 'USERNAME', + 'PASSWORD', + 'NEXT_PUBLIC_STORAGE_TYPE', + 'UPSTASH_URL', + 'UPSTASH_TOKEN', + 'TMDB_API_KEY', + 'TMDB_IMAGE_DOMAIN', + 'NEXT_PUBLIC_SITE_NAME', + 'ANNOUNCEMENT', + 'ENABLE_REGISTER', + 'NEXT_PUBLIC_SEARCH_MAX_PAGE', + 'NEXT_PUBLIC_DOUBAN_PROXY_TYPE', + 'NEXT_PUBLIC_DOUBAN_PROXY', + 'NEXT_PUBLIC_DOUBAN_IMAGE_PROXY_TYPE', + 'NEXT_PUBLIC_DOUBAN_IMAGE_PROXY', + 'NEXT_PUBLIC_ENABLE_OFFLINE_DOWNLOAD', + 'NEXT_PUBLIC_IMAGE_PROXY', + 'NEXT_PUBLIC_IMAGE_PROXY_PREFIX', + 'NEXT_PUBLIC_DISABLE_YELLOW_FILTER', + 'DISABLE_USER_FOLDER', + 'NEXT_PUBLIC_STORAGE_PREFIX', + 'NEXT_PUBLIC_ENABLE_STREAM_PROXY', + 'NEXT_PUBLIC_STREAM_PROXY_URL', + 'NEXT_PUBLIC_ENABLE_TURNSTILE', + 'TURNSTILE_SECRET_KEY', + 'NEXT_PUBLIC_TURNSTILE_SITE_KEY', + 'NEXT_PUBLIC_THEME', + 'NEXT_PUBLIC_THEME_COLOR', + 'NEXT_PUBLIC_APP_URL', + 'ENABLE_TVBOX_SUBSCRIBE', + 'TVBOX_SUBSCRIBE_PATH', + 'TVBOX_SUBSCRIBE_TOKEN', + 'WATCH_ROOM_ENABLED', + 'WATCH_ROOM_WS_URL', + 'WATCH_ROOM_SERVER_URL', + 'WEBSOCKET_SECRET', + 'WEB_PUSH_EMAIL', + 'WEB_PUSH_PRIVATE_KEY', + 'WEB_PUSH_PUBLIC_KEY', + 'WEB_PUSH_BASEURL', + 'NEXT_PUBLIC_BASE_PATH', +]; + +const edgeOneMiddlewareSkipPaths = [ + '/login', + '/register', + '/oidc-register', + '/qr-login', + '/warning', + '/tv/login', + '/api/login', + '/api/register', + '/api/logout', + '/api/auth/oidc', + '/api/auth/qr', + '/api/auth/refresh', + '/api/server-config', + '/api/tvbox/subscribe', +]; + +function getRuntimeEnvLiteral() { + const env = {}; + for (const key of runtimeEnvKeys) { + if (process.env[key] !== undefined) { + env[key] = process.env[key]; + } + } + return JSON.stringify(env); +} + +function replaceEnvLiterals(code, envLiteral) { + let output = ''; + let cursor = 0; + let replaced = 0; + const needle = 'env: {'; + + while (true) { + const start = code.indexOf(needle, cursor); + if (start === -1) { + output += code.slice(cursor); + break; + } + + let i = start + 'env: '.length; + let depth = 0; + let inString = false; + let escaped = false; + + for (; i < code.length; i += 1) { + const char = code[i]; + + if (inString) { + if (escaped) { + escaped = false; + } else if (char === '\\') { + escaped = true; + } else if (char === '"') { + inString = false; + } + continue; + } + + if (char === '"') { + inString = true; + } else if (char === '{') { + depth += 1; + } else if (char === '}') { + depth -= 1; + if (depth === 0) { + i += 1; + break; + } + } + } + + if (depth !== 0) { + console.warn('[edgeone-build] Unable to replace an env literal: malformed object'); + output += code.slice(cursor); + break; + } + + output += code.slice(cursor, start) + `env: ${envLiteral}`; + cursor = i; + replaced += 1; + } + + if (replaced > 0) { + console.log(`[edgeone-build] Replaced ${replaced} generated env literal(s) with filtered runtime env`); + } + + return output; +} + +function patchEdgeFunctionEnvInjection() { + const edgeFunctionPath = join(process.cwd(), '.edgeone', 'edge-functions', 'index.js'); + let code; + try { + code = readFileSync(edgeFunctionPath, 'utf8'); + } catch { + return; + } + + const marker = '/* edgeone-process-env-injected */'; + const envLiteral = getRuntimeEnvLiteral(); + + code = replaceEnvLiterals(code, envLiteral); + + const target = 'let request = context.request;'; + if (!code.includes(marker) && !code.includes(target)) { + console.warn('[edgeone-build] Unable to patch edge function env injection: target not found'); + } else if (!code.includes(marker)) { + code = code.replace( + target, + `${target}\n ${marker}\n if (typeof globalThis !== 'undefined' && globalThis.process?.env && context?.env) {\n Object.assign(globalThis.process.env, context.env);\n }` + ); + } + + const middlewareSignature = 'async function executeMiddleware({request}) {'; + const middlewareMarker = '/* edgeone-middleware-env-injected */'; + if (!code.includes(middlewareMarker) && !code.includes(middlewareSignature)) { + console.warn('[edgeone-build] Unable to patch middleware env injection: target not found'); + } else if (!code.includes(middlewareMarker)) { + code = code.replace( + middlewareSignature, + `async function executeMiddleware({request, env}) {\n ${middlewareMarker}\n if (typeof globalThis !== 'undefined' && globalThis.process?.env && env) {\n Object.assign(globalThis.process.env, env);\n }` + ); + } + + const matcherFallbackMarker = '/* edgeone-middleware-matcher-fallback */'; + const matcherFallbackTarget = ` if (!matchesPath(pathname, config.matcher)) { + return null; + }`; + if (!code.includes(matcherFallbackMarker) && !code.includes(matcherFallbackTarget)) { + console.warn('[edgeone-build] Unable to patch middleware matcher fallback: target not found'); + } else if (!code.includes(matcherFallbackMarker)) { + code = code.replace( + matcherFallbackTarget, + `${matcherFallbackTarget}\n\n ${matcherFallbackMarker}\n const edgeOneMiddlewareSkipPaths = ${JSON.stringify(edgeOneMiddlewareSkipPaths)};\n if (edgeOneMiddlewareSkipPaths.some((path) => pathname.startsWith(path))) {\n return null;\n }` + ); + } + + writeFileSync(edgeFunctionPath, code); + console.log('[edgeone-build] Patched edge function process.env injection'); +} const isInsideEdgeOneBuilder = process.env.NEXT_PRIVATE_STANDALONE === 'true'; @@ -18,6 +239,21 @@ const child = spawn(command, { }); child.on('exit', (code, signal) => { + if (!signal && code === 0) { + for (const file of ['edgeone.json', 'package.json']) { + copyFileSync(join(process.cwd(), file), join(process.cwd(), '.edgeone', file)); + } + + patchEdgeFunctionEnvInjection(); + + for (const envPath of [ + join(process.cwd(), '.edgeone', '.env'), + join(process.cwd(), '.edgeone', 'cloud-functions', 'ssr-node', '.env'), + ]) { + rmSync(envPath, { force: true }); + } + } + if (signal) { process.kill(process.pid, signal); return;