@awak-app/simy-cli 0.5.17 → 0.5.18
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/package.json
CHANGED
package/src/cli-contract.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "./durable-local-task-steps-contract.js";
|
|
14
14
|
import { buildExecutionCapabilityContract } from "./execution-capability-contract.js";
|
|
15
15
|
|
|
16
|
-
export const CLI_VERSION = "0.5.
|
|
16
|
+
export const CLI_VERSION = "0.5.18";
|
|
17
17
|
export const CLI_API_CONTRACT_VERSION = 10;
|
|
18
18
|
|
|
19
19
|
export function withCliContract(
|
package/src/desktop-host.js
CHANGED
|
@@ -210,7 +210,7 @@ if (isDesktopHostEntry()) {
|
|
|
210
210
|
const conflict = error?.code === PROCESS_CONFLICT_CODE;
|
|
211
211
|
process.stdout.write(`${JSON.stringify({
|
|
212
212
|
type: "error",
|
|
213
|
-
...(conflict ? { code: PROCESS_CONFLICT_CODE } : {}),
|
|
213
|
+
...(conflict ? { code: PROCESS_CONFLICT_CODE, conflicts: error.conflicts || [] } : {}),
|
|
214
214
|
message: conflict
|
|
215
215
|
? PROCESS_CONFLICT_MESSAGE
|
|
216
216
|
: "SIMY could not start. Check whether another SIMY window is already running, then reconnect.",
|
|
@@ -10,10 +10,11 @@ export const PROCESS_CONFLICT_MESSAGE =
|
|
|
10
10
|
"Another SIMY background service is already running for this Web address. Stop the existing CLI daemon, then reopen SIMY CLI.";
|
|
11
11
|
|
|
12
12
|
export class SimyProcessConflictError extends Error {
|
|
13
|
-
constructor(message = PROCESS_CONFLICT_MESSAGE) {
|
|
13
|
+
constructor(message = PROCESS_CONFLICT_MESSAGE, conflicts = []) {
|
|
14
14
|
super(message);
|
|
15
15
|
this.name = "SimyProcessConflictError";
|
|
16
16
|
this.code = PROCESS_CONFLICT_CODE;
|
|
17
|
+
this.conflicts = conflicts;
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -96,12 +97,13 @@ export async function assertNoDesktopHostForOrigin(
|
|
|
96
97
|
throw new SimyProcessConflictError();
|
|
97
98
|
}
|
|
98
99
|
|
|
99
|
-
export async function
|
|
100
|
+
export async function findLegacyCliDaemons(
|
|
100
101
|
webOrigin,
|
|
101
102
|
{
|
|
102
103
|
processes = listSameUserProcesses,
|
|
103
104
|
verifyEntry = verifySimyCliEntry,
|
|
104
105
|
currentPid = process.pid,
|
|
106
|
+
identity = processStart,
|
|
105
107
|
} = {},
|
|
106
108
|
) {
|
|
107
109
|
const requestedOrigin = normalizeWebOrigin(webOrigin);
|
|
@@ -113,13 +115,48 @@ export async function assertNoLegacyCliDaemon(
|
|
|
113
115
|
"SIMY could not verify whether another CLI service is running. Stop the existing CLI daemon, then reopen SIMY CLI.",
|
|
114
116
|
);
|
|
115
117
|
}
|
|
118
|
+
const conflicts = [];
|
|
116
119
|
for (const processInfo of candidates) {
|
|
117
|
-
if (processInfo.pid === currentPid) continue;
|
|
120
|
+
if (!Number.isSafeInteger(processInfo.pid) || processInfo.pid <= 0 || processInfo.pid === currentPid) continue;
|
|
118
121
|
const parsed = parseLegacyAgentArguments(processInfo.command ?? processInfo.args);
|
|
119
122
|
if (!parsed || !(await verifyEntry(parsed.entryPath))) continue;
|
|
120
123
|
if (parsed.webOrigin && parsed.webOrigin !== requestedOrigin) continue;
|
|
121
|
-
|
|
124
|
+
const startedAt = await identity(processInfo.pid).catch(() => null);
|
|
125
|
+
conflicts.push({ pid: processInfo.pid, entryPath: parsed.entryPath, startedAt });
|
|
122
126
|
}
|
|
127
|
+
return conflicts;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
export async function assertNoLegacyCliDaemon(webOrigin, options) {
|
|
132
|
+
const conflicts = await findLegacyCliDaemons(webOrigin, options);
|
|
133
|
+
if (conflicts.length) throw new SimyProcessConflictError(PROCESS_CONFLICT_MESSAGE, conflicts);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Re-discover same-user SIMY processes and compare their start time immediately
|
|
137
|
+
// before signaling. A PID alone is never sufficient authorization after a dialog.
|
|
138
|
+
export async function stopConfirmedCliConflicts(webOrigin, approved, { force = false,
|
|
139
|
+
discover = findLegacyCliDaemons, signal = process.kill.bind(process),
|
|
140
|
+
alive = processAlive, wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
141
|
+
} = {}) {
|
|
142
|
+
if (!Array.isArray(approved) || !approved.length || approved.length > 100) throw new Error("No confirmed SIMY process.");
|
|
143
|
+
const stopped = [];
|
|
144
|
+
for (const expected of approved) {
|
|
145
|
+
if (!Number.isSafeInteger(expected.pid) || expected.pid <= 0 || !expected.startedAt || typeof expected.entryPath !== "string") {
|
|
146
|
+
throw new Error("SIMY process identity is unavailable. Refresh and try again.");
|
|
147
|
+
}
|
|
148
|
+
const current = (await discover(webOrigin)).find((item) => item.pid === expected.pid);
|
|
149
|
+
if (!current) continue;
|
|
150
|
+
if (current.startedAt !== expected.startedAt || current.entryPath !== expected.entryPath) {
|
|
151
|
+
throw new Error("The process changed. Refresh before stopping it.");
|
|
152
|
+
}
|
|
153
|
+
try { signal(current.pid, force ? "SIGKILL" : "SIGTERM"); }
|
|
154
|
+
catch (error) { if (error.code !== "ESRCH") throw error; }
|
|
155
|
+
stopped.push(current);
|
|
156
|
+
}
|
|
157
|
+
for (let n = 0; n < 40 && stopped.some((item) => alive(item.pid)); n++) await wait(250);
|
|
158
|
+
const remaining = await discover(webOrigin);
|
|
159
|
+
return { remaining: remaining.filter((item) => stopped.some((old) => old.pid === item.pid && old.startedAt === item.startedAt && old.entryPath === item.entryPath)) };
|
|
123
160
|
}
|
|
124
161
|
|
|
125
162
|
export function parseLegacyAgentArguments(args) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { stopConfirmedCliConflicts } from "./desktop-process-lock.js";
|
|
2
|
+
import { resolveWebOrigin } from "./web-origin.js";
|
|
3
|
+
|
|
4
|
+
// Invoked by the native confirmation dialog, never automatically at startup.
|
|
5
|
+
let input = "";
|
|
6
|
+
try {
|
|
7
|
+
for await (const chunk of process.stdin) {
|
|
8
|
+
input += chunk;
|
|
9
|
+
if (input.length > 32768) throw new Error("Confirmation is too large.");
|
|
10
|
+
}
|
|
11
|
+
const request = JSON.parse(input);
|
|
12
|
+
const result = await stopConfirmedCliConflicts(resolveWebOrigin(null, process.env), request.conflicts, { force: request.force === true });
|
|
13
|
+
process.stdout.write(JSON.stringify({ ok: true, ...result }));
|
|
14
|
+
} catch {
|
|
15
|
+
process.stdout.write(JSON.stringify({ ok: false }));
|
|
16
|
+
process.exitCode = 1;
|
|
17
|
+
}
|