weacpx 0.1.0 → 0.1.1
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 +129 -11
- package/package.json +24 -24
package/dist/cli.js
CHANGED
|
@@ -2911,10 +2911,18 @@ class DaemonController {
|
|
|
2911
2911
|
paths;
|
|
2912
2912
|
deps;
|
|
2913
2913
|
statusStore;
|
|
2914
|
+
startupPollIntervalMs;
|
|
2915
|
+
startupTimeoutMs;
|
|
2916
|
+
onStartupPoll;
|
|
2914
2917
|
constructor(paths, deps) {
|
|
2915
2918
|
this.paths = paths;
|
|
2916
2919
|
this.deps = deps;
|
|
2917
2920
|
this.statusStore = new DaemonStatusStore(paths.statusFile);
|
|
2921
|
+
this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
|
|
2922
|
+
this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
|
|
2923
|
+
this.onStartupPoll = deps.onStartupPoll ?? (async () => {
|
|
2924
|
+
await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
|
|
2925
|
+
});
|
|
2918
2926
|
}
|
|
2919
2927
|
async getStatus() {
|
|
2920
2928
|
const pid = await this.loadPid();
|
|
@@ -2940,8 +2948,10 @@ class DaemonController {
|
|
|
2940
2948
|
if (current.state === "running") {
|
|
2941
2949
|
return { state: "already-running", pid: current.pid };
|
|
2942
2950
|
}
|
|
2951
|
+
await this.statusStore.clear();
|
|
2943
2952
|
const pid = await this.deps.spawnDetached();
|
|
2944
2953
|
await this.writePid(pid);
|
|
2954
|
+
await this.waitForStartupMetadata(pid);
|
|
2945
2955
|
return { state: "started", pid };
|
|
2946
2956
|
}
|
|
2947
2957
|
async stop() {
|
|
@@ -2976,6 +2986,21 @@ class DaemonController {
|
|
|
2976
2986
|
await rm2(this.paths.pidFile, { force: true });
|
|
2977
2987
|
await this.statusStore.clear();
|
|
2978
2988
|
}
|
|
2989
|
+
async waitForStartupMetadata(pid) {
|
|
2990
|
+
const deadline = Date.now() + this.startupTimeoutMs;
|
|
2991
|
+
while (Date.now() < deadline) {
|
|
2992
|
+
const status = await this.statusStore.load();
|
|
2993
|
+
if (status?.pid === pid) {
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
if (!this.deps.isProcessRunning(pid)) {
|
|
2997
|
+
await this.clearRuntimeFiles();
|
|
2998
|
+
throw new Error(`weacpx daemon exited before reporting ready state (pid ${pid})`);
|
|
2999
|
+
}
|
|
3000
|
+
await this.onStartupPoll();
|
|
3001
|
+
}
|
|
3002
|
+
throw new Error(`weacpx daemon did not report ready state within ${this.startupTimeoutMs}ms (pid ${pid})`);
|
|
3003
|
+
}
|
|
2979
3004
|
}
|
|
2980
3005
|
|
|
2981
3006
|
// src/daemon/create-daemon-controller.ts
|
|
@@ -2987,17 +3012,7 @@ function createDaemonController(paths, options) {
|
|
|
2987
3012
|
const stdoutHandle = await open(paths.stdoutLog, "a");
|
|
2988
3013
|
const stderrHandle = await open(paths.stderrLog, "a");
|
|
2989
3014
|
try {
|
|
2990
|
-
return await (options.spawnProcess ?? defaultSpawnProcess)(
|
|
2991
|
-
command: options.processExecPath,
|
|
2992
|
-
args: [options.cliEntryPath, "run"],
|
|
2993
|
-
options: {
|
|
2994
|
-
cwd: options.cwd,
|
|
2995
|
-
detached: true,
|
|
2996
|
-
env: options.env,
|
|
2997
|
-
stdio: ["ignore", stdoutHandle.fd, stderrHandle.fd],
|
|
2998
|
-
...(options.platform ?? process.platform) === "win32" ? { windowsHide: true } : {}
|
|
2999
|
-
}
|
|
3000
|
-
});
|
|
3015
|
+
return await (options.spawnProcess ?? defaultSpawnProcess)(buildSpawnRequest(paths, options, stdoutHandle.fd, stderrHandle.fd));
|
|
3001
3016
|
} finally {
|
|
3002
3017
|
await stdoutHandle.close();
|
|
3003
3018
|
await stderrHandle.close();
|
|
@@ -3014,11 +3029,114 @@ function defaultIsProcessRunning(pid) {
|
|
|
3014
3029
|
return false;
|
|
3015
3030
|
}
|
|
3016
3031
|
}
|
|
3032
|
+
function buildSpawnRequest(paths, options, stdoutFd, stderrFd) {
|
|
3033
|
+
const platform = options.platform ?? process.platform;
|
|
3034
|
+
if (platform === "win32") {
|
|
3035
|
+
return {
|
|
3036
|
+
mode: "windows-hidden",
|
|
3037
|
+
command: "powershell.exe",
|
|
3038
|
+
args: [
|
|
3039
|
+
"-NoProfile",
|
|
3040
|
+
"-NonInteractive",
|
|
3041
|
+
"-EncodedCommand",
|
|
3042
|
+
buildWindowsLauncherScript()
|
|
3043
|
+
],
|
|
3044
|
+
options: {
|
|
3045
|
+
cwd: options.cwd,
|
|
3046
|
+
env: {
|
|
3047
|
+
...options.env,
|
|
3048
|
+
WEACPX_DAEMON_COMMAND: options.processExecPath,
|
|
3049
|
+
WEACPX_DAEMON_ARG0: options.cliEntryPath,
|
|
3050
|
+
WEACPX_DAEMON_ARG1: "run",
|
|
3051
|
+
WEACPX_DAEMON_CWD: options.cwd,
|
|
3052
|
+
WEACPX_DAEMON_STDOUT: paths.stdoutLog,
|
|
3053
|
+
WEACPX_DAEMON_STDERR: paths.stderrLog
|
|
3054
|
+
},
|
|
3055
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
3056
|
+
windowsHide: true
|
|
3057
|
+
}
|
|
3058
|
+
};
|
|
3059
|
+
}
|
|
3060
|
+
return {
|
|
3061
|
+
mode: "direct",
|
|
3062
|
+
command: options.processExecPath,
|
|
3063
|
+
args: [options.cliEntryPath, "run"],
|
|
3064
|
+
options: {
|
|
3065
|
+
cwd: options.cwd,
|
|
3066
|
+
detached: true,
|
|
3067
|
+
env: options.env,
|
|
3068
|
+
stdio: ["ignore", stdoutFd, stderrFd]
|
|
3069
|
+
}
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
function buildWindowsLauncherScript() {
|
|
3073
|
+
const script = [
|
|
3074
|
+
"$process = Start-Process -FilePath $env:WEACPX_DAEMON_COMMAND `",
|
|
3075
|
+
" -ArgumentList @($env:WEACPX_DAEMON_ARG0, $env:WEACPX_DAEMON_ARG1) `",
|
|
3076
|
+
" -WorkingDirectory $env:WEACPX_DAEMON_CWD `",
|
|
3077
|
+
" -RedirectStandardOutput $env:WEACPX_DAEMON_STDOUT `",
|
|
3078
|
+
" -RedirectStandardError $env:WEACPX_DAEMON_STDERR `",
|
|
3079
|
+
" -WindowStyle Hidden `",
|
|
3080
|
+
" -PassThru",
|
|
3081
|
+
"[Console]::Out.WriteLine($process.Id)"
|
|
3082
|
+
].join(`
|
|
3083
|
+
`);
|
|
3084
|
+
return Buffer.from(script, "utf16le").toString("base64");
|
|
3085
|
+
}
|
|
3017
3086
|
async function defaultSpawnProcess(request) {
|
|
3087
|
+
if (request.mode === "windows-hidden") {
|
|
3088
|
+
return await spawnWindowsHiddenProcess(request);
|
|
3089
|
+
}
|
|
3018
3090
|
const child = spawn(request.command, request.args, request.options);
|
|
3019
3091
|
child.unref();
|
|
3020
3092
|
return child.pid ?? 0;
|
|
3021
3093
|
}
|
|
3094
|
+
async function spawnWindowsHiddenProcess(request) {
|
|
3095
|
+
return await new Promise((resolve, reject) => {
|
|
3096
|
+
const child = spawn(request.command, request.args, request.options);
|
|
3097
|
+
let stdout = "";
|
|
3098
|
+
let settled = false;
|
|
3099
|
+
child.stdout?.setEncoding("utf8");
|
|
3100
|
+
child.stdout?.on("data", (chunk) => {
|
|
3101
|
+
stdout += chunk;
|
|
3102
|
+
if (settled) {
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
const pid = Number.parseInt(stdout.trim(), 10);
|
|
3106
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
3107
|
+
return;
|
|
3108
|
+
}
|
|
3109
|
+
settled = true;
|
|
3110
|
+
child.unref();
|
|
3111
|
+
resolve(pid);
|
|
3112
|
+
});
|
|
3113
|
+
child.on("error", (error) => {
|
|
3114
|
+
if (settled) {
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
settled = true;
|
|
3118
|
+
reject(error);
|
|
3119
|
+
});
|
|
3120
|
+
child.on("close", (code) => {
|
|
3121
|
+
if (settled) {
|
|
3122
|
+
return;
|
|
3123
|
+
}
|
|
3124
|
+
if (code !== 0) {
|
|
3125
|
+
settled = true;
|
|
3126
|
+
reject(new Error(`Failed to launch hidden Windows daemon process (exit ${code ?? 1})`));
|
|
3127
|
+
return;
|
|
3128
|
+
}
|
|
3129
|
+
const pid = Number.parseInt(stdout.trim(), 10);
|
|
3130
|
+
if (!Number.isFinite(pid) || pid <= 0) {
|
|
3131
|
+
settled = true;
|
|
3132
|
+
reject(new Error("Failed to read daemon pid from hidden Windows launcher"));
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
settled = true;
|
|
3136
|
+
resolve(pid);
|
|
3137
|
+
});
|
|
3138
|
+
});
|
|
3139
|
+
}
|
|
3022
3140
|
async function defaultTerminateProcess(pid) {
|
|
3023
3141
|
await terminateProcessTree(pid);
|
|
3024
3142
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weacpx",
|
|
3
|
+
"description": "用微信远程控制 `acpx` 会话的控制台, 底层基于 `weixin-agent-sdk` 与 `acpx`",
|
|
4
|
+
"version": "0.1.1",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"doc": "docs",
|
|
8
|
+
"test": "tests"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/gadzan/weacpx.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"acpx",
|
|
16
|
+
"weixin-agent-sdk",
|
|
17
|
+
"openclaw",
|
|
18
|
+
"weixin"
|
|
19
|
+
],
|
|
20
|
+
"author": "gadzan",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/gadzan/weacpx/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/gadzan/weacpx#readme",
|
|
3
26
|
"type": "module",
|
|
4
27
|
"bin": {
|
|
5
28
|
"weacpx": "dist/cli.js"
|
|
@@ -27,28 +50,5 @@
|
|
|
27
50
|
"node-pty": "^1.1.0",
|
|
28
51
|
"qrcode-terminal": "^0.12.0",
|
|
29
52
|
"weixin-agent-sdk": "^0.2.0"
|
|
30
|
-
}
|
|
31
|
-
"description": "用微信远程控制 `acpx` 会话的控制台, 底层基于 `weixin-agent-sdk` 与 `acpx`",
|
|
32
|
-
"version": "0.1.0",
|
|
33
|
-
"main": "index.js",
|
|
34
|
-
"directories": {
|
|
35
|
-
"doc": "docs",
|
|
36
|
-
"test": "tests"
|
|
37
|
-
},
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "git+https://github.com/gadzan/weacpx.git"
|
|
41
|
-
},
|
|
42
|
-
"keywords": [
|
|
43
|
-
"acpx",
|
|
44
|
-
"weixin-agent-sdk",
|
|
45
|
-
"openclaw",
|
|
46
|
-
"weixin"
|
|
47
|
-
],
|
|
48
|
-
"author": "gadzan",
|
|
49
|
-
"license": "MIT",
|
|
50
|
-
"bugs": {
|
|
51
|
-
"url": "https://github.com/gadzan/weacpx/issues"
|
|
52
|
-
},
|
|
53
|
-
"homepage": "https://github.com/gadzan/weacpx#readme"
|
|
53
|
+
}
|
|
54
54
|
}
|