// Runtime language adapters derived from the retained panel preloaders. // Keep these sources readable: the build embeds them without extra runtime files. export const nodePreloadSource: string = [ "const { execFileSync } = require('child_process');", "const Module = require('module');", "const path = require('path');", "const client = require('./client.js');", 'require(`./env.js`);', '', '// 注册 ESM loader,使全局安装的包也可通过 import 导入', 'try {', " Module.register(require('node:url').pathToFileURL(path.join(__dirname, 'esm-loader.mjs')).href);", '} catch (_) {}', '', 'function preferGlobalNodeModules() {', ' const { QL_NODE_GLOBAL_PATH } = process.env;', ' if (!QL_NODE_GLOBAL_PATH || Module._qlGlobalPathPatched) {', ' return;', ' }', '', ' const originalResolveFilename = Module._resolveFilename;', ' Module._resolveFilename = function (request, parent, isMain, options) {', ' if (', ' !Module.builtinModules.includes(request) &&', " !request.startsWith('node:') &&", " !request.startsWith('.') &&", ' !path.isAbsolute(request)', ' ) {', ' try {', ' return originalResolveFilename.call(this, request, parent, isMain, {', ' ...options,', ' paths: [QL_NODE_GLOBAL_PATH],', ' });', ' } catch (error) {}', ' }', '', ' return originalResolveFilename.call(this, request, parent, isMain, options);', ' };', ' Module._qlGlobalPathPatched = true;', '}', '', 'function expandRange(rangeStr, max) {', ' const tempRangeStr = rangeStr', ' .trim()', ' .replace(/-max/g, `-${max}`)', ' .replace(/max-/g, `${max}-`);', '', " return tempRangeStr.split(' ').flatMap((part) => {", ' const rangeMatch = part.match(/^(\\d+)([-~_])(\\d+)$/);', ' if (rangeMatch) {', ' const [, start, , end] = rangeMatch.map(Number);', ' const step = start < end ? 1 : -1;', ' return Array.from(', ' { length: Math.abs(end - start) + 1 },', ' (_, i) => start + i * step,', ' );', ' }', ' return Number(part);', ' });', '}', '', 'function run() {', ' const {', ' envParam,', ' numParam,', ' file_task_before,', ' file_task_before_js,', ' dir_scripts,', ' task_before,', ' PREV_NODE_OPTIONS,', ' } = process.env;', '', ' try {', ' process.env.NODE_OPTIONS = PREV_NODE_OPTIONS;', '', " const splitStr = '__sitecustomize__';", " const fileName = process.argv[1].replace(`${dir_scripts}/`, '');", ' const tempFile = `/tmp/env_${process.pid}.json`;', '', ' const commands = `file="$1"; output="$2"; inline="$3"; shift 3', 'source "$file" "$@" &&', '{ if [ -n "$inline" ]; then eval "$inline"; fi; } &&', "printf '%s\\\\n' '__sitecustomize__' &&", 'node -e \'require("fs").writeFileSync(process.argv[1], JSON.stringify(process.env))\' "$output"`;', " if (task_before) console.log(process.env.QL_LANG === 'en' ? 'Executing before command\\n' : '执行前置命令\\n');", ' const hookArgs = JSON.parse(process.env.QL_CLI_PRELOAD_ARGS || JSON.stringify([fileName]));', " const res = execFileSync('/bin/bash', ['-c', commands, 'ql-before', file_task_before, tempFile, task_before || '', ...hookArgs], {", " encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024,", ' });', '', ' const [output] = res.split(splitStr);', '', ' try {', " const envStr = require('fs').readFileSync(tempFile, 'utf-8');", ' const newEnvObject = JSON.parse(envStr);', " if (typeof newEnvObject === 'object' && newEnvObject !== null) {", ' for (const key in newEnvObject) {', ' if (Object.prototype.hasOwnProperty.call(newEnvObject, key)) {', ' process.env[key] = newEnvObject[key];', ' }', ' }', ' }', " require('fs').unlinkSync(tempFile);", ' } catch (jsonError) {', ' console.log(', " '\\ue926 Failed to parse environment variables:',", ' jsonError.message,', ' );', ' try {', " require('fs').unlinkSync(tempFile);", ' } catch (e) {}', ' }', '', ' if (output) {', ' console.log(output);', ' }', ' if (task_before) {', " console.log(process.env.QL_LANG === 'en' ? 'Before command finished\\n' : '执行前置命令结束\\n');", ' }', ' } catch (error) {', " if (!error.message.includes('spawnSync /bin/bash E2BIG')) {", ' console.log(`\\ue926 run task before error: `, error);', ' } else {', ' // environment variable is too large', ' }', ' if (task_before) {', " console.log(process.env.QL_LANG === 'en' ? 'Before command finished\\n' : '执行前置命令结束\\n');", ' }', ' }', '', ' require(file_task_before_js);', '', ' if (envParam && numParam) {', " const array = (process.env[envParam] || '').split('&');", ' const runArr = expandRange(numParam, array.length);', ' const arrayRun = runArr.map((i) => array[i - 1]);', " const envStr = arrayRun.join('&');", ' process.env[envParam] = envStr;', ' }', '}', '', 'try {', ' if (!process.argv[1]) {', ' return;', ' }', '', ' preferGlobalNodeModules();', '', " process.on('SIGTERM', (code) => {", ' process.exit(15);', ' });', '', ' run();', '', " const { sendNotify } = require('./__ql_notify__.js');", ' global.QLAPI = {', ' notify: sendNotify,', ' ...client,', ' };', '} catch (error) {', " console.log(`run builtin code error: `, error, '\\n');", '}', '', ].join('\n'); export const pythonPreloadSource: string = [ 'import os', 'import re', 'import subprocess', 'import json', 'import builtins', 'import sys', 'import env', 'import signal', 'from client import Client', '', '', 'def try_parse_int(value):', ' try:', ' return int(value)', ' except ValueError:', ' return None', '', '', 'def expand_range(range_str, max_value):', ' temp_range_str = (', ' range_str.strip()', ' .replace("-max", f"-{max_value}")', ' .replace("max-", f"{max_value}-")', ' )', '', ' result = []', ' for part in temp_range_str.split(" "):', ' range_match = re.match(r"^(\\d+)([-~_])(\\d+)$", part)', ' if range_match:', ' start, _, end = map(try_parse_int, range_match.groups())', ' step = 1 if start < end else -1', ' result.extend(range(start, end + step, step))', ' else:', ' result.append(int(part))', '', ' return result', '', '', 'def run():', ' try:', ' prev_pythonpath = os.getenv("PREV_PYTHONPATH", "")', ' os.environ["PYTHONPATH"] = prev_pythonpath', '', ' split_str = "__sitecustomize__"', ' file_name = sys.argv[0].replace(f"{os.getenv(\'dir_scripts\')}/", "")', ' ', ' # 创建临时文件路径', ' temp_file = f"/tmp/env_{os.getpid()}.json"', ' ', ' task_before = os.getenv("task_before", "")', ' if task_before:', ' print("Executing before command\\n" if os.environ.get("QL_LANG") == "en" else "执行前置命令\\n")', ' command = """file="$1"; output="$2"; inline="$3"; shift 3', 'source "$file" "$@" &&', '{ if [ -n "$inline" ]; then eval "$inline"; fi; } &&', "printf '%s\\\\n' '__sitecustomize__' &&", 'python3 -c \'import os,json,sys; f=open(sys.argv[1],"w"); json.dump(dict(os.environ),f); f.close()\' "$output"', '"""', ' hook_args = json.loads(os.getenv("QL_CLI_PRELOAD_ARGS", json.dumps([file_name])))', ' res = subprocess.check_output(["/bin/bash", "-c", command, "ql-before",', ' os.environ["file_task_before"], temp_file, task_before, *hook_args], encoding="utf-8")', ' output = res.split(split_str)[0]', '', ' try:', " with open(temp_file, 'r') as f:", ' env_json = json.loads(f.read())', '', ' for key, value in env_json.items():', ' os.environ[key] = value', '', ' os.unlink(temp_file)', ' except Exception as json_error:', ' print(f"\\ue926 Failed to parse environment variables: {json_error}")', ' try:', ' os.unlink(temp_file)', ' except:', ' pass', '', ' if len(output) > 0:', ' print(output)', ' if task_before:', ' print("Before command finished\\n" if os.environ.get("QL_LANG") == "en" else "执行前置命令结束\\n")', '', ' except subprocess.CalledProcessError as error:', ' print(f"\\ue926 run task before error: {error}")', ' if task_before:', ' print("Before command finished\\n" if os.environ.get("QL_LANG") == "en" else "执行前置命令结束\\n")', ' except OSError as error:', ' error_message = str(error)', ' if "Argument list too long" not in error_message:', ' print(f"\\ue926 run task before error: {error}")', ' # else:', ' # environment variable is too large', ' if task_before:', ' print("Before command finished\\n" if os.environ.get("QL_LANG") == "en" else "执行前置命令结束\\n")', ' except Exception as error:', ' print(f"\\ue926 run task before error: {error}")', ' if task_before:', ' print("Before command finished\\n" if os.environ.get("QL_LANG") == "en" else "执行前置命令结束\\n")', '', ' import task_before', '', ' env_param = os.getenv("envParam")', ' num_param = os.getenv("numParam")', '', ' if env_param and num_param:', ' array = (os.getenv(env_param) or "").split("&")', ' run_arr = expand_range(num_param, len(array))', ' array_run = [array[i - 1] for i in run_arr if i - 1 < len(array) and i > 0]', ' env_str = "&".join(array_run)', ' os.environ[env_param] = env_str', '', '', 'def handle_sigterm(signum, frame):', ' sys.exit(15)', '', '', 'try:', ' signal.signal(signal.SIGTERM, handle_sigterm)', '', ' run()', '', ' from __ql_notify__ import send', '', ' class BaseApi(Client):', ' def notify(self, *args, **kwargs):', ' return send(*args, **kwargs)', '', ' QLAPI = BaseApi()', ' builtins.QLAPI = QLAPI', 'except Exception as error:', ' print(f"run builtin code error: {error}\\n")', '', ].join('\n'); // Native ESM resolution with global-first package selection. export const esmLoaderSource: string = [ "import { existsSync } from 'node:fs';", "import { isBuiltin } from 'node:module';", "import { join } from 'node:path';", "import { pathToFileURL } from 'node:url';", '', 'export function resolve(specifier, context, nextResolve) {', ' // Leave builtins, package imports, paths and URL schemes to Node unchanged.', ' if (isBuiltin(specifier) || /^[.#/]/.test(specifier) || /^[a-zA-Z][a-zA-Z\\d+.-]*:/.test(specifier))', ' return nextResolve(specifier, context);', " const parts = specifier.split('/');", " const name = specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0];", " for (const base of [process.env.QL_NODE_GLOBAL_PATH, '/usr/local/lib/node_modules'].filter(Boolean)) {", ' const directory = join(base, name);', ' if (!existsSync(directory)) continue;', " // Resolve from inside the package, retaining Node's exports, import conditions", ' // and package-boundary checks. A virtual file need not exist for resolution.', ' return nextResolve(specifier, {', ' ...context,', " parentURL: pathToFileURL(join(directory, '__ql_resolve__.mjs')).href,", ' });', ' }', ' return nextResolve(specifier, context);', '}', '', ].join('\n');