@shetty4l/core 0.1.29 → 0.1.31

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.29",
3
+ "version": "0.1.31",
4
4
  "description": "Shared infrastructure primitives for Bun/TypeScript services",
5
5
  "repository": {
6
6
  "type": "git",
@@ -46,8 +46,15 @@ check_prereqs() {
46
46
 
47
47
  fetch_latest_release() {
48
48
  info "Fetching latest release from GitHub..."
49
+
50
+ local auth_header=()
51
+ if [ -n "${GITHUB_TOKEN:-}" ]; then
52
+ auth_header=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
53
+ info "Using authenticated GitHub API request"
54
+ fi
55
+
49
56
  local release_json
50
- release_json=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest")
57
+ release_json=$(curl -fsSL "${auth_header[@]}" "https://api.github.com/repos/${REPO}/releases/latest")
51
58
 
52
59
  RELEASE_TAG=$(echo "$release_json" | jq -r '.tag_name')
53
60
  TARBALL_URL=$(echo "$release_json" | jq -r ".assets[] | select(.name | startswith(\"${SERVICE_NAME}-\")) | .browser_download_url")
@@ -74,10 +81,15 @@ download_and_extract() {
74
81
 
75
82
  mkdir -p "$version_dir"
76
83
 
84
+ local auth_header=()
85
+ if [ -n "${GITHUB_TOKEN:-}" ]; then
86
+ auth_header=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
87
+ fi
88
+
77
89
  info "Downloading ${RELEASE_TAG}..."
78
90
  local tmpfile
79
91
  tmpfile=$(mktemp)
80
- curl -fsSL -o "$tmpfile" "$TARBALL_URL"
92
+ curl -fsSL "${auth_header[@]}" -o "$tmpfile" "$TARBALL_URL"
81
93
 
82
94
  info "Extracting to ${version_dir}..."
83
95
  tar xzf "$tmpfile" -C "$version_dir"
package/src/cli.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  * Each service defines its own commands and help text.
6
6
  */
7
7
 
8
+ import type { DaemonManager } from "./daemon";
9
+
8
10
  // --- Arg parsing ---
9
11
 
10
12
  export interface ParsedArgs {
@@ -184,3 +186,158 @@ export async function runCli(opts: RunCliOpts): Promise<void> {
184
186
  process.exit(result);
185
187
  }
186
188
  }
189
+
190
+ // --- Daemon command factories ---
191
+
192
+ export interface DaemonCommandsOpts {
193
+ /** Service name (e.g. "synapse"). Used in output messages. */
194
+ name: string;
195
+ /** Factory that returns a DaemonManager. Called lazily per command. */
196
+ getDaemon: () => DaemonManager;
197
+ }
198
+
199
+ /**
200
+ * Create standard daemon lifecycle commands: start, stop, status, restart.
201
+ *
202
+ * Returns four CommandHandler functions ready to register with runCli.
203
+ * Output format is standardized across all services.
204
+ */
205
+ export function createDaemonCommands(opts: DaemonCommandsOpts): {
206
+ start: CommandHandler;
207
+ stop: CommandHandler;
208
+ status: CommandHandler;
209
+ restart: CommandHandler;
210
+ } {
211
+ const { name, getDaemon } = opts;
212
+
213
+ const start: CommandHandler = async () => {
214
+ const result = await getDaemon().start();
215
+ if (!result.ok) {
216
+ console.error(result.error);
217
+ return 1;
218
+ }
219
+ const { pid, port } = result.value;
220
+ const portStr = port != null ? `, port: ${port}` : "";
221
+ console.log(`${name} daemon started (PID: ${pid}${portStr})`);
222
+ return 0;
223
+ };
224
+
225
+ const stop: CommandHandler = async () => {
226
+ const result = await getDaemon().stop();
227
+ if (!result.ok) {
228
+ if (result.error.includes("not running")) {
229
+ console.log(`${name} daemon is not running`);
230
+ } else {
231
+ console.error(result.error);
232
+ }
233
+ return 1;
234
+ }
235
+ console.log(`${name} daemon stopped (was PID: ${result.value.pid})`);
236
+ return 0;
237
+ };
238
+
239
+ const status: CommandHandler = async (_args, json) => {
240
+ const s = await getDaemon().status();
241
+
242
+ if (json) {
243
+ console.log(JSON.stringify(s, null, 2));
244
+ return s.running ? 0 : 1;
245
+ }
246
+
247
+ if (!s.running) {
248
+ console.log(`${name} is not running`);
249
+ return 1;
250
+ }
251
+
252
+ const uptimeStr = s.uptime != null ? formatUptime(s.uptime) : "unknown";
253
+ const portStr = s.port != null ? `, port: ${s.port}` : "";
254
+ console.log(
255
+ `${name} is running (PID: ${s.pid}${portStr}, uptime: ${uptimeStr})`,
256
+ );
257
+ return 0;
258
+ };
259
+
260
+ const restart: CommandHandler = async () => {
261
+ const result = await getDaemon().restart();
262
+ if (!result.ok) {
263
+ console.error(result.error);
264
+ return 1;
265
+ }
266
+ const { pid, port } = result.value;
267
+ const portStr = port != null ? `, port: ${port}` : "";
268
+ console.log(`${name} daemon restarted (PID: ${pid}${portStr})`);
269
+ return 0;
270
+ };
271
+
272
+ return { start, stop, status, restart };
273
+ }
274
+
275
+ // --- Health command factory ---
276
+
277
+ export interface HealthCommandOpts {
278
+ /** Service name (e.g. "synapse"). Used in output messages. */
279
+ name: string;
280
+ /** Returns the health endpoint URL. Called lazily per invocation. */
281
+ getHealthUrl: () => string;
282
+ /**
283
+ * Optional callback to print service-specific health data after the
284
+ * standard Status/Version/Uptime fields (e.g. provider health table).
285
+ */
286
+ formatExtra?: (data: Record<string, unknown>) => void;
287
+ }
288
+
289
+ /**
290
+ * Create a standard `health` CLI command that fetches /health and displays
291
+ * the result. Supports --json for machine-readable output.
292
+ */
293
+ export function createHealthCommand(opts: HealthCommandOpts): CommandHandler {
294
+ const { name, getHealthUrl, formatExtra } = opts;
295
+
296
+ return async (_args, json) => {
297
+ const healthUrl = getHealthUrl();
298
+ const port = safeParsePort(healthUrl);
299
+
300
+ let response: Response;
301
+ try {
302
+ response = await fetch(healthUrl);
303
+ } catch {
304
+ if (json) {
305
+ console.log(JSON.stringify({ error: "Server not reachable", port }));
306
+ } else {
307
+ console.error(`${name} is not running on port ${port}`);
308
+ }
309
+ return 1;
310
+ }
311
+
312
+ const data = (await response.json()) as Record<string, unknown>;
313
+
314
+ if (json) {
315
+ console.log(JSON.stringify(data, null, 2));
316
+ return data.status === "healthy" ? 0 : 1;
317
+ }
318
+
319
+ console.log(
320
+ `\nStatus: ${data.status === "healthy" ? "healthy" : "degraded"}`,
321
+ );
322
+ console.log(`Version: ${data.version}`);
323
+ if (typeof data.uptime === "number") {
324
+ console.log(`Uptime: ${formatUptime(data.uptime)}`);
325
+ }
326
+
327
+ if (formatExtra) {
328
+ formatExtra(data);
329
+ }
330
+
331
+ console.log();
332
+ return data.status === "healthy" ? 0 : 1;
333
+ };
334
+ }
335
+
336
+ /** Extract port from a URL string, returning "unknown" on failure. */
337
+ function safeParsePort(url: string): string {
338
+ try {
339
+ return new URL(url).port || "unknown";
340
+ } catch {
341
+ return "unknown";
342
+ }
343
+ }