@exaudeus/workrail 3.72.4 → 3.73.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.
@@ -21,6 +21,7 @@ export interface WorktrainDaemonCommandDeps {
21
21
  }>;
22
22
  readonly print: (line: string) => void;
23
23
  readonly sleep: (ms: number) => Promise<void>;
24
+ readonly httpGet: (url: string) => Promise<number | null>;
24
25
  readonly startDaemon?: () => Promise<void>;
25
26
  }
26
27
  export interface WorktrainDaemonCommandOpts {
@@ -30,5 +31,11 @@ export interface WorktrainDaemonCommandOpts {
30
31
  readonly start?: boolean;
31
32
  readonly stop?: boolean;
32
33
  }
34
+ export declare function pollHealthEndpoint(url: string, deps: Pick<WorktrainDaemonCommandDeps, 'httpGet' | 'sleep'>, maxAttempts: number, intervalMs: number): Promise<{
35
+ readonly ok: true;
36
+ } | {
37
+ readonly ok: false;
38
+ readonly reason: string;
39
+ }>;
33
40
  export declare function parseDotEnv(content: string): Record<string, string>;
34
41
  export declare function executeWorktrainDaemonCommand(deps: WorktrainDaemonCommandDeps, opts: WorktrainDaemonCommandOpts): Promise<CliResult>;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pollHealthEndpoint = pollHealthEndpoint;
3
4
  exports.parseDotEnv = parseDotEnv;
4
5
  exports.executeWorktrainDaemonCommand = executeWorktrainDaemonCommand;
5
6
  const cli_result_js_1 = require("../types/cli-result.js");
@@ -12,6 +13,7 @@ const CAPTURED_ENV_VARS = [
12
13
  'HOME',
13
14
  'USER',
14
15
  'PATH',
16
+ 'WORKRAIL_TRIGGER_PORT',
15
17
  'WORKRAIL_DEV',
16
18
  'WORKRAIL_LOG_LEVEL',
17
19
  'WORKRAIL_VERBOSE_LOGGING',
@@ -106,6 +108,19 @@ function parseLaunchctlList(stdout, exitCode) {
106
108
  return { running: false, pid: null, loaded: false };
107
109
  }
108
110
  }
111
+ async function pollHealthEndpoint(url, deps, maxAttempts, intervalMs) {
112
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
113
+ await deps.sleep(intervalMs);
114
+ const status = await deps.httpGet(url);
115
+ if (status === 200) {
116
+ return { ok: true };
117
+ }
118
+ }
119
+ return {
120
+ ok: false,
121
+ reason: `Health endpoint did not respond after ${maxAttempts} attempts (${(maxAttempts * intervalMs) / 1000}s).`,
122
+ };
123
+ }
109
124
  async function runInstall(deps) {
110
125
  const home = deps.homedir();
111
126
  const plistDir = deps.joinPath(home, 'Library', 'LaunchAgents');
@@ -285,15 +300,22 @@ async function runStart(deps) {
285
300
  if (result.exitCode !== 0) {
286
301
  return (0, cli_result_js_1.failure)(`launchctl start failed (exit ${result.exitCode}): ${result.stderr.trim() || result.stdout.trim()}`, { suggestions: [`View logs: tail -f ${logDir}/daemon.stderr.log`] });
287
302
  }
288
- await deps.sleep(1000);
289
- const listResult = await deps.exec('launchctl', ['list', LAUNCHD_LABEL]);
290
- const status = parseLaunchctlList(listResult.stdout, listResult.exitCode);
291
- if (status.running) {
292
- deps.print(`WorkTrain daemon started (PID ${status.pid}).`);
303
+ const port = deps.env['WORKRAIL_TRIGGER_PORT']
304
+ ? parseInt(deps.env['WORKRAIL_TRIGGER_PORT'], 10)
305
+ : 3200;
306
+ const healthUrl = `http://127.0.0.1:${port}/health`;
307
+ const poll = await pollHealthEndpoint(healthUrl, deps, 10, 500);
308
+ if (poll.ok) {
309
+ deps.print('Daemon started successfully.');
293
310
  deps.print(`Logs: ${logDir}/daemon.stdout.log`);
294
- return (0, cli_result_js_1.success)({ message: `WorkTrain daemon started (PID ${status.pid})` });
311
+ return (0, cli_result_js_1.success)({ message: 'Daemon started successfully' });
295
312
  }
296
- return (0, cli_result_js_1.failure)('launchctl start returned 0 but daemon does not appear to be running.', { suggestions: [`View logs: tail -f ${logDir}/daemon.stderr.log`] });
313
+ return (0, cli_result_js_1.failure)(`Daemon did not respond to health check at ${healthUrl} within 5 seconds.`, {
314
+ suggestions: [
315
+ `View logs: tail -f ${logDir}/daemon.stderr.log`,
316
+ `Check daemon status: worktrain daemon --status`,
317
+ ],
318
+ });
297
319
  }
298
320
  async function runStop(deps) {
299
321
  const result = await deps.exec('launchctl', ['stop', LAUNCHD_LABEL]);
@@ -287,6 +287,17 @@ program
287
287
  },
288
288
  print: (line) => console.log(line),
289
289
  sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
290
+ httpGet: async (url) => {
291
+ const { get } = await Promise.resolve().then(() => __importStar(require('http')));
292
+ return new Promise((resolve) => {
293
+ const req = get(url, { timeout: 1000 }, (res) => {
294
+ res.resume();
295
+ resolve(res.statusCode ?? null);
296
+ });
297
+ req.on('error', () => resolve(null));
298
+ req.on('timeout', () => { req.destroy(); resolve(null); });
299
+ });
300
+ },
290
301
  startDaemon: async () => {
291
302
  await (0, daemon_env_js_1.loadDaemonEnv)();
292
303
  const { startTriggerListener } = await Promise.resolve().then(() => __importStar(require('./trigger/trigger-listener.js')));