chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 启动前端口检查脚本(聚合逻辑的 CLI 入口)
|
||||
*
|
||||
* 端口默认值与解析规则与 src/shared/startupPorts.ts 保持一致,请同步修改。
|
||||
* 从 ~/.qimingclaw/qimingclaw.db(Windows: %USERPROFILE%\.qimingclaw\)读取配置。
|
||||
* 端口占用检查:优先统一用 bash 执行 scripts/tools/check-port.sh(Windows 需先 npm run prepare:git 集成 Git Bash),
|
||||
* 无 bash 或脚本时回退到 Node 内联 netstat/lsof。
|
||||
* 依赖:Node;读取 DB 需系统 sqlite3(Windows 上多数未预装,无则用默认端口)。
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { execSync, spawnSync } = require('child_process');
|
||||
const { getProjectRoot } = require('../utils/project-paths');
|
||||
|
||||
const isWin = process.platform === 'win32';
|
||||
const home = process.env.HOME || process.env.USERPROFILE;
|
||||
const dbPath = path.join(home, '.qimingclaw', 'qimingclaw.db');
|
||||
const projectRoot = getProjectRoot();
|
||||
const checkPortShPath = path.join(__dirname, 'check-port.sh');
|
||||
const winBashPath = path.join(projectRoot, 'resources', 'git', 'bin', 'bash.exe');
|
||||
|
||||
// 与 shared/constants + shared/startupPorts 保持一致
|
||||
const DEFAULTS = {
|
||||
agent: 60001,
|
||||
fileServer: 60000,
|
||||
mcp: 18099,
|
||||
lanproxyLocal: 60002,
|
||||
vite: 60173,
|
||||
};
|
||||
|
||||
const LABELS = {
|
||||
agent: 'Agent(ComputerServer)',
|
||||
fileServer: 'FileServer',
|
||||
mcp: 'MCP Proxy',
|
||||
lanproxyLocal: 'Lanproxy',
|
||||
vite: 'Vite',
|
||||
};
|
||||
|
||||
function getSetting(dbPath, key) {
|
||||
try {
|
||||
// key 仅来自内部常量,仍做引号转义以防将来扩展;Windows 路径含反斜杠,双引号包裹路径即可
|
||||
const safeKey = String(key).replace(/'/g, "''");
|
||||
const quotedPath = dbPath.replace(/\\/g, '/'); // sqlite3 在 Windows 上通常也接受正斜杠
|
||||
const out = execSync(`sqlite3 "${quotedPath}" "SELECT value FROM settings WHERE key='${safeKey}';"`, {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
...(isWin && { shell: true }), // Windows 下若 sqlite3 为 .cmd 或路径含空格,需 shell
|
||||
}).trim();
|
||||
if (!out) return null;
|
||||
try {
|
||||
return JSON.parse(out);
|
||||
} catch {
|
||||
return out;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function resolvePortsFromSettings(getSettingFn) {
|
||||
const step1 = getSettingFn('step1_config');
|
||||
const agent = (step1 && step1.agentPort != null) ? step1.agentPort : DEFAULTS.agent;
|
||||
const fileServer = (step1 && step1.fileServerPort != null) ? step1.fileServerPort : DEFAULTS.fileServer;
|
||||
|
||||
const mcpRaw = getSettingFn('mcp_proxy_port');
|
||||
let mcp = DEFAULTS.mcp;
|
||||
if (typeof mcpRaw === 'number' && Number.isInteger(mcpRaw)) mcp = mcpRaw;
|
||||
else if (typeof mcpRaw === 'string') { const n = parseInt(mcpRaw, 10); if (!Number.isNaN(n)) mcp = n; }
|
||||
|
||||
return {
|
||||
agent,
|
||||
fileServer,
|
||||
mcp,
|
||||
lanproxyLocal: DEFAULTS.lanproxyLocal,
|
||||
vite: DEFAULTS.vite,
|
||||
};
|
||||
}
|
||||
|
||||
function getPortsToCheck(ports, includeVite) {
|
||||
const list = [
|
||||
{ name: 'agent', label: LABELS.agent, port: ports.agent },
|
||||
{ name: 'fileServer', label: LABELS.fileServer, port: ports.fileServer },
|
||||
{ name: 'mcp', label: LABELS.mcp, port: ports.mcp },
|
||||
{ name: 'lanproxyLocal', label: LABELS.lanproxyLocal, port: ports.lanproxyLocal },
|
||||
];
|
||||
if (includeVite) list.push({ name: 'vite', label: LABELS.vite, port: ports.vite });
|
||||
return list;
|
||||
}
|
||||
|
||||
/** 优先用 bash 执行 scripts/tools/check-port.sh(与 main 进程一致),否则回退 Node 内联逻辑 */
|
||||
function isPortInUse(port) {
|
||||
const bashPath = isWin ? (fs.existsSync(winBashPath) ? winBashPath : null) : 'bash';
|
||||
const scriptContent = fs.existsSync(checkPortShPath)
|
||||
? fs.readFileSync(checkPortShPath, 'utf8')
|
||||
: null;
|
||||
|
||||
if (bashPath && scriptContent) {
|
||||
const result = spawnSync(bashPath, ['-c', scriptContent, '_', String(port)], {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
if (result.status === 0 && result.stdout) {
|
||||
const text = (result.stdout && String(result.stdout)).trim();
|
||||
const pid = text && /^\d+$/.test(text) ? parseInt(text, 10) : undefined;
|
||||
return { inUse: true, pid };
|
||||
}
|
||||
return { inUse: false };
|
||||
}
|
||||
|
||||
// 回退:无 bash 或脚本时用 Node 内联 netstat/lsof
|
||||
try {
|
||||
if (isWin) {
|
||||
const out = execSync(`netstat -ano 2>nul | findstr ":${port} "`, {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
shell: true,
|
||||
});
|
||||
const text = (out && String(out)).trim();
|
||||
const inUse = text.length > 0;
|
||||
let pid;
|
||||
if (inUse) {
|
||||
const firstLine = text.split(/\r?\n/)[0] || '';
|
||||
const lastCol = firstLine.split(/\s+/).filter(Boolean).pop();
|
||||
if (lastCol && /^\d+$/.test(lastCol)) pid = parseInt(lastCol, 10);
|
||||
}
|
||||
return { inUse, pid };
|
||||
}
|
||||
const out = execSync(`lsof -t -i :${port} 2>/dev/null`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
||||
const text = (out && String(out)).trim();
|
||||
const inUse = text.length > 0;
|
||||
const pid = inUse && /^\d+$/.test(text) ? parseInt(text.split('\n')[0], 10) : undefined;
|
||||
return { inUse, pid };
|
||||
} catch {
|
||||
return { inUse: false };
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const includeVite = process.argv.includes('--vite');
|
||||
const getSettingFn = (key) => getSetting(dbPath, key);
|
||||
|
||||
let ports;
|
||||
let source = '';
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
ports = { ...DEFAULTS };
|
||||
source = '未找到 DB,使用默认端口';
|
||||
} else {
|
||||
try {
|
||||
ports = resolvePortsFromSettings(getSettingFn);
|
||||
source = `已从 ${dbPath} 读取配置`;
|
||||
} catch (err) {
|
||||
ports = { ...DEFAULTS };
|
||||
source = '读取 DB 失败(如未安装 sqlite3),使用默认端口';
|
||||
}
|
||||
}
|
||||
console.log('端口来源:', source);
|
||||
|
||||
const list = getPortsToCheck(ports, includeVite);
|
||||
console.log('检查端口:', list.map(({ label, port }) => `${label}=${port}`).join(', '));
|
||||
console.log('');
|
||||
|
||||
let hasInUse = false;
|
||||
for (const { label, port } of list) {
|
||||
const { inUse, pid } = isPortInUse(port);
|
||||
if (inUse) {
|
||||
hasInUse = true;
|
||||
console.log(` [占用] ${label} (${port}) ${pid ? `PID=${pid}` : ''}`);
|
||||
} else {
|
||||
console.log(` [空闲] ${label} (${port})`);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit(hasInUse ? 1 : 0);
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user