@shetty4l/core 0.1.15 → 0.1.17

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shetty4l/core",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Shared infrastructure primitives for Bun/TypeScript services",
5
5
  "repository": {
6
6
  "type": "git",
package/src/cli.ts CHANGED
@@ -62,7 +62,8 @@ export interface RunCliOpts {
62
62
  * Run the CLI: parse process.argv, dispatch to the matching command handler.
63
63
  *
64
64
  * Handles --help/-h, --version/-v, and unknown commands automatically.
65
- * Calls `process.exit(0)` after successful command execution.
65
+ * If the handler returns a number, exits with that code.
66
+ * If the handler returns void, the process stays alive (for long-running servers).
66
67
  */
67
68
  export async function runCli(opts: RunCliOpts): Promise<void> {
68
69
  const rawArgs = process.argv.slice(2);
@@ -104,6 +105,12 @@ export async function runCli(opts: RunCliOpts): Promise<void> {
104
105
  process.exit(1);
105
106
  }
106
107
 
107
- const exitCode = (await handler(args, json)) ?? 0;
108
- process.exit(exitCode);
108
+ const result = await handler(args, json);
109
+
110
+ // If the handler returned a number, exit with that code.
111
+ // If it returned void/undefined, the command is long-running (e.g. serve)
112
+ // and the process should stay alive.
113
+ if (typeof result === "number") {
114
+ process.exit(result);
115
+ }
109
116
  }
package/src/daemon.ts CHANGED
@@ -25,8 +25,10 @@ export interface DaemonManagerOpts {
25
25
  serveCommand?: string;
26
26
  /** Health endpoint URL for verifying the daemon is responsive. */
27
27
  healthUrl?: string;
28
- /** Milliseconds to wait after spawn before health-checking. Defaults to 500. */
29
- startupWaitMs?: number;
28
+ /** Milliseconds between health poll attempts during startup. Defaults to 500. */
29
+ startupPollMs?: number;
30
+ /** Total milliseconds to wait for health endpoint after spawn. Defaults to 10000. */
31
+ healthTimeoutMs?: number;
30
32
  /** Milliseconds to wait for graceful stop before SIGKILL. Defaults to 5000. */
31
33
  stopTimeoutMs?: number;
32
34
  }
@@ -94,7 +96,8 @@ export function createDaemonManager(opts: DaemonManagerOpts): DaemonManager {
94
96
  cliPath,
95
97
  serveCommand = "serve",
96
98
  healthUrl,
97
- startupWaitMs = 500,
99
+ startupPollMs = 500,
100
+ healthTimeoutMs = 10_000,
98
101
  stopTimeoutMs = 5000,
99
102
  } = opts;
100
103
 
@@ -157,8 +160,43 @@ export function createDaemonManager(opts: DaemonManagerOpts): DaemonManager {
157
160
  });
158
161
 
159
162
  writePid(pidFile, proc.pid);
160
- await new Promise((resolve) => setTimeout(resolve, startupWaitMs));
161
163
 
164
+ if (healthUrl) {
165
+ // Poll health endpoint until responsive or timeout
166
+ let waited = 0;
167
+ while (waited < healthTimeoutMs) {
168
+ await new Promise((resolve) => setTimeout(resolve, startupPollMs));
169
+ waited += startupPollMs;
170
+
171
+ if (!isProcessRunning(proc.pid)) {
172
+ removePidFile(pidFile);
173
+ return err(
174
+ `${name}: process exited during startup (check ${logFile})`,
175
+ );
176
+ }
177
+
178
+ const health = await checkHealth();
179
+ if (health.healthy) {
180
+ return ok({
181
+ running: true,
182
+ pid: proc.pid,
183
+ uptime: health.uptime,
184
+ port: health.port,
185
+ });
186
+ }
187
+ }
188
+
189
+ // Timed out — kill the unresponsive process
190
+ process.kill(proc.pid, "SIGKILL");
191
+ await new Promise((resolve) => setTimeout(resolve, 100));
192
+ removePidFile(pidFile);
193
+ return err(
194
+ `${name}: health check timed out after ${healthTimeoutMs}ms (check ${logFile})`,
195
+ );
196
+ }
197
+
198
+ // No health URL — fall back to PID check after one poll interval
199
+ await new Promise((resolve) => setTimeout(resolve, startupPollMs));
162
200
  const status = await manager.status();
163
201
  if (status.running) {
164
202
  return ok(status);
@@ -195,7 +233,13 @@ export function createDaemonManager(opts: DaemonManagerOpts): DaemonManager {
195
233
  },
196
234
 
197
235
  async restart(): Promise<Result<DaemonStatus>> {
198
- await manager.stop();
236
+ const stopResult = await manager.stop();
237
+ // Propagate stop errors unless the service simply wasn't running
238
+ if (!stopResult.ok && !stopResult.error.includes("not running")) {
239
+ return stopResult;
240
+ }
241
+ // Brief delay to let the OS release the port
242
+ await new Promise((resolve) => setTimeout(resolve, startupPollMs));
199
243
  return manager.start();
200
244
  },
201
245
  };