feat: 使用SignalR来控制jtag边界扫描
This commit is contained in:
@@ -1,36 +1,42 @@
|
||||
import { spawn, exec, ChildProcess } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import fetch from 'node-fetch';
|
||||
import * as fs from 'fs';
|
||||
import { spawn, exec, ChildProcess } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import fetch from "node-fetch";
|
||||
import * as fs from "fs";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// Windows 支持函数
|
||||
function getCommand(command: string): string {
|
||||
// dotnet 在 Windows 上不需要 .cmd 后缀
|
||||
if (command === 'dotnet') {
|
||||
return 'dotnet';
|
||||
if (command === "dotnet") {
|
||||
return "dotnet";
|
||||
}
|
||||
return process.platform === 'win32' ? `${command}.cmd` : command;
|
||||
return process.platform === "win32" ? `${command}.cmd` : command;
|
||||
}
|
||||
|
||||
function getSpawnOptions() {
|
||||
return process.platform === 'win32' ? { stdio: 'pipe', shell: true } : { stdio: 'pipe' };
|
||||
return process.platform === "win32"
|
||||
? { stdio: "pipe", shell: true }
|
||||
: { stdio: "pipe" };
|
||||
}
|
||||
|
||||
async function waitForServer(url: string, maxRetries: number = 30, interval: number = 1000): Promise<boolean> {
|
||||
async function waitForServer(
|
||||
url: string,
|
||||
maxRetries: number = 30,
|
||||
interval: number = 1000,
|
||||
): Promise<boolean> {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
console.log('✓ Server is ready');
|
||||
console.log("✓ Server is ready");
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
// Server not ready yet
|
||||
}
|
||||
console.log(`Waiting for server... (${i + 1}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, interval));
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -40,32 +46,39 @@ let serverProcess: ChildProcess | null = null;
|
||||
let webProcess: ChildProcess | null = null;
|
||||
|
||||
async function startWeb(): Promise<ChildProcess> {
|
||||
console.log('Starting Vite frontend...');
|
||||
console.log("Starting Vite frontend...");
|
||||
return new Promise((resolve, reject) => {
|
||||
const process = spawn(getCommand('npm'), ['run', 'dev'], getSpawnOptions() as any);
|
||||
const process = spawn(
|
||||
getCommand("npm"),
|
||||
["run", "dev"],
|
||||
getSpawnOptions() as any,
|
||||
);
|
||||
|
||||
let webStarted = false;
|
||||
|
||||
process.stdout?.on('data', (data) => {
|
||||
process.stdout?.on("data", (data) => {
|
||||
const output = data.toString();
|
||||
console.log(`Web: ${output}`);
|
||||
|
||||
|
||||
// 检查 Vite 是否已启动
|
||||
if ((output.includes('Local:') || output.includes('ready in')) && !webStarted) {
|
||||
if (
|
||||
(output.includes("Local:") || output.includes("ready in")) &&
|
||||
!webStarted
|
||||
) {
|
||||
webStarted = true;
|
||||
resolve(process);
|
||||
}
|
||||
});
|
||||
|
||||
process.stderr?.on('data', (data) => {
|
||||
process.stderr?.on("data", (data) => {
|
||||
console.error(`Web Error: ${data}`);
|
||||
});
|
||||
|
||||
process.on('error', (error) => {
|
||||
process.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
process.on('exit', (code, signal) => {
|
||||
process.on("exit", (code, signal) => {
|
||||
console.log(`Web process exited with code ${code} and signal ${signal}`);
|
||||
if (!webStarted) {
|
||||
reject(new Error(`Web process exited unexpectedly with code ${code}`));
|
||||
@@ -74,222 +87,211 @@ async function startWeb(): Promise<ChildProcess> {
|
||||
|
||||
// 存储进程引用
|
||||
webProcess = process;
|
||||
|
||||
|
||||
// 超时处理
|
||||
setTimeout(() => {
|
||||
if (!webStarted) {
|
||||
reject(new Error('Web server failed to start within timeout'));
|
||||
reject(new Error("Web server failed to start within timeout"));
|
||||
}
|
||||
}, 30000); // 30秒超时
|
||||
}, 10000); // 10秒超时
|
||||
});
|
||||
}
|
||||
|
||||
async function startServer(): Promise<ChildProcess> {
|
||||
console.log('Starting .NET server...');
|
||||
console.log("Starting .NET server...");
|
||||
return new Promise((resolve, reject) => {
|
||||
const process = spawn(getCommand('dotnet'), ['run', '--property:Configuration=Release'], {
|
||||
cwd: 'server',
|
||||
...getSpawnOptions()
|
||||
} as any);
|
||||
const process = spawn(
|
||||
getCommand("dotnet"),
|
||||
["run", "--property:Configuration=Release"],
|
||||
{
|
||||
cwd: "server",
|
||||
...getSpawnOptions(),
|
||||
} as any,
|
||||
);
|
||||
|
||||
let serverStarted = false;
|
||||
|
||||
process.stdout?.on('data', (data) => {
|
||||
process.stdout?.on("data", (data) => {
|
||||
const output = data.toString();
|
||||
console.log(`Server: ${output}`);
|
||||
|
||||
|
||||
// 检查服务器是否已启动
|
||||
if (output.includes('Now listening on:') && !serverStarted) {
|
||||
if (output.includes("Now listening on:") && !serverStarted) {
|
||||
serverStarted = true;
|
||||
resolve(process);
|
||||
}
|
||||
});
|
||||
|
||||
process.stderr?.on('data', (data) => {
|
||||
process.stderr?.on("data", (data) => {
|
||||
console.error(`Server Error: ${data}`);
|
||||
});
|
||||
|
||||
process.on('error', (error) => {
|
||||
process.on("error", (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
process.on('exit', (code, signal) => {
|
||||
console.log(`Server process exited with code ${code} and signal ${signal}`);
|
||||
process.on("exit", (code, signal) => {
|
||||
console.log(
|
||||
`Server process exited with code ${code} and signal ${signal}`,
|
||||
);
|
||||
if (!serverStarted) {
|
||||
reject(new Error(`Server process exited unexpectedly with code ${code}`));
|
||||
reject(
|
||||
new Error(`Server process exited unexpectedly with code ${code}`),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// 存储进程引用
|
||||
serverProcess = process;
|
||||
|
||||
|
||||
// 超时处理
|
||||
setTimeout(() => {
|
||||
if (!serverStarted) {
|
||||
reject(new Error('Server failed to start within timeout'));
|
||||
reject(new Error("Server failed to start within timeout"));
|
||||
}
|
||||
}, 30000); // 30秒超时
|
||||
}, 10000); // 10秒超时
|
||||
});
|
||||
}
|
||||
|
||||
async function stopServer(): Promise<void> {
|
||||
console.log('Stopping server...');
|
||||
|
||||
console.log("Stopping server...");
|
||||
|
||||
if (!serverProcess) {
|
||||
console.log('No server process to stop');
|
||||
console.log("No server process to stop");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查进程是否还存在
|
||||
if (serverProcess.killed || serverProcess.exitCode !== null) {
|
||||
console.log('✓ Server process already terminated');
|
||||
console.log("✓ Server process already terminated");
|
||||
serverProcess = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送 SIGTERM 信号
|
||||
const killed = serverProcess.kill('SIGTERM');
|
||||
const killed = serverProcess.kill("SIGTERM");
|
||||
if (!killed) {
|
||||
console.warn('Failed to send SIGTERM to server process');
|
||||
console.warn("Failed to send SIGTERM to server process");
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待进程优雅退出
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
if (serverProcess) {
|
||||
serverProcess.on('exit', () => {
|
||||
console.log('✓ Server stopped gracefully');
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// 设置超时,如果 3 秒内没有退出则强制终止
|
||||
const timeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
if (serverProcess && !serverProcess.killed && serverProcess.exitCode === null) {
|
||||
console.log('Force killing server process...');
|
||||
serverProcess.kill('SIGKILL');
|
||||
if (
|
||||
serverProcess &&
|
||||
!serverProcess.killed &&
|
||||
serverProcess.exitCode === null
|
||||
) {
|
||||
console.log("Force killing server process...");
|
||||
serverProcess.kill("SIGKILL");
|
||||
}
|
||||
resolve();
|
||||
}, 3000); // 减少超时时间到3秒
|
||||
});
|
||||
|
||||
await Promise.race([exitPromise, timeoutPromise]);
|
||||
|
||||
await Promise.race([timeoutPromise]);
|
||||
} catch (error) {
|
||||
console.warn('Warning: Could not stop server process:', error);
|
||||
console.warn("Warning: Could not stop server process:", error);
|
||||
} finally {
|
||||
serverProcess = null;
|
||||
|
||||
|
||||
// 只有在进程可能没有正常退出时才执行清理
|
||||
// 移除自动清理逻辑,因为正常退出时不需要
|
||||
}
|
||||
}
|
||||
|
||||
async function stopWeb(): Promise<void> {
|
||||
console.log('Stopping web server...');
|
||||
|
||||
console.log("Stopping web server...");
|
||||
|
||||
if (!webProcess) {
|
||||
console.log('No web process to stop');
|
||||
console.log("No web process to stop");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 检查进程是否还存在
|
||||
if (webProcess.killed || webProcess.exitCode !== null) {
|
||||
console.log('✓ Web process already terminated');
|
||||
console.log("✓ Web process already terminated");
|
||||
webProcess = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送 SIGTERM 信号
|
||||
const killed = webProcess.kill('SIGTERM');
|
||||
const killed = webProcess.kill("SIGTERM");
|
||||
if (!killed) {
|
||||
console.warn('Failed to send SIGTERM to web process');
|
||||
console.warn("Failed to send SIGTERM to web process");
|
||||
return;
|
||||
}
|
||||
|
||||
// 等待进程优雅退出
|
||||
const exitPromise = new Promise<void>((resolve) => {
|
||||
if (webProcess) {
|
||||
webProcess.on('exit', () => {
|
||||
console.log('✓ Web server stopped gracefully');
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// 设置超时,如果 3 秒内没有退出则强制终止
|
||||
const timeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => {
|
||||
if (webProcess && !webProcess.killed && webProcess.exitCode === null) {
|
||||
console.log('Force killing web process...');
|
||||
webProcess.kill('SIGKILL');
|
||||
console.log("Force killing web process...");
|
||||
webProcess.kill("SIGKILL");
|
||||
}
|
||||
resolve();
|
||||
}, 3000); // 减少超时时间到3秒
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
await Promise.race([exitPromise, timeoutPromise]);
|
||||
|
||||
await Promise.race([timeoutPromise]);
|
||||
} catch (error) {
|
||||
console.warn('Warning: Could not stop web process:', error);
|
||||
console.warn("Warning: Could not stop web process:", error);
|
||||
} finally {
|
||||
webProcess = null;
|
||||
|
||||
// 只有在进程可能没有正常退出时才执行清理
|
||||
// 移除自动清理逻辑,因为正常退出时不需要
|
||||
}
|
||||
}
|
||||
|
||||
async function postProcessApiClient(): Promise<void> {
|
||||
console.log('Post-processing API client...');
|
||||
console.log("Post-processing API client...");
|
||||
try {
|
||||
const filePath = 'src/APIClient.ts';
|
||||
|
||||
const filePath = "src/APIClient.ts";
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`API client file not found: ${filePath}`);
|
||||
}
|
||||
|
||||
|
||||
// 读取文件内容
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
let content = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
// 替换 ArgumentException 中的 message 属性声明
|
||||
content = content.replace(
|
||||
/(\s+)message!:\s*string;/g,
|
||||
'$1declare message: string;'
|
||||
"$1declare message: string;",
|
||||
);
|
||||
|
||||
content = content.replace(
|
||||
"{ AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, CancelToken }",
|
||||
"{ AxiosError, type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, type CancelToken }",
|
||||
);
|
||||
|
||||
// 写回文件
|
||||
fs.writeFileSync(filePath, content, 'utf8');
|
||||
|
||||
console.log('✓ API client post-processing completed');
|
||||
fs.writeFileSync(filePath, content, "utf8");
|
||||
|
||||
console.log("✓ API client post-processing completed");
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to post-process API client: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateApiClient(): Promise<void> {
|
||||
console.log('Generating API client...');
|
||||
console.log("Generating API client...");
|
||||
try {
|
||||
const url = 'http://127.0.0.1:5000/GetAPIClientCode';
|
||||
const url = "http://127.0.0.1:5000/GetAPIClientCode";
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch API client code: ${response.status} ${response.statusText}`);
|
||||
throw new Error(
|
||||
`Failed to fetch API client code: ${response.status} ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
const code = await response.text();
|
||||
|
||||
// 写入 APIClient.ts
|
||||
const filePath = 'src/APIClient.ts';
|
||||
fs.writeFileSync(filePath, code, 'utf8');
|
||||
console.log('✓ API client code fetched and written successfully');
|
||||
const filePath = "src/APIClient.ts";
|
||||
fs.writeFileSync(filePath, code, "utf8");
|
||||
console.log("✓ API client code fetched and written successfully");
|
||||
|
||||
// 添加后处理步骤
|
||||
await postProcessApiClient();
|
||||
@@ -298,35 +300,55 @@ async function generateApiClient(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function generateSignalRClient(): Promise<void> {
|
||||
console.log("Generating SignalR TypeScript client...");
|
||||
try {
|
||||
const { stdout, stderr } = await execAsync(
|
||||
"dotnet tsrts --project ./server/server.csproj --output ./src",
|
||||
);
|
||||
if (stdout) console.log(stdout);
|
||||
if (stderr) console.error(stderr);
|
||||
console.log("✓ SignalR TypeScript client generated successfully");
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to generate SignalR client: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
try {
|
||||
// Generate SignalR client
|
||||
await generateSignalRClient();
|
||||
console.log("✓ SignalR TypeScript client generated successfully");
|
||||
|
||||
// Start web frontend first
|
||||
await startWeb();
|
||||
console.log('✓ Frontend started');
|
||||
|
||||
console.log("✓ Frontend started");
|
||||
|
||||
// Wait a bit for frontend to fully initialize
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
// Start server
|
||||
await startServer();
|
||||
console.log('✓ Backend started');
|
||||
|
||||
console.log("✓ Backend started");
|
||||
|
||||
// Wait for server to be ready (给服务器额外时间完全启动)
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
// Check if swagger endpoint is available
|
||||
const serverReady = await waitForServer('http://localhost:5000/swagger/v1/swagger.json');
|
||||
|
||||
const serverReady = await waitForServer(
|
||||
"http://localhost:5000/swagger/v1/swagger.json",
|
||||
);
|
||||
|
||||
if (!serverReady) {
|
||||
throw new Error('Server failed to start within the expected time');
|
||||
throw new Error("Server failed to start within the expected time");
|
||||
}
|
||||
|
||||
|
||||
// Generate API client
|
||||
await generateApiClient();
|
||||
|
||||
console.log('✓ API generation completed successfully');
|
||||
|
||||
console.log("✓ API generation completed successfully");
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error);
|
||||
console.error("❌ Error:", error);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
// Always try to stop processes in order: server first, then web
|
||||
@@ -340,80 +362,68 @@ let isCleaningUp = false;
|
||||
|
||||
const cleanup = async (signal: string) => {
|
||||
if (isCleaningUp) {
|
||||
console.log('Cleanup already in progress, ignoring signal');
|
||||
console.log("Cleanup already in progress, ignoring signal");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
isCleaningUp = true;
|
||||
console.log(`\nReceived ${signal}, cleaning up...`);
|
||||
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
stopServer(),
|
||||
stopWeb()
|
||||
]);
|
||||
await Promise.all([stopServer(), stopWeb()]);
|
||||
} catch (error) {
|
||||
console.error('Error during cleanup:', error);
|
||||
console.error("Error during cleanup:", error);
|
||||
}
|
||||
|
||||
|
||||
// 立即退出,不等待
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => cleanup('SIGINT'));
|
||||
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
||||
process.on("SIGINT", () => cleanup("SIGINT"));
|
||||
process.on("SIGTERM", () => cleanup("SIGTERM"));
|
||||
|
||||
// 处理未捕获的异常
|
||||
process.on('uncaughtException', async (error) => {
|
||||
process.on("uncaughtException", async (error) => {
|
||||
if (isCleaningUp) return;
|
||||
|
||||
console.error('❌ Uncaught exception:', error);
|
||||
|
||||
console.error("❌ Uncaught exception:", error);
|
||||
isCleaningUp = true;
|
||||
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
stopServer(),
|
||||
stopWeb()
|
||||
]);
|
||||
await Promise.all([stopServer(), stopWeb()]);
|
||||
} catch (cleanupError) {
|
||||
console.error('Error during cleanup:', cleanupError);
|
||||
console.error("Error during cleanup:", cleanupError);
|
||||
}
|
||||
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', async (reason, promise) => {
|
||||
process.on("unhandledRejection", async (reason, promise) => {
|
||||
if (isCleaningUp) return;
|
||||
|
||||
console.error('❌ Unhandled rejection at:', promise, 'reason:', reason);
|
||||
|
||||
console.error("❌ Unhandled rejection at:", promise, "reason:", reason);
|
||||
isCleaningUp = true;
|
||||
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
stopServer(),
|
||||
stopWeb()
|
||||
]);
|
||||
await Promise.all([stopServer(), stopWeb()]);
|
||||
} catch (cleanupError) {
|
||||
console.error('Error during cleanup:', cleanupError);
|
||||
console.error("Error during cleanup:", cleanupError);
|
||||
}
|
||||
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
main().catch(async (error) => {
|
||||
if (isCleaningUp) return;
|
||||
|
||||
console.error('❌ Unhandled error:', error);
|
||||
|
||||
console.error("❌ Unhandled error:", error);
|
||||
isCleaningUp = true;
|
||||
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
stopServer(),
|
||||
stopWeb()
|
||||
]);
|
||||
await Promise.all([stopServer(), stopWeb()]);
|
||||
} catch (cleanupError) {
|
||||
console.error('Error during cleanup:', cleanupError);
|
||||
console.error("Error during cleanup:", cleanupError);
|
||||
}
|
||||
|
||||
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user