shared-things-server 1.0.4 → 1.1.0

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/dist/cli.js CHANGED
@@ -1,16 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { Command } from "commander";
5
- import { input, confirm } from "@inquirer/prompts";
4
+ import { spawn } from "child_process";
5
+ import * as fs2 from "fs";
6
+ import * as os2 from "os";
7
+ import * as path2 from "path";
8
+ import cors from "@fastify/cors";
9
+ import { confirm, input } from "@inquirer/prompts";
6
10
  import chalk from "chalk";
11
+ import { Command } from "commander";
12
+ import Fastify from "fastify";
13
+ import updateNotifier from "update-notifier";
7
14
 
8
15
  // src/db.ts
9
- import Database from "better-sqlite3";
10
- import * as path from "path";
11
- import * as os from "os";
12
- import * as fs from "fs";
13
16
  import * as crypto from "crypto";
17
+ import * as fs from "fs";
18
+ import * as os from "os";
19
+ import * as path from "path";
20
+ import Database from "better-sqlite3";
14
21
  var DATA_DIR = process.env.DATA_DIR || path.join(os.homedir(), ".shared-things-server");
15
22
  var DB_PATH = path.join(DATA_DIR, "data.db");
16
23
  function initDatabase() {
@@ -277,14 +284,6 @@ function resetUserData(db, userId) {
277
284
  };
278
285
  }
279
286
 
280
- // src/cli.ts
281
- import * as fs2 from "fs";
282
- import * as path2 from "path";
283
- import * as os2 from "os";
284
- import { spawn } from "child_process";
285
- import Fastify from "fastify";
286
- import cors from "@fastify/cors";
287
-
288
287
  // src/auth.ts
289
288
  function authMiddleware(db) {
290
289
  return (request, reply, done) => {
@@ -293,7 +292,10 @@ function authMiddleware(db) {
293
292
  }
294
293
  const authHeader = request.headers.authorization;
295
294
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
296
- reply.code(401).send({ error: "Missing or invalid authorization header", code: "UNAUTHORIZED" });
295
+ reply.code(401).send({
296
+ error: "Missing or invalid authorization header",
297
+ code: "UNAUTHORIZED"
298
+ });
297
299
  return;
298
300
  }
299
301
  const apiKey = authHeader.slice(7);
@@ -312,7 +314,7 @@ function registerRoutes(app, db) {
312
314
  app.get("/health", async () => {
313
315
  return { status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
314
316
  });
315
- app.get("/state", async (request) => {
317
+ app.get("/state", async (_request) => {
316
318
  const headings = getAllHeadings(db);
317
319
  const todos = getAllTodos(db);
318
320
  return {
@@ -341,59 +343,73 @@ function registerRoutes(app, db) {
341
343
  syncedAt: (/* @__PURE__ */ new Date()).toISOString()
342
344
  };
343
345
  });
344
- app.post("/push", async (request, reply) => {
345
- const { headings, todos } = request.body;
346
- const userId = request.user.id;
347
- const conflicts = [];
348
- try {
349
- for (const thingsId of headings.deleted) {
350
- deleteHeading(db, thingsId, userId);
351
- }
352
- for (const heading of headings.upserted) {
353
- upsertHeading(db, heading.thingsId, heading.title, heading.position, userId);
354
- }
355
- for (const serverId of todos.deleted) {
356
- deleteTodoByServerId(db, serverId, userId);
357
- }
358
- for (const todo of todos.upserted) {
359
- let headingId = null;
360
- if (todo.headingId) {
361
- const headingRow = db.prepare(`SELECT id FROM headings WHERE things_id = ?`).get(todo.headingId);
362
- headingId = headingRow?.id || null;
346
+ app.post(
347
+ "/push",
348
+ async (request, reply) => {
349
+ const { headings, todos } = request.body;
350
+ const userId = request.user.id;
351
+ const conflicts = [];
352
+ try {
353
+ for (const thingsId of headings.deleted) {
354
+ deleteHeading(db, thingsId, userId);
363
355
  }
364
- upsertTodoByServerId(db, todo.serverId, {
365
- thingsId: todo.thingsId,
366
- title: todo.title,
367
- notes: todo.notes,
368
- dueDate: todo.dueDate,
369
- tags: todo.tags,
370
- status: todo.status,
371
- headingId,
372
- position: todo.position
373
- }, userId);
374
- }
375
- } catch (err) {
376
- const error = err;
377
- if (error.message?.includes("UNIQUE constraint failed")) {
378
- reply.status(409);
379
- return {
380
- error: 'Sync conflict: Server has data that conflicts with your local state. Run "shared-things reset --server" to start fresh.',
381
- code: "SYNC_CONFLICT"
382
- };
356
+ for (const heading of headings.upserted) {
357
+ upsertHeading(
358
+ db,
359
+ heading.thingsId,
360
+ heading.title,
361
+ heading.position,
362
+ userId
363
+ );
364
+ }
365
+ for (const serverId of todos.deleted) {
366
+ deleteTodoByServerId(db, serverId, userId);
367
+ }
368
+ for (const todo of todos.upserted) {
369
+ let headingId = null;
370
+ if (todo.headingId) {
371
+ const headingRow = db.prepare(`SELECT id FROM headings WHERE things_id = ?`).get(todo.headingId);
372
+ headingId = headingRow?.id || null;
373
+ }
374
+ upsertTodoByServerId(
375
+ db,
376
+ todo.serverId,
377
+ {
378
+ thingsId: todo.thingsId,
379
+ title: todo.title,
380
+ notes: todo.notes,
381
+ dueDate: todo.dueDate,
382
+ tags: todo.tags,
383
+ status: todo.status,
384
+ headingId,
385
+ position: todo.position
386
+ },
387
+ userId
388
+ );
389
+ }
390
+ } catch (err) {
391
+ const error = err;
392
+ if (error.message?.includes("UNIQUE constraint failed")) {
393
+ reply.status(409);
394
+ return {
395
+ error: 'Sync conflict: Server has data that conflicts with your local state. Run "shared-things reset --server" to start fresh.',
396
+ code: "SYNC_CONFLICT"
397
+ };
398
+ }
399
+ throw err;
383
400
  }
384
- throw err;
401
+ const currentHeadings = getAllHeadings(db);
402
+ const currentTodos = getAllTodos(db);
403
+ return {
404
+ state: {
405
+ headings: currentHeadings,
406
+ todos: currentTodos,
407
+ syncedAt: (/* @__PURE__ */ new Date()).toISOString()
408
+ },
409
+ conflicts
410
+ };
385
411
  }
386
- const currentHeadings = getAllHeadings(db);
387
- const currentTodos = getAllTodos(db);
388
- return {
389
- state: {
390
- headings: currentHeadings,
391
- todos: currentTodos,
392
- syncedAt: (/* @__PURE__ */ new Date()).toISOString()
393
- },
394
- conflicts
395
- };
396
- });
412
+ );
397
413
  app.delete("/reset", async (request) => {
398
414
  const userId = request.user.id;
399
415
  const result = resetUserData(db, userId);
@@ -408,7 +424,47 @@ function registerRoutes(app, db) {
408
424
  }
409
425
 
410
426
  // src/cli.ts
411
- var pkg = JSON.parse(fs2.readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
427
+ var pkg = JSON.parse(
428
+ fs2.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
429
+ );
430
+ var updateCheckInterval = 1e3 * 60 * 60;
431
+ var notifier = updateNotifier({ pkg, updateCheckInterval });
432
+ if (notifier.update) {
433
+ notifier.update.current = pkg.version;
434
+ if (notifier.update.current === notifier.update.latest) {
435
+ notifier.update = void 0;
436
+ }
437
+ }
438
+ var lastCheck = notifier.config?.get("lastUpdateCheck") ?? 0;
439
+ var isFirstRun = Date.now() - lastCheck < 1e3;
440
+ var intervalPassed = Date.now() - lastCheck >= updateCheckInterval;
441
+ if (!notifier.update && (isFirstRun || intervalPassed)) {
442
+ try {
443
+ const update = await notifier.fetchInfo();
444
+ notifier.config?.set("lastUpdateCheck", Date.now());
445
+ if (update && update.type !== "latest") {
446
+ notifier.update = update;
447
+ }
448
+ } catch {
449
+ }
450
+ }
451
+ if (notifier.update && notifier.update.current !== notifier.update.latest) {
452
+ notifier.config?.set("update", notifier.update);
453
+ } else {
454
+ notifier.config?.delete("update");
455
+ }
456
+ process.on("exit", () => {
457
+ if (notifier.update && notifier.update.current !== notifier.update.latest) {
458
+ console.error(
459
+ chalk.yellow(
460
+ `
461
+ Update available: ${notifier.update.current} \u2192 ${notifier.update.latest}`
462
+ ) + chalk.dim(`
463
+ Run: npm i -g ${pkg.name}
464
+ `)
465
+ );
466
+ }
467
+ });
412
468
  var DATA_DIR2 = process.env.DATA_DIR || path2.join(os2.homedir(), ".shared-things-server");
413
469
  var PID_FILE = path2.join(DATA_DIR2, "server.pid");
414
470
  var LOG_FILE = path2.join(DATA_DIR2, "server.log");
@@ -439,9 +495,13 @@ program.command("start").description("Start the sync server").option("-p, --port
439
495
  if (!isChildProcess) {
440
496
  const status = isServerRunning();
441
497
  if (status.running) {
442
- console.log(chalk.yellow(`
443
- \u26A0\uFE0F Server already running (PID: ${status.pid})`));
444
- console.log(chalk.dim('Use "shared-things-server stop" to stop it first.\n'));
498
+ console.log(
499
+ chalk.yellow(`
500
+ \u26A0\uFE0F Server already running (PID: ${status.pid})`)
501
+ );
502
+ console.log(
503
+ chalk.dim('Use "shared-things-server stop" to stop it first.\n')
504
+ );
445
505
  return;
446
506
  }
447
507
  }
@@ -449,11 +509,15 @@ program.command("start").description("Start the sync server").option("-p, --port
449
509
  ensureDataDir();
450
510
  const logFd = fs2.openSync(LOG_FILE, "a");
451
511
  const scriptPath = process.argv[1];
452
- const child = spawn(process.execPath, [scriptPath, "start", "--port", String(PORT), "--host", HOST], {
453
- detached: true,
454
- stdio: ["ignore", logFd, logFd],
455
- env: { ...process.env, SHARED_THINGS_DETACHED: "1" }
456
- });
512
+ const child = spawn(
513
+ process.execPath,
514
+ [scriptPath, "start", "--port", String(PORT), "--host", HOST],
515
+ {
516
+ detached: true,
517
+ stdio: ["ignore", logFd, logFd],
518
+ env: { ...process.env, SHARED_THINGS_DETACHED: "1" }
519
+ }
520
+ );
457
521
  fs2.writeFileSync(PID_FILE, String(child.pid));
458
522
  child.unref();
459
523
  fs2.closeSync(logFd);
@@ -462,13 +526,17 @@ program.command("start").description("Start the sync server").option("-p, --port
462
526
  console.log(` ${chalk.dim("PID:")} ${child.pid}`);
463
527
  console.log(` ${chalk.dim("URL:")} http://${HOST}:${PORT}`);
464
528
  console.log(` ${chalk.dim("Logs:")} ${LOG_FILE}`);
465
- console.log(chalk.dim('\nUse "shared-things-server logs -f" to follow logs'));
466
- console.log(chalk.dim('Use "shared-things-server stop" to stop the server\n'));
529
+ console.log(
530
+ chalk.dim('\nUse "shared-things-server logs -f" to follow logs')
531
+ );
532
+ console.log(
533
+ chalk.dim('Use "shared-things-server stop" to stop the server\n')
534
+ );
467
535
  return;
468
536
  }
469
537
  const db = initDatabase();
470
538
  const app = Fastify({
471
- logger: isChildProcess ? true : true
539
+ logger: true
472
540
  });
473
541
  await app.register(cors, {
474
542
  origin: true
@@ -488,9 +556,11 @@ program.command("start").description("Start the sync server").option("-p, --port
488
556
  try {
489
557
  await app.listen({ port: PORT, host: HOST });
490
558
  if (!process.env.SHARED_THINGS_DETACHED) {
491
- console.log(chalk.green(`
559
+ console.log(
560
+ chalk.green(`
492
561
  \u2705 Server running at http://${HOST}:${PORT}
493
- `));
562
+ `)
563
+ );
494
564
  }
495
565
  } catch (err) {
496
566
  app.log.error(err);
@@ -522,24 +592,29 @@ program.command("stop").description("Stop the background server").action(() => {
522
592
  program.command("status").description("Show server status").action(() => {
523
593
  const status = isServerRunning();
524
594
  console.log(chalk.bold("\n\u{1F4CA} Server Status\n"));
595
+ let versionLine = ` ${chalk.dim("Version:")} ${pkg.version}`;
596
+ if (notifier.update && notifier.update.current !== notifier.update.latest) {
597
+ versionLine += chalk.yellow(` \u2192 ${notifier.update.latest} available`);
598
+ }
599
+ console.log(versionLine);
525
600
  if (status.running) {
526
- console.log(` ${chalk.dim("Status:")} ${chalk.green("\u25CF running")}`);
527
- console.log(` ${chalk.dim("PID:")} ${status.pid}`);
601
+ console.log(` ${chalk.dim("Status:")} ${chalk.green("\u25CF running")}`);
602
+ console.log(` ${chalk.dim("PID:")} ${status.pid}`);
528
603
  } else {
529
- console.log(` ${chalk.dim("Status:")} ${chalk.red("\u25CB stopped")}`);
604
+ console.log(` ${chalk.dim("Status:")} ${chalk.red("\u25CB stopped")}`);
530
605
  }
531
606
  if (fs2.existsSync(LOG_FILE)) {
532
607
  const stats = fs2.statSync(LOG_FILE);
533
608
  const sizeKB = Math.round(stats.size / 1024);
534
- console.log(` ${chalk.dim("Logs:")} ${LOG_FILE} (${sizeKB}KB)`);
609
+ console.log(` ${chalk.dim("Logs:")} ${LOG_FILE} (${sizeKB}KB)`);
535
610
  }
536
611
  const dbPath = path2.join(DATA_DIR2, "data.db");
537
612
  if (fs2.existsSync(dbPath)) {
538
613
  const db = initDatabase();
539
614
  const users = listUsers(db);
540
615
  const todos = getAllTodos(db);
541
- console.log(` ${chalk.dim("Users:")} ${users.length}`);
542
- console.log(` ${chalk.dim("Todos:")} ${todos.length}`);
616
+ console.log(` ${chalk.dim("Users:")} ${users.length}`);
617
+ console.log(` ${chalk.dim("Todos:")} ${todos.length}`);
543
618
  }
544
619
  console.log();
545
620
  });
@@ -557,7 +632,9 @@ program.command("logs").description("Show server logs").option("-f, --follow", "
557
632
  process.exit(0);
558
633
  });
559
634
  } else {
560
- const tail = spawn("tail", ["-n", options.lines, LOG_FILE], { stdio: "inherit" });
635
+ const tail = spawn("tail", ["-n", options.lines, LOG_FILE], {
636
+ stdio: "inherit"
637
+ });
561
638
  tail.on("close", () => process.exit(0));
562
639
  }
563
640
  });
@@ -570,7 +647,8 @@ program.command("create-user").description("Create a new user and generate API k
570
647
  message: "Username",
571
648
  validate: (value) => {
572
649
  if (!value.trim()) return "Username is required";
573
- if (userExists(db, value.trim())) return `User "${value.trim()}" already exists`;
650
+ if (userExists(db, value.trim()))
651
+ return `User "${value.trim()}" already exists`;
574
652
  return true;
575
653
  }
576
654
  });
@@ -586,14 +664,18 @@ program.command("create-user").description("Create a new user and generate API k
586
664
  console.log(` ${chalk.dim("ID:")} ${id}`);
587
665
  console.log(` ${chalk.dim("Name:")} ${name}`);
588
666
  console.log(` ${chalk.dim("API Key:")} ${chalk.cyan(apiKey)}`);
589
- console.log(chalk.yellow("\n\u26A0\uFE0F Save this API key - it cannot be retrieved later!\n"));
667
+ console.log(
668
+ chalk.yellow("\n\u26A0\uFE0F Save this API key - it cannot be retrieved later!\n")
669
+ );
590
670
  });
591
671
  program.command("list-users").description("List all users").action(async () => {
592
672
  const db = initDatabase();
593
673
  const users = listUsers(db);
594
674
  if (users.length === 0) {
595
675
  console.log(chalk.yellow("\nNo users found.\n"));
596
- console.log(chalk.dim("Create a user with: shared-things-server create-user\n"));
676
+ console.log(
677
+ chalk.dim("Create a user with: shared-things-server create-user\n")
678
+ );
597
679
  } else {
598
680
  console.log(chalk.bold(`
599
681
  \u{1F465} Users (${users.length})
@@ -624,7 +706,8 @@ program.command("delete-user").description("Delete a user").option("-n, --name <
624
706
  message: "Username to delete",
625
707
  validate: (value) => {
626
708
  if (!value.trim()) return "Username is required";
627
- if (!users.find((u) => u.name === value.trim())) return "User not found";
709
+ if (!users.find((u) => u.name === value.trim()))
710
+ return "User not found";
628
711
  return true;
629
712
  }
630
713
  });
@@ -682,7 +765,7 @@ ${title}
682
765
  const statusColor = todo.status === "completed" ? chalk.green : todo.status === "canceled" ? chalk.red : chalk.white;
683
766
  console.log(` ${statusColor(statusIcon)} ${chalk.white(todo.title)}`);
684
767
  if (todo.notes) {
685
- const shortNotes = todo.notes.length > 50 ? todo.notes.substring(0, 50) + "..." : todo.notes;
768
+ const shortNotes = todo.notes.length > 50 ? `${todo.notes.substring(0, 50)}...` : todo.notes;
686
769
  console.log(` ${chalk.dim("Notes:")} ${shortNotes}`);
687
770
  }
688
771
  if (todo.dueDate) {
@@ -691,7 +774,9 @@ ${title}
691
774
  if (todo.tags && todo.tags.length > 0) {
692
775
  console.log(` ${chalk.dim("Tags:")} ${todo.tags.join(", ")}`);
693
776
  }
694
- console.log(` ${chalk.dim("Status:")} ${todo.status} ${chalk.dim("|")} ${chalk.dim("By:")} ${userName} ${chalk.dim("|")} ${todo.updatedAt}`);
777
+ console.log(
778
+ ` ${chalk.dim("Status:")} ${todo.status} ${chalk.dim("|")} ${chalk.dim("By:")} ${userName} ${chalk.dim("|")} ${todo.updatedAt}`
779
+ );
695
780
  console.log();
696
781
  }
697
782
  });
@@ -718,7 +803,9 @@ program.command("reset").description("Delete all todos and headings (keeps users
718
803
  db.prepare("DELETE FROM todos").run();
719
804
  db.prepare("DELETE FROM headings").run();
720
805
  db.prepare("DELETE FROM deleted_items").run();
721
- console.log(chalk.green("\n\u2705 All todos and headings deleted. Users preserved.\n"));
806
+ console.log(
807
+ chalk.green("\n\u2705 All todos and headings deleted. Users preserved.\n")
808
+ );
722
809
  });
723
810
  program.command("purge").description("Delete entire database (all data including users)").action(async () => {
724
811
  const dataDir = process.env.DATA_DIR || path2.join(os2.homedir(), ".shared-things-server");
@@ -739,8 +826,12 @@ program.command("purge").description("Delete entire database (all data including
739
826
  return;
740
827
  }
741
828
  fs2.unlinkSync(dbPath);
742
- if (fs2.existsSync(dbPath + "-wal")) fs2.unlinkSync(dbPath + "-wal");
743
- if (fs2.existsSync(dbPath + "-shm")) fs2.unlinkSync(dbPath + "-shm");
744
- console.log(chalk.green('\n\u2705 Database deleted. Run "shared-things-server create-user" to start fresh.\n'));
829
+ if (fs2.existsSync(`${dbPath}-wal`)) fs2.unlinkSync(`${dbPath}-wal`);
830
+ if (fs2.existsSync(`${dbPath}-shm`)) fs2.unlinkSync(`${dbPath}-shm`);
831
+ console.log(
832
+ chalk.green(
833
+ '\n\u2705 Database deleted. Run "shared-things-server create-user" to start fresh.\n'
834
+ )
835
+ );
745
836
  });
746
837
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shared-things-server",
3
- "version": "1.0.4",
3
+ "version": "1.1.0",
4
4
  "description": "Sync server for Things 3 projects",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
+ "scripts",
12
13
  "README.md"
13
14
  ],
14
15
  "keywords": [
@@ -42,7 +43,8 @@
42
43
  "better-sqlite3": "^11.0.0",
43
44
  "chalk": "^5.6.2",
44
45
  "commander": "^12.0.0",
45
- "fastify": "^4.26.0"
46
+ "fastify": "^4.26.0",
47
+ "update-notifier": "^7.0.0"
46
48
  },
47
49
  "devDependencies": {
48
50
  "@types/better-sqlite3": "^7.6.8",
@@ -54,6 +56,8 @@
54
56
  },
55
57
  "scripts": {
56
58
  "build": "tsup",
57
- "dev": "tsx watch src/cli.ts start"
59
+ "dev": "tsx watch src/cli.ts start",
60
+ "typecheck": "tsc --noEmit",
61
+ "postinstall": "node scripts/postinstall.js || true"
58
62
  }
59
63
  }
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Postinstall script: Automatically restart the server if it's running.
5
+ * This ensures users get the new version after `npm update -g shared-things-server`.
6
+ *
7
+ * Safe behaviors:
8
+ * - Silent success if server is not running
9
+ * - Never fails npm install (catches all errors)
10
+ * - Works on any platform (Linux, macOS)
11
+ */
12
+
13
+ import { execSync } from "node:child_process";
14
+ import * as fs from "node:fs";
15
+ import * as os from "node:os";
16
+ import * as path from "node:path";
17
+
18
+ const DATA_DIR =
19
+ process.env.DATA_DIR || path.join(os.homedir(), ".shared-things-server");
20
+ const PID_FILE = path.join(DATA_DIR, "server.pid");
21
+
22
+ function isServerRunning() {
23
+ if (!fs.existsSync(PID_FILE)) {
24
+ return { running: false };
25
+ }
26
+
27
+ const pid = parseInt(fs.readFileSync(PID_FILE, "utf-8").trim(), 10);
28
+
29
+ try {
30
+ // Check if process exists (signal 0 doesn't kill, just checks)
31
+ process.kill(pid, 0);
32
+ return { running: true, pid };
33
+ } catch {
34
+ // Process doesn't exist, clean up stale PID file
35
+ try {
36
+ fs.unlinkSync(PID_FILE);
37
+ } catch {
38
+ // Ignore cleanup errors
39
+ }
40
+ return { running: false };
41
+ }
42
+ }
43
+
44
+ function stopServer(pid) {
45
+ try {
46
+ process.kill(pid, "SIGTERM");
47
+ // Wait for graceful shutdown
48
+ let attempts = 0;
49
+ while (attempts < 10) {
50
+ try {
51
+ process.kill(pid, 0);
52
+ // Still running, wait
53
+ execSync("sleep 0.2", { stdio: "pipe" });
54
+ attempts++;
55
+ } catch {
56
+ // Process stopped
57
+ break;
58
+ }
59
+ }
60
+ // Clean up PID file if still exists
61
+ if (fs.existsSync(PID_FILE)) {
62
+ fs.unlinkSync(PID_FILE);
63
+ }
64
+ return true;
65
+ } catch {
66
+ return false;
67
+ }
68
+ }
69
+
70
+ function startServer() {
71
+ // Verify the command exists
72
+ try {
73
+ execSync("which shared-things-server", { stdio: "pipe" });
74
+ } catch {
75
+ // Command not in PATH, can't start
76
+ return false;
77
+ }
78
+
79
+ // Use the CLI's built-in detach mode (-d flag)
80
+ // This properly handles daemonization and PID file management
81
+ try {
82
+ execSync("shared-things-server start -d", {
83
+ stdio: "pipe",
84
+ timeout: 10000,
85
+ });
86
+ return true;
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ function main() {
93
+ const status = isServerRunning();
94
+
95
+ if (!status.running) {
96
+ // Server not running, nothing to restart
97
+ return;
98
+ }
99
+
100
+ // Stop the old server
101
+ const stopped = stopServer(status.pid);
102
+ if (!stopped) {
103
+ console.log(
104
+ ' ℹ️ Could not auto-restart server. Run "shared-things-server stop && shared-things-server start -d" manually.',
105
+ );
106
+ return;
107
+ }
108
+
109
+ // Start with new version
110
+ const started = startServer();
111
+ if (started) {
112
+ console.log(" ✅ Server restarted with new version");
113
+ } else {
114
+ console.log(
115
+ ' ℹ️ Server stopped. Run "shared-things-server start -d" to start it again.',
116
+ );
117
+ }
118
+ }
119
+
120
+ try {
121
+ main();
122
+ } catch {
123
+ // Never fail npm install
124
+ }