mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
chore(release): v1.16.0
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
import { spawn } from "node:child_process";
|
||||
import process from "node:process";
|
||||
|
||||
const DEV_URL_TIMEOUT_MS = 1_500;
|
||||
const ENTRY_MODULE_TIMEOUT_MS = 6_000;
|
||||
const OPTIMIZED_DEPS_TIMEOUT_MS = 6_000;
|
||||
const REUSE_ONLY_TIMEOUT_MS = 10_000;
|
||||
const REUSE_ONLY_INTERVAL_MS = 500;
|
||||
const ROOT_MARKERS = ["<title>Lime</title>", '<div id="root"></div>'];
|
||||
const OPTIMIZED_DEP_FILES = [
|
||||
"react.js",
|
||||
"react-dom_client.js",
|
||||
"react_jsx-dev-runtime.js",
|
||||
];
|
||||
|
||||
function isLimeDevShell(html) {
|
||||
return ROOT_MARKERS.some((marker) => html.includes(marker));
|
||||
}
|
||||
|
||||
function isEntryModuleReady(code) {
|
||||
return (
|
||||
code.includes("ReactDOM.createRoot") ||
|
||||
code.includes("registerLightweightRenderers")
|
||||
);
|
||||
}
|
||||
|
||||
function isOptimizedDepReady(code) {
|
||||
return code.includes("export") || code.includes("import ");
|
||||
}
|
||||
|
||||
async function fetchText(url, timeoutMs) {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
const text = await response.text();
|
||||
|
||||
return {
|
||||
response,
|
||||
text,
|
||||
};
|
||||
}
|
||||
|
||||
function createEnv({ browserBridge }) {
|
||||
const env = { ...process.env };
|
||||
|
||||
if (browserBridge) {
|
||||
delete env.TAURI_ENV_PLATFORM;
|
||||
env.LIME_BROWSER_BRIDGE = "1";
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
function resolveOptimizedDepUrls(url, { browserBridge }) {
|
||||
const depsDir = browserBridge ? ".vite-web" : ".vite-tauri";
|
||||
return OPTIMIZED_DEP_FILES.map((file) =>
|
||||
new URL(`/node_modules/${depsDir}/deps/${file}`, url).toString(),
|
||||
);
|
||||
}
|
||||
|
||||
async function probeExistingDevServer(url, options) {
|
||||
if (typeof fetch !== "function") {
|
||||
return { reachable: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const { response, text: html } = await fetchText(url, DEV_URL_TIMEOUT_MS);
|
||||
const limeDevShellReady = response.ok && isLimeDevShell(html);
|
||||
|
||||
let entryModuleReady = false;
|
||||
let optimizedDepsReady = false;
|
||||
|
||||
if (limeDevShellReady) {
|
||||
try {
|
||||
const entryModuleUrl = new URL(options.entryModulePath, url).toString();
|
||||
const { response: entryResponse, text: entryCode } = await fetchText(
|
||||
entryModuleUrl,
|
||||
ENTRY_MODULE_TIMEOUT_MS,
|
||||
);
|
||||
entryModuleReady =
|
||||
entryResponse.ok && isEntryModuleReady(entryCode);
|
||||
} catch {
|
||||
entryModuleReady = false;
|
||||
}
|
||||
|
||||
if (entryModuleReady) {
|
||||
const optimizedDepResults = await Promise.all(
|
||||
resolveOptimizedDepUrls(url, options).map(async (depUrl) => {
|
||||
try {
|
||||
const { response: depResponse, text: depCode } = await fetchText(
|
||||
depUrl,
|
||||
OPTIMIZED_DEPS_TIMEOUT_MS,
|
||||
);
|
||||
return depResponse.ok && isOptimizedDepReady(depCode);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
);
|
||||
optimizedDepsReady = optimizedDepResults.every(Boolean);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
reachable: true,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
isLimeDevShell: limeDevShellReady,
|
||||
isEntryModuleReady: entryModuleReady,
|
||||
areOptimizedDepsReady: optimizedDepsReady,
|
||||
};
|
||||
} catch {
|
||||
return { reachable: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForExistingDevServer(url, options) {
|
||||
const startedAt = Date.now();
|
||||
let lastProbe = { reachable: false };
|
||||
|
||||
while (Date.now() - startedAt < REUSE_ONLY_TIMEOUT_MS) {
|
||||
lastProbe = await probeExistingDevServer(url, options);
|
||||
|
||||
if (
|
||||
lastProbe.reachable &&
|
||||
lastProbe.isLimeDevShell &&
|
||||
lastProbe.isEntryModuleReady &&
|
||||
lastProbe.areOptimizedDepsReady
|
||||
) {
|
||||
return lastProbe;
|
||||
}
|
||||
|
||||
if (lastProbe.reachable && !lastProbe.isLimeDevShell) {
|
||||
const statusLabel = `${lastProbe.status} ${lastProbe.statusText}`.trim();
|
||||
throw new Error(
|
||||
`[${options.logLabel}] ${options.devUrl} 已被其他服务占用,且返回内容不是 Lime dev shell(${statusLabel})。请先关闭占用进程后重试。`,
|
||||
);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, REUSE_ONLY_INTERVAL_MS));
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`[${options.logLabel}] 要求复用已有 Lime dev server,但 ${url} 在 ${REUSE_ONLY_TIMEOUT_MS}ms 内未完成入口模块与优化依赖预热。`,
|
||||
);
|
||||
}
|
||||
|
||||
async function runCommand(command, args, env, label) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
env,
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
reject(new Error(`[${label}] 子进程被信号中断: ${signal}`));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((code ?? 0) !== 0) {
|
||||
reject(new Error(`[${label}] 子进程退出码异常: ${code ?? 0}`));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startVite(env) {
|
||||
const child = spawn("npx", ["vite"], {
|
||||
stdio: "inherit",
|
||||
shell: true,
|
||||
env,
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForExitSignal() {
|
||||
await new Promise((resolve) => {
|
||||
const handleExit = () => resolve();
|
||||
process.once("SIGINT", handleExit);
|
||||
process.once("SIGTERM", handleExit);
|
||||
});
|
||||
}
|
||||
|
||||
export async function runViteDevServerBootstrap({
|
||||
browserBridge = false,
|
||||
devUrl = "http://127.0.0.1:1420/",
|
||||
entryModulePath = "/src/main.tsx",
|
||||
reuseExistingOnly = false,
|
||||
logLabel = "vite:dev",
|
||||
} = {}) {
|
||||
const env = createEnv({ browserBridge });
|
||||
const options = {
|
||||
browserBridge,
|
||||
devUrl,
|
||||
entryModulePath,
|
||||
logLabel,
|
||||
};
|
||||
|
||||
if (reuseExistingOnly) {
|
||||
await waitForExistingDevServer(devUrl, options);
|
||||
console.log(`[${logLabel}] 复用已存在的 Lime dev server: ${devUrl}`);
|
||||
await waitForExitSignal();
|
||||
return;
|
||||
}
|
||||
|
||||
const existingServer = await probeExistingDevServer(devUrl, options);
|
||||
|
||||
if (existingServer.reachable) {
|
||||
if (!existingServer.isLimeDevShell) {
|
||||
const statusLabel = `${existingServer.status} ${existingServer.statusText}`.trim();
|
||||
throw new Error(
|
||||
`[${logLabel}] ${devUrl} 已被其他服务占用,且返回内容不是 Lime dev shell(${statusLabel})。请先关闭占用进程后重试。`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!existingServer.isEntryModuleReady ||
|
||||
!existingServer.areOptimizedDepsReady
|
||||
) {
|
||||
await waitForExistingDevServer(devUrl, options);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[${logLabel}] 复用已存在的 Lime dev server: ${devUrl}(入口模块与优化依赖均已就绪)`,
|
||||
);
|
||||
await waitForExitSignal();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[${logLabel}] 先执行 vite optimize,避免 Tauri 抢跑到半就绪 dev server。`);
|
||||
await runCommand("npx", ["vite", "optimize"], env, logLabel);
|
||||
startVite(env);
|
||||
await waitForExitSignal();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import process from "node:process";
|
||||
import { runViteDevServerBootstrap } from "./lib/vite-dev-server-bootstrap.mjs";
|
||||
|
||||
runViteDevServerBootstrap({
|
||||
browserBridge: false,
|
||||
devUrl: "http://127.0.0.1:1420/",
|
||||
entryModulePath: "/src/main.tsx",
|
||||
reuseExistingOnly: false,
|
||||
logLabel: "dev:tauri-shell",
|
||||
}).catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,125 +1,22 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { spawn } from 'node:child_process';
|
||||
import process from 'node:process';
|
||||
import process from "node:process";
|
||||
import { runViteDevServerBootstrap } from "./lib/vite-dev-server-bootstrap.mjs";
|
||||
|
||||
const DEV_URL = process.env.LIME_WEB_BRIDGE_URL?.trim() || 'http://127.0.0.1:1420/';
|
||||
const DEV_URL_TIMEOUT_MS = 1_500;
|
||||
const REUSE_ONLY_TIMEOUT_MS = 10_000;
|
||||
const REUSE_ONLY_INTERVAL_MS = 500;
|
||||
const ROOT_MARKERS = ['<title>Lime</title>', '<div id="root"></div>'];
|
||||
const DEV_URL =
|
||||
process.env.LIME_WEB_BRIDGE_URL?.trim() || "http://127.0.0.1:1420/";
|
||||
const ENTRY_MODULE_PATH =
|
||||
process.env.LIME_WEB_BRIDGE_ENTRY_PATH?.trim() || "/src/main.tsx";
|
||||
const REUSE_EXISTING_ONLY =
|
||||
process.env.LIME_WEB_BRIDGE_REUSE_EXISTING_ONLY?.trim() === '1';
|
||||
process.env.LIME_WEB_BRIDGE_REUSE_EXISTING_ONLY?.trim() === "1";
|
||||
|
||||
const env = { ...process.env };
|
||||
delete env.TAURI_ENV_PLATFORM;
|
||||
|
||||
env.LIME_BROWSER_BRIDGE = '1';
|
||||
|
||||
function isLimeDevShell(html) {
|
||||
return ROOT_MARKERS.some((marker) => html.includes(marker));
|
||||
}
|
||||
|
||||
async function probeExistingDevServer(url) {
|
||||
if (typeof fetch !== 'function') {
|
||||
return { reachable: false };
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
signal: AbortSignal.timeout(DEV_URL_TIMEOUT_MS),
|
||||
});
|
||||
const html = await response.text();
|
||||
|
||||
return {
|
||||
reachable: true,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
isLimeDevShell: response.ok && isLimeDevShell(html),
|
||||
};
|
||||
} catch {
|
||||
return { reachable: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForExistingDevServer(url) {
|
||||
const startedAt = Date.now();
|
||||
let lastProbe = { reachable: false };
|
||||
|
||||
while (Date.now() - startedAt < REUSE_ONLY_TIMEOUT_MS) {
|
||||
lastProbe = await probeExistingDevServer(url);
|
||||
|
||||
if (lastProbe.reachable && lastProbe.isLimeDevShell) {
|
||||
return lastProbe;
|
||||
}
|
||||
|
||||
if (lastProbe.reachable && !lastProbe.isLimeDevShell) {
|
||||
const statusLabel = `${lastProbe.status} ${lastProbe.statusText}`.trim();
|
||||
throw new Error(
|
||||
`[dev:web-bridge] ${DEV_URL} 已被其他服务占用,且返回内容不是 Lime dev shell(${statusLabel})。请先关闭占用进程后重试。`,
|
||||
);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, REUSE_ONLY_INTERVAL_MS));
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`[dev:web-bridge] 要求复用已有 Lime dev server,但 ${url} 在 ${REUSE_ONLY_TIMEOUT_MS}ms 内未就绪。`,
|
||||
);
|
||||
}
|
||||
|
||||
async function waitForExitSignal() {
|
||||
await new Promise((resolve) => {
|
||||
const handleExit = () => resolve();
|
||||
process.once('SIGINT', handleExit);
|
||||
process.once('SIGTERM', handleExit);
|
||||
});
|
||||
}
|
||||
|
||||
function startVite() {
|
||||
const child = spawn('npx', ['vite'], {
|
||||
stdio: 'inherit',
|
||||
shell: true,
|
||||
env,
|
||||
});
|
||||
|
||||
child.on('exit', (code, signal) => {
|
||||
if (signal) {
|
||||
process.kill(process.pid, signal);
|
||||
return;
|
||||
}
|
||||
process.exit(code ?? 0);
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
if (REUSE_EXISTING_ONLY) {
|
||||
await waitForExistingDevServer(DEV_URL);
|
||||
console.log(`[dev:web-bridge] 复用已存在的 Lime dev server: ${DEV_URL}`);
|
||||
await waitForExitSignal();
|
||||
return;
|
||||
}
|
||||
|
||||
const existingServer = await probeExistingDevServer(DEV_URL);
|
||||
|
||||
if (existingServer.reachable) {
|
||||
if (!existingServer.isLimeDevShell) {
|
||||
const statusLabel = `${existingServer.status} ${existingServer.statusText}`.trim();
|
||||
throw new Error(
|
||||
`[dev:web-bridge] ${DEV_URL} 已被其他服务占用,且返回内容不是 Lime dev shell(${statusLabel})。请先关闭占用进程后重试。`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[dev:web-bridge] 复用已存在的 Lime dev server: ${DEV_URL}`);
|
||||
await waitForExitSignal();
|
||||
return;
|
||||
}
|
||||
|
||||
startVite();
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
runViteDevServerBootstrap({
|
||||
browserBridge: true,
|
||||
devUrl: DEV_URL,
|
||||
entryModulePath: ENTRY_MODULE_PATH,
|
||||
reuseExistingOnly: REUSE_EXISTING_ONLY,
|
||||
logLabel: "dev:web-bridge",
|
||||
}).catch((error) => {
|
||||
console.error(error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user