@wu529778790/open-im 1.5.5-beta.3 → 1.5.5-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +42 -38
- package/dist/config-web.js +5 -5
- package/dist/index.js +1 -17
- package/dist/manager-control.d.ts +17 -0
- package/dist/manager-control.js +160 -0
- package/dist/manager.d.ts +1 -0
- package/dist/manager.js +37 -0
- package/dist/service-control.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { main, needsSetup, runInteractiveSetup } from "./index.js";
|
|
3
3
|
import { loadConfig } from "./config.js";
|
|
4
4
|
import { checkAndUpdate } from "./check-update.js";
|
|
5
|
-
import { getWebConfigUrl, runWebConfigFlow } from "./config-web.js";
|
|
6
|
-
import {
|
|
5
|
+
import { getWebConfigUrl, openWebConfigUrl, runWebConfigFlow } from "./config-web.js";
|
|
6
|
+
import { getManagerStatus, startManagerProcess, stopManagerProcess } from "./manager-control.js";
|
|
7
|
+
import { stopBackgroundService } from "./service-control.js";
|
|
7
8
|
async function ensureConfigured(mode) {
|
|
8
9
|
const forceWeb = process.env.OPEN_IM_FORCE_WEB === "1";
|
|
9
10
|
if (mode !== "init" && !needsSetup()) {
|
|
@@ -30,80 +31,83 @@ async function ensureConfigured(mode) {
|
|
|
30
31
|
return false;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
|
-
// ============================================================================
|
|
34
|
-
// 命令处理
|
|
35
|
-
// ============================================================================
|
|
36
34
|
async function cmdStart() {
|
|
37
|
-
const status =
|
|
35
|
+
const status = getManagerStatus();
|
|
38
36
|
if (status.running && status.pid) {
|
|
39
|
-
console.log("\
|
|
40
|
-
console.log(`
|
|
41
|
-
console.log(`
|
|
37
|
+
console.log("\nopen-im is already running in the background.");
|
|
38
|
+
console.log(` pid: ${status.pid}`);
|
|
39
|
+
console.log(` config page: ${getWebConfigUrl()}`);
|
|
42
40
|
return;
|
|
43
41
|
}
|
|
44
|
-
removePid();
|
|
45
42
|
if (!(await ensureConfigured("start"))) {
|
|
46
43
|
process.exit(1);
|
|
47
44
|
}
|
|
48
|
-
// 检查并自动更新到最新版本
|
|
49
45
|
const { updated } = await checkAndUpdate();
|
|
50
46
|
if (updated) {
|
|
51
47
|
process.exit(0);
|
|
52
48
|
}
|
|
53
49
|
process.env.OPEN_IM_AUTO_OPEN_CONFIG_ONCE = "1";
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
try {
|
|
51
|
+
const child = await startManagerProcess(process.cwd());
|
|
52
|
+
console.log("\nopen-im started in the background.");
|
|
53
|
+
console.log(` pid: ${child.pid}`);
|
|
54
|
+
console.log(` config page: ${getWebConfigUrl()}`);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
delete process.env.OPEN_IM_AUTO_OPEN_CONFIG_ONCE;
|
|
58
|
+
}
|
|
59
59
|
}
|
|
60
60
|
async function cmdStop() {
|
|
61
|
-
const status =
|
|
61
|
+
const status = getManagerStatus();
|
|
62
62
|
if (!status.pid) {
|
|
63
|
-
console.log("open-im
|
|
63
|
+
console.log("open-im is not running in the background.");
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
console.log(
|
|
66
|
+
await stopBackgroundService();
|
|
67
|
+
const result = await stopManagerProcess();
|
|
68
|
+
console.log("\nopen-im stopped.");
|
|
69
|
+
console.log(` pid: ${result.pid}`);
|
|
69
70
|
}
|
|
70
71
|
async function cmdInit() {
|
|
71
|
-
console.log("\
|
|
72
|
+
console.log("\nopen-im local control\n");
|
|
73
|
+
const status = getManagerStatus();
|
|
74
|
+
if (status.running && status.pid) {
|
|
75
|
+
openWebConfigUrl();
|
|
76
|
+
console.log(`Config page is already running: ${getWebConfigUrl()}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
72
79
|
const saved = await ensureConfigured("init");
|
|
73
80
|
if (!saved) {
|
|
74
|
-
console.log("\
|
|
81
|
+
console.log("\nConfiguration was not completed.");
|
|
75
82
|
process.exit(1);
|
|
76
83
|
}
|
|
77
|
-
console.log("\
|
|
78
|
-
console.log("\
|
|
84
|
+
console.log("\nConfiguration saved.");
|
|
85
|
+
console.log("\nYou can start the app with:");
|
|
79
86
|
console.log(" open-im start");
|
|
80
87
|
console.log(" open-im dev");
|
|
81
88
|
}
|
|
82
89
|
async function cmdDev() {
|
|
83
90
|
if (!(await ensureConfigured("dev"))) {
|
|
84
|
-
console.log("
|
|
91
|
+
console.log("Configuration was not completed.");
|
|
85
92
|
process.exit(1);
|
|
86
93
|
}
|
|
87
94
|
await main();
|
|
88
95
|
}
|
|
89
96
|
function showHelp(exitCode = 0) {
|
|
90
97
|
console.log(`
|
|
91
|
-
|
|
98
|
+
Usage: open-im <command>
|
|
92
99
|
|
|
93
|
-
|
|
94
|
-
start
|
|
95
|
-
stop
|
|
96
|
-
init
|
|
97
|
-
dev
|
|
100
|
+
Commands:
|
|
101
|
+
start Run the full app in the background
|
|
102
|
+
stop Stop the full app
|
|
103
|
+
init Open the local web configuration page
|
|
104
|
+
dev Run in the foreground for debugging
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
-h, --help
|
|
106
|
+
Options:
|
|
107
|
+
-h, --help Show this help message
|
|
101
108
|
`);
|
|
102
109
|
process.exit(exitCode);
|
|
103
110
|
}
|
|
104
|
-
// ============================================================================
|
|
105
|
-
// 命令路由
|
|
106
|
-
// ============================================================================
|
|
107
111
|
const cmd = process.argv[2];
|
|
108
112
|
const commands = {
|
|
109
113
|
start: cmdStart,
|
|
@@ -127,6 +131,6 @@ else if (commands[cmd]) {
|
|
|
127
131
|
});
|
|
128
132
|
}
|
|
129
133
|
else {
|
|
130
|
-
console.error(
|
|
134
|
+
console.error(`Unknown command: ${cmd}`);
|
|
131
135
|
showHelp(1);
|
|
132
136
|
}
|
package/dist/config-web.js
CHANGED
|
@@ -288,8 +288,8 @@ const PAGE_HTML = String.raw `<!doctype html>
|
|
|
288
288
|
<div class="actions">
|
|
289
289
|
<button id="validateButton" class="warning">Validate</button>
|
|
290
290
|
<button id="saveButton" class="secondary">Save config</button>
|
|
291
|
-
<button id="startButton">Start
|
|
292
|
-
<button id="stopButton" class="danger">Stop
|
|
291
|
+
<button id="startButton">Start bridge</button>
|
|
292
|
+
<button id="stopButton" class="danger">Stop bridge</button>
|
|
293
293
|
</div>
|
|
294
294
|
<div class="message" id="message"></div>
|
|
295
295
|
</section>
|
|
@@ -315,7 +315,7 @@ const PAGE_HTML = String.raw `<!doctype html>
|
|
|
315
315
|
const payload = () => ({ platforms: { telegram: { enabled: el("telegram-enabled").checked, botToken: el("telegram-botToken").value, proxy: el("telegram-proxy").value, allowedUserIds: el("telegram-allowedUserIds").value }, feishu: { enabled: el("feishu-enabled").checked, appId: el("feishu-appId").value, appSecret: el("feishu-appSecret").value, allowedUserIds: el("feishu-allowedUserIds").value }, wework: { enabled: el("wework-enabled").checked, corpId: el("wework-corpId").value, secret: el("wework-secret").value, allowedUserIds: el("wework-allowedUserIds").value }, dingtalk: { enabled: el("dingtalk-enabled").checked, clientId: el("dingtalk-clientId").value, clientSecret: el("dingtalk-clientSecret").value, cardTemplateId: el("dingtalk-cardTemplateId").value, allowedUserIds: el("dingtalk-allowedUserIds").value } }, ai: { aiCommand: el("ai-aiCommand").value, claudeCliPath: el("ai-claudeCliPath").value, claudeWorkDir: el("ai-claudeWorkDir").value, claudeSkipPermissions: el("ai-claudeSkipPermissions").checked, claudeTimeoutMs: Number(el("ai-claudeTimeoutMs").value || "0"), claudeModel: el("ai-claudeModel").value, cursorCliPath: el("ai-cursorCliPath").value, codexCliPath: el("ai-codexCliPath").value, codexProxy: el("ai-codexProxy").value, hookPort: Number(el("ai-hookPort").value || "0"), logLevel: el("ai-logLevel").value, useSdkMode: el("ai-useSdkMode").checked } });
|
|
316
316
|
async function request(path, options={}) { const response = await fetch(path, { headers: { "content-type": "application/json" }, ...options }); const body = await response.json(); if (!response.ok) throw new Error(body.error || "Request failed"); return body; }
|
|
317
317
|
function fill(data, meta) { el("configPath").textContent = meta.configPath; el("modeBadge").textContent = "Flow: " + meta.mode; el("telegram-enabled").checked = data.platforms.telegram.enabled; el("telegram-botToken").value = data.platforms.telegram.botToken; el("telegram-proxy").value = data.platforms.telegram.proxy; el("telegram-allowedUserIds").value = data.platforms.telegram.allowedUserIds; el("feishu-enabled").checked = data.platforms.feishu.enabled; el("feishu-appId").value = data.platforms.feishu.appId; el("feishu-appSecret").value = data.platforms.feishu.appSecret; el("feishu-allowedUserIds").value = data.platforms.feishu.allowedUserIds; el("wework-enabled").checked = data.platforms.wework.enabled; el("wework-corpId").value = data.platforms.wework.corpId; el("wework-secret").value = data.platforms.wework.secret; el("wework-allowedUserIds").value = data.platforms.wework.allowedUserIds; el("dingtalk-enabled").checked = data.platforms.dingtalk.enabled; el("dingtalk-clientId").value = data.platforms.dingtalk.clientId; el("dingtalk-clientSecret").value = data.platforms.dingtalk.clientSecret; el("dingtalk-cardTemplateId").value = data.platforms.dingtalk.cardTemplateId; el("dingtalk-allowedUserIds").value = data.platforms.dingtalk.allowedUserIds; el("ai-aiCommand").value = data.ai.aiCommand; el("ai-claudeCliPath").value = data.ai.claudeCliPath; el("ai-claudeWorkDir").value = data.ai.claudeWorkDir; el("ai-claudeSkipPermissions").checked = data.ai.claudeSkipPermissions; el("ai-claudeTimeoutMs").value = String(data.ai.claudeTimeoutMs); el("ai-claudeModel").value = data.ai.claudeModel; el("ai-cursorCliPath").value = data.ai.cursorCliPath; el("ai-codexCliPath").value = data.ai.codexCliPath; el("ai-codexProxy").value = data.ai.codexProxy; el("ai-hookPort").value = String(data.ai.hookPort); el("ai-logLevel").value = data.ai.logLevel; el("ai-useSdkMode").checked = data.ai.useSdkMode; updateVisualState(); }
|
|
318
|
-
async function refreshStatus() { const data = await request("/api/service/status"); el("serviceState").textContent = data.running ? ("
|
|
318
|
+
async function refreshStatus() { const data = await request("/api/service/status"); el("serviceState").textContent = data.running ? ("Bridge running (pid " + data.pid + ")") : "Bridge stopped"; el("statusMeta").textContent = data.running ? "Bridge worker is active." : "Bridge worker is currently stopped."; }
|
|
319
319
|
async function boot() { setBusy(true); try { const data = await request("/api/config"); fill(data.payload, data.meta); await refreshStatus(); setMessage("Control surface ready.", "success"); } catch (error) { setMessage(error.message || String(error), "error"); } finally { setBusy(false); } setInterval(() => { refreshStatus().catch(() => {}); }, 5000); ids.forEach((id) => { const node = el(id); if (node) node.addEventListener("input", updateVisualState); if (node) node.addEventListener("change", updateVisualState); }); }
|
|
320
320
|
async function validate() { setBusy(true); try { const data = await request("/api/config/validate", { method: "POST", body: JSON.stringify(payload()) }); setMessage(data.message, "success"); } catch (error) { setMessage(error.message || String(error), "error"); } finally { setBusy(false); } }
|
|
321
321
|
async function save() { setBusy(true); try { const data = await request("/api/config/save?final=1", { method: "POST", body: JSON.stringify(payload()) }); setMessage(data.message, "success"); } catch (error) { setMessage(error.message || String(error), "error"); } finally { setBusy(false); } }
|
|
@@ -400,7 +400,7 @@ export async function startWebConfigServer(options) {
|
|
|
400
400
|
try {
|
|
401
401
|
loadConfig();
|
|
402
402
|
const started = startBackgroundService(options.cwd);
|
|
403
|
-
json(response, 200, { message: `
|
|
403
|
+
json(response, 200, { message: `Bridge started with pid ${started.pid}.`, pid: started.pid });
|
|
404
404
|
if (!options.persistent) {
|
|
405
405
|
setTimeout(() => finishFlow("saved"), 120);
|
|
406
406
|
}
|
|
@@ -413,7 +413,7 @@ export async function startWebConfigServer(options) {
|
|
|
413
413
|
if (request.method === "POST" && requestUrl.pathname === "/api/service/stop") {
|
|
414
414
|
try {
|
|
415
415
|
const result = await stopBackgroundService();
|
|
416
|
-
json(response, 200, { message: result.pid ? `
|
|
416
|
+
json(response, 200, { message: result.pid ? `Bridge stopped (pid ${result.pid}).` : "Bridge was already stopped." });
|
|
417
417
|
}
|
|
418
418
|
catch (error) {
|
|
419
419
|
json(response, 400, { error: error instanceof Error ? error.message : String(error) });
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { writeFileSync, existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { loadConfig, needsSetup } from "./config.js";
|
|
5
5
|
import { runInteractiveSetup, runClaudeApiSetup } from "./setup.js";
|
|
6
|
-
import {
|
|
6
|
+
import { runWebConfigFlow } from "./config-web.js";
|
|
7
7
|
// 导出供 cli.ts 使用
|
|
8
8
|
export { needsSetup, runInteractiveSetup };
|
|
9
9
|
import { initTelegram, stopTelegram } from "./telegram/client.js";
|
|
@@ -30,7 +30,6 @@ import { initPermissionModes } from "./permission-mode/session-mode.js";
|
|
|
30
30
|
import { createRequire } from "node:module";
|
|
31
31
|
const require = createRequire(import.meta.url);
|
|
32
32
|
const { version: APP_VERSION } = require("../package.json");
|
|
33
|
-
const CONFIG_UI_ONCE_FILE = join(APP_HOME, ".config-ui-once");
|
|
34
33
|
const log = createLogger("Main");
|
|
35
34
|
async function sendLifecycleNotification(platform, message) {
|
|
36
35
|
const telegramChatId = getActiveChatId("telegram");
|
|
@@ -121,20 +120,6 @@ export async function main() {
|
|
|
121
120
|
initLogger(config.logDir, config.logLevel);
|
|
122
121
|
loadActiveChats();
|
|
123
122
|
initPermissionModes();
|
|
124
|
-
let configWebServer = null;
|
|
125
|
-
try {
|
|
126
|
-
configWebServer = await startWebConfigServer({ mode: "start", cwd: process.cwd(), persistent: true });
|
|
127
|
-
log.info(`Web config page available at ${configWebServer.url}`);
|
|
128
|
-
if (process.env.OPEN_IM_AUTO_OPEN_CONFIG_ONCE === "1" && !existsSync(CONFIG_UI_ONCE_FILE)) {
|
|
129
|
-
if (!existsSync(APP_HOME))
|
|
130
|
-
mkdirSync(APP_HOME, { recursive: true });
|
|
131
|
-
writeFileSync(CONFIG_UI_ONCE_FILE, "1", "utf-8");
|
|
132
|
-
openWebConfigUrl();
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
log.warn("Failed to start web config page:", error);
|
|
137
|
-
}
|
|
138
123
|
// 当配置为跳过权限时,设置 CC_SKIP_PERMISSIONS 让权限服务器自动放行
|
|
139
124
|
// 否则 Cursor/Claude 请求工具权限时会卡住等待用户 /allow
|
|
140
125
|
if (config.claudeSkipPermissions) {
|
|
@@ -256,7 +241,6 @@ export async function main() {
|
|
|
256
241
|
dingtalkHandle?.stop();
|
|
257
242
|
stopDingTalk();
|
|
258
243
|
stopPermissionServer();
|
|
259
|
-
await configWebServer?.close().catch(() => { });
|
|
260
244
|
sessionManager.destroy();
|
|
261
245
|
cleanupAdapters();
|
|
262
246
|
flushActiveChats();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function getManagerPid(): number | null;
|
|
2
|
+
export declare function removeManagerPid(): void;
|
|
3
|
+
export declare function removeManagerReady(): void;
|
|
4
|
+
export declare function writeManagerReady(): void;
|
|
5
|
+
export declare function isManagerReady(): boolean;
|
|
6
|
+
export declare function writeManagerPid(pid: number): void;
|
|
7
|
+
export declare function getManagerStatus(): {
|
|
8
|
+
running: boolean;
|
|
9
|
+
pid: number | null;
|
|
10
|
+
};
|
|
11
|
+
export declare function startManagerProcess(cwd: string): Promise<{
|
|
12
|
+
pid: number;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function stopManagerProcess(): Promise<{
|
|
15
|
+
pid: number | null;
|
|
16
|
+
stopped: boolean;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, extname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { APP_HOME } from "./constants.js";
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const PID_FILE = join(APP_HOME, "open-im.pid");
|
|
8
|
+
const READY_FILE = join(APP_HOME, "open-im.ready");
|
|
9
|
+
function getManagerEntry() {
|
|
10
|
+
const extension = extname(fileURLToPath(import.meta.url));
|
|
11
|
+
if (extension === ".ts") {
|
|
12
|
+
return {
|
|
13
|
+
command: process.execPath,
|
|
14
|
+
args: ["--import", "tsx", join(__dirname, "manager.ts")],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
command: process.execPath,
|
|
19
|
+
args: [join(__dirname, "manager.js")],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function isRunning(pid) {
|
|
23
|
+
try {
|
|
24
|
+
if (process.platform === "win32") {
|
|
25
|
+
const result = execFileSync("tasklist", ["/FI", `PID eq ${pid}`, "/NH"], {
|
|
26
|
+
stdio: "pipe",
|
|
27
|
+
windowsHide: true,
|
|
28
|
+
}).toString();
|
|
29
|
+
return result.includes(String(pid));
|
|
30
|
+
}
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function getManagerPid() {
|
|
39
|
+
if (!existsSync(PID_FILE))
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
const pid = parseInt(readFileSync(PID_FILE, "utf-8").trim(), 10);
|
|
43
|
+
return Number.isNaN(pid) ? null : pid;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function removeManagerPid() {
|
|
50
|
+
try {
|
|
51
|
+
if (existsSync(PID_FILE))
|
|
52
|
+
unlinkSync(PID_FILE);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
/* ignore */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function removeManagerReady() {
|
|
59
|
+
try {
|
|
60
|
+
if (existsSync(READY_FILE))
|
|
61
|
+
unlinkSync(READY_FILE);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* ignore */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export function writeManagerReady() {
|
|
68
|
+
if (!existsSync(APP_HOME))
|
|
69
|
+
mkdirSync(APP_HOME, { recursive: true });
|
|
70
|
+
writeFileSync(READY_FILE, "1", "utf-8");
|
|
71
|
+
}
|
|
72
|
+
export function isManagerReady() {
|
|
73
|
+
return existsSync(READY_FILE);
|
|
74
|
+
}
|
|
75
|
+
export function writeManagerPid(pid) {
|
|
76
|
+
if (!existsSync(APP_HOME))
|
|
77
|
+
mkdirSync(APP_HOME, { recursive: true });
|
|
78
|
+
writeFileSync(PID_FILE, String(pid), "utf-8");
|
|
79
|
+
}
|
|
80
|
+
export function getManagerStatus() {
|
|
81
|
+
const pid = getManagerPid();
|
|
82
|
+
if (!pid)
|
|
83
|
+
return { running: false, pid: null };
|
|
84
|
+
if (!isRunning(pid)) {
|
|
85
|
+
removeManagerReady();
|
|
86
|
+
removeManagerPid();
|
|
87
|
+
return { running: false, pid: null };
|
|
88
|
+
}
|
|
89
|
+
return { running: true, pid };
|
|
90
|
+
}
|
|
91
|
+
export async function startManagerProcess(cwd) {
|
|
92
|
+
const current = getManagerStatus();
|
|
93
|
+
if (current.running && current.pid) {
|
|
94
|
+
if (isManagerReady()) {
|
|
95
|
+
return { pid: current.pid };
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Manager process exists but is not ready yet.");
|
|
98
|
+
}
|
|
99
|
+
removeManagerReady();
|
|
100
|
+
removeManagerPid();
|
|
101
|
+
const entry = getManagerEntry();
|
|
102
|
+
const child = spawn(entry.command, entry.args, {
|
|
103
|
+
detached: true,
|
|
104
|
+
stdio: "ignore",
|
|
105
|
+
cwd,
|
|
106
|
+
env: process.env,
|
|
107
|
+
windowsHide: process.platform === "win32",
|
|
108
|
+
});
|
|
109
|
+
child.unref();
|
|
110
|
+
if (!child.pid) {
|
|
111
|
+
throw new Error("Failed to start manager process.");
|
|
112
|
+
}
|
|
113
|
+
writeManagerPid(child.pid);
|
|
114
|
+
const deadline = Date.now() + 8000;
|
|
115
|
+
while (Date.now() < deadline) {
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
117
|
+
if (!isRunning(child.pid)) {
|
|
118
|
+
removeManagerReady();
|
|
119
|
+
removeManagerPid();
|
|
120
|
+
throw new Error("Manager process exited before becoming ready.");
|
|
121
|
+
}
|
|
122
|
+
if (isManagerReady()) {
|
|
123
|
+
return { pid: child.pid };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
removeManagerReady();
|
|
127
|
+
removeManagerPid();
|
|
128
|
+
try {
|
|
129
|
+
process.kill(child.pid, "SIGTERM");
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
/* ignore */
|
|
133
|
+
}
|
|
134
|
+
throw new Error("Manager process did not become ready in time.");
|
|
135
|
+
}
|
|
136
|
+
export async function stopManagerProcess() {
|
|
137
|
+
const pid = getManagerPid();
|
|
138
|
+
if (!pid) {
|
|
139
|
+
removeManagerReady();
|
|
140
|
+
return { pid: null, stopped: false };
|
|
141
|
+
}
|
|
142
|
+
if (!isRunning(pid)) {
|
|
143
|
+
removeManagerReady();
|
|
144
|
+
removeManagerPid();
|
|
145
|
+
return { pid, stopped: true };
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
process.kill(pid, "SIGTERM");
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
/* ignore */
|
|
153
|
+
}
|
|
154
|
+
if (isRunning(pid)) {
|
|
155
|
+
process.kill(pid, "SIGKILL");
|
|
156
|
+
}
|
|
157
|
+
removeManagerReady();
|
|
158
|
+
removeManagerPid();
|
|
159
|
+
return { pid, stopped: true };
|
|
160
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/manager.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { APP_HOME } from "./constants.js";
|
|
4
|
+
import { openWebConfigUrl, startWebConfigServer } from "./config-web.js";
|
|
5
|
+
import { removeManagerPid, removeManagerReady, writeManagerReady } from "./manager-control.js";
|
|
6
|
+
import { startBackgroundService, stopBackgroundService } from "./service-control.js";
|
|
7
|
+
const CONFIG_UI_ONCE_FILE = join(APP_HOME, ".config-ui-once");
|
|
8
|
+
async function main() {
|
|
9
|
+
const web = await startWebConfigServer({ mode: "start", cwd: process.cwd(), persistent: true });
|
|
10
|
+
startBackgroundService(process.cwd());
|
|
11
|
+
writeManagerReady();
|
|
12
|
+
if (process.env.OPEN_IM_AUTO_OPEN_CONFIG_ONCE === "1" && !existsSync(CONFIG_UI_ONCE_FILE)) {
|
|
13
|
+
if (!existsSync(APP_HOME))
|
|
14
|
+
mkdirSync(APP_HOME, { recursive: true });
|
|
15
|
+
writeFileSync(CONFIG_UI_ONCE_FILE, "1", "utf-8");
|
|
16
|
+
openWebConfigUrl();
|
|
17
|
+
}
|
|
18
|
+
const shutdown = async () => {
|
|
19
|
+
await web.close().catch(() => { });
|
|
20
|
+
await stopBackgroundService().catch(() => { });
|
|
21
|
+
removeManagerReady();
|
|
22
|
+
removeManagerPid();
|
|
23
|
+
process.exit(0);
|
|
24
|
+
};
|
|
25
|
+
process.on("SIGINT", () => shutdown().catch(() => process.exit(1)));
|
|
26
|
+
process.on("SIGTERM", () => shutdown().catch(() => process.exit(1)));
|
|
27
|
+
}
|
|
28
|
+
const isEntry = process.argv[1]?.replace(/\\/g, "/").endsWith("/manager.js") ||
|
|
29
|
+
process.argv[1]?.replace(/\\/g, "/").endsWith("/manager.ts");
|
|
30
|
+
if (isEntry) {
|
|
31
|
+
main().catch((error) => {
|
|
32
|
+
console.error("Manager fatal error:", error);
|
|
33
|
+
removeManagerReady();
|
|
34
|
+
removeManagerPid();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
}
|
package/dist/service-control.js
CHANGED
|
@@ -4,7 +4,7 @@ import { dirname, extname, join } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { APP_HOME, SHUTDOWN_PORT } from "./constants.js";
|
|
6
6
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const PID_FILE = join(APP_HOME, "open-im.pid");
|
|
7
|
+
const PID_FILE = join(APP_HOME, "open-im-worker.pid");
|
|
8
8
|
const PORT_FILE = join(APP_HOME, "open-im.port");
|
|
9
9
|
function getServiceEntry() {
|
|
10
10
|
const extension = extname(fileURLToPath(import.meta.url));
|