@cydm/happy-elves 0.1.0-beta.271 → 0.1.0-beta.272
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.
|
@@ -255,7 +255,10 @@ async function waitForDaemonLifecycle(client, machineId, requestId, action, opti
|
|
|
255
255
|
catch (error) {
|
|
256
256
|
lastError = error;
|
|
257
257
|
const code = errorCode(error);
|
|
258
|
-
if (code !== "MACHINE_OFFLINE" &&
|
|
258
|
+
if (code !== "MACHINE_OFFLINE" &&
|
|
259
|
+
code !== "COMMAND_TIMEOUT" &&
|
|
260
|
+
code !== "RELAY_CONNECT_TIMEOUT" &&
|
|
261
|
+
code !== "RELAY_WEBSOCKET_ERROR")
|
|
259
262
|
throw error;
|
|
260
263
|
}
|
|
261
264
|
await sleep(1000);
|
package/apps/cli/package.json
CHANGED
|
@@ -238,6 +238,8 @@ import fs from "node:fs/promises";
|
|
|
238
238
|
import path from "node:path";
|
|
239
239
|
|
|
240
240
|
const input = ${inputJson};
|
|
241
|
+
let daemonStopAttempted = false;
|
|
242
|
+
let daemonStopped = false;
|
|
241
243
|
|
|
242
244
|
main().catch(async (error) => {
|
|
243
245
|
await fail(error);
|
|
@@ -263,6 +265,11 @@ async function main() {
|
|
|
263
265
|
async function fail(error) {
|
|
264
266
|
const message = error instanceof Error ? error.stack || error.message : String(error);
|
|
265
267
|
await log("runner failed: " + message).catch(() => {});
|
|
268
|
+
if (daemonStopAttempted) {
|
|
269
|
+
await restartDaemonAfterFailure().catch(async (restartError) => {
|
|
270
|
+
await log("failure recovery restart failed: " + String(restartError instanceof Error ? restartError.message : restartError)).catch(() => {});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
266
273
|
await updateState({ status: "failed", finishedAt: new Date().toISOString(), error: String(error instanceof Error ? error.message : error) }).catch(() => {});
|
|
267
274
|
}
|
|
268
275
|
|
|
@@ -280,11 +287,13 @@ async function log(line) {
|
|
|
280
287
|
}
|
|
281
288
|
|
|
282
289
|
async function stopDaemon() {
|
|
290
|
+
daemonStopAttempted = true;
|
|
283
291
|
await updateState({ status: "stopping" });
|
|
284
292
|
const pid = await readPid();
|
|
285
293
|
if (!pid || pid === process.pid) {
|
|
286
294
|
await fs.rm(input.pidPath, { force: true }).catch(() => {});
|
|
287
295
|
await log("no managed daemon pid to stop");
|
|
296
|
+
daemonStopped = true;
|
|
288
297
|
return;
|
|
289
298
|
}
|
|
290
299
|
await log("stopping daemon pid " + pid);
|
|
@@ -298,6 +307,7 @@ async function stopDaemon() {
|
|
|
298
307
|
if (!isPidAlive(pid)) {
|
|
299
308
|
await fs.rm(input.pidPath, { force: true }).catch(() => {});
|
|
300
309
|
await log("daemon stopped");
|
|
310
|
+
daemonStopped = true;
|
|
301
311
|
return;
|
|
302
312
|
}
|
|
303
313
|
await delay(200);
|
|
@@ -333,6 +343,22 @@ async function startDaemon() {
|
|
|
333
343
|
await spawnDetached(commandName("happy-elves"), ["daemon", "start", "--json"]);
|
|
334
344
|
}
|
|
335
345
|
|
|
346
|
+
async function restartDaemonAfterFailure() {
|
|
347
|
+
const currentlyRunning = await captureStatus().catch(() => undefined);
|
|
348
|
+
if (currentlyRunning?.ok === true && (currentlyRunning.data?.local?.running === true || currentlyRunning.data?.running === true)) {
|
|
349
|
+
await log("failure recovery skipped: daemon already running");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (!daemonStopped) {
|
|
353
|
+
await log("failure recovery skipped: daemon stop did not complete");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
await log("failure recovery attempting daemon restart");
|
|
357
|
+
await startDaemon();
|
|
358
|
+
await waitForDaemonRunning();
|
|
359
|
+
await log("failure recovery daemon running");
|
|
360
|
+
}
|
|
361
|
+
|
|
336
362
|
async function waitForDaemonRunning() {
|
|
337
363
|
const deadline = Date.now() + 45_000;
|
|
338
364
|
while (Date.now() < deadline) {
|
|
@@ -369,7 +395,8 @@ function statusCommand() {
|
|
|
369
395
|
}
|
|
370
396
|
|
|
371
397
|
async function spawnDetached(command, args) {
|
|
372
|
-
const
|
|
398
|
+
const spec = spawnSpec(command, args);
|
|
399
|
+
const child = spawn(spec.command, spec.args, {
|
|
373
400
|
cwd: input.configDir,
|
|
374
401
|
detached: true,
|
|
375
402
|
env: { ...process.env, HAPPY_ELVES_HOME: input.homeEnv },
|
|
@@ -377,13 +404,14 @@ async function spawnDetached(command, args) {
|
|
|
377
404
|
windowsHide: true,
|
|
378
405
|
});
|
|
379
406
|
child.unref();
|
|
380
|
-
await log("spawned " + command + " pid " + (child.pid || "unknown"));
|
|
407
|
+
await log("spawned " + spec.command + " " + spec.args.join(" ") + " pid " + (child.pid || "unknown"));
|
|
381
408
|
}
|
|
382
409
|
|
|
383
410
|
async function run(command, args, options = {}) {
|
|
384
|
-
|
|
411
|
+
const spec = spawnSpec(command, args);
|
|
412
|
+
await log("run " + spec.command + " " + spec.args.join(" "));
|
|
385
413
|
return await new Promise((resolve, reject) => {
|
|
386
|
-
const child = spawn(command, args, {
|
|
414
|
+
const child = spawn(spec.command, spec.args, {
|
|
387
415
|
cwd: input.configDir,
|
|
388
416
|
env: { ...process.env, HAPPY_ELVES_HOME: input.homeEnv },
|
|
389
417
|
windowsHide: true,
|
|
@@ -450,7 +478,48 @@ function isPidAlive(pid) {
|
|
|
450
478
|
}
|
|
451
479
|
|
|
452
480
|
function commandName(name) {
|
|
453
|
-
return
|
|
481
|
+
return runnerPlatform() === "win32" ? name + ".cmd" : name;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function runnerPlatform() {
|
|
485
|
+
return process.env.HAPPY_ELVES_TEST_RUNNER_PLATFORM || process.platform;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function spawnSpec(command, args) {
|
|
489
|
+
if (runnerPlatform() !== "win32" || !/\\.cmd$/i.test(command)) {
|
|
490
|
+
return { command, args };
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
command: process.env.ComSpec || process.env.COMSPEC || "cmd.exe",
|
|
494
|
+
args: ["/d", "/s", "/c", windowsCommandLine([command, ...args])],
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function windowsCommandLine(parts) {
|
|
499
|
+
return parts.map(quoteWindowsArg).join(" ");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function quoteWindowsArg(value) {
|
|
503
|
+
const text = String(value);
|
|
504
|
+
if (text.length === 0) return '""';
|
|
505
|
+
if (!/[\\s"]/u.test(text)) return text;
|
|
506
|
+
let result = '"';
|
|
507
|
+
let backslashes = 0;
|
|
508
|
+
for (const char of text) {
|
|
509
|
+
if (char === "\\\\") {
|
|
510
|
+
backslashes += 1;
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
513
|
+
if (char === '"') {
|
|
514
|
+
result += "\\\\".repeat(backslashes * 2 + 1) + '"';
|
|
515
|
+
backslashes = 0;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
result += "\\\\".repeat(backslashes) + char;
|
|
519
|
+
backslashes = 0;
|
|
520
|
+
}
|
|
521
|
+
result += "\\\\".repeat(backslashes * 2) + '"';
|
|
522
|
+
return result;
|
|
454
523
|
}
|
|
455
524
|
|
|
456
525
|
function compact(text) {
|
package/apps/daemon/package.json
CHANGED