shared-things-server 1.0.5 → 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 +142 -93
- package/package.json +5 -2
- package/scripts/postinstall.js +124 -0
package/dist/cli.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
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";
|
|
7
13
|
import updateNotifier from "update-notifier";
|
|
8
14
|
|
|
9
15
|
// src/db.ts
|
|
10
|
-
import Database from "better-sqlite3";
|
|
11
|
-
import * as path from "path";
|
|
12
|
-
import * as os from "os";
|
|
13
|
-
import * as fs from "fs";
|
|
14
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";
|
|
15
21
|
var DATA_DIR = process.env.DATA_DIR || path.join(os.homedir(), ".shared-things-server");
|
|
16
22
|
var DB_PATH = path.join(DATA_DIR, "data.db");
|
|
17
23
|
function initDatabase() {
|
|
@@ -278,14 +284,6 @@ function resetUserData(db, userId) {
|
|
|
278
284
|
};
|
|
279
285
|
}
|
|
280
286
|
|
|
281
|
-
// src/cli.ts
|
|
282
|
-
import * as fs2 from "fs";
|
|
283
|
-
import * as path2 from "path";
|
|
284
|
-
import * as os2 from "os";
|
|
285
|
-
import { spawn } from "child_process";
|
|
286
|
-
import Fastify from "fastify";
|
|
287
|
-
import cors from "@fastify/cors";
|
|
288
|
-
|
|
289
287
|
// src/auth.ts
|
|
290
288
|
function authMiddleware(db) {
|
|
291
289
|
return (request, reply, done) => {
|
|
@@ -294,7 +292,10 @@ function authMiddleware(db) {
|
|
|
294
292
|
}
|
|
295
293
|
const authHeader = request.headers.authorization;
|
|
296
294
|
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
297
|
-
reply.code(401).send({
|
|
295
|
+
reply.code(401).send({
|
|
296
|
+
error: "Missing or invalid authorization header",
|
|
297
|
+
code: "UNAUTHORIZED"
|
|
298
|
+
});
|
|
298
299
|
return;
|
|
299
300
|
}
|
|
300
301
|
const apiKey = authHeader.slice(7);
|
|
@@ -313,7 +314,7 @@ function registerRoutes(app, db) {
|
|
|
313
314
|
app.get("/health", async () => {
|
|
314
315
|
return { status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
315
316
|
});
|
|
316
|
-
app.get("/state", async (
|
|
317
|
+
app.get("/state", async (_request) => {
|
|
317
318
|
const headings = getAllHeadings(db);
|
|
318
319
|
const todos = getAllTodos(db);
|
|
319
320
|
return {
|
|
@@ -342,59 +343,73 @@ function registerRoutes(app, db) {
|
|
|
342
343
|
syncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
343
344
|
};
|
|
344
345
|
});
|
|
345
|
-
app.post(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
upsertHeading(db, heading.thingsId, heading.title, heading.position, userId);
|
|
355
|
-
}
|
|
356
|
-
for (const serverId of todos.deleted) {
|
|
357
|
-
deleteTodoByServerId(db, serverId, userId);
|
|
358
|
-
}
|
|
359
|
-
for (const todo of todos.upserted) {
|
|
360
|
-
let headingId = null;
|
|
361
|
-
if (todo.headingId) {
|
|
362
|
-
const headingRow = db.prepare(`SELECT id FROM headings WHERE things_id = ?`).get(todo.headingId);
|
|
363
|
-
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);
|
|
364
355
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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;
|
|
384
400
|
}
|
|
385
|
-
|
|
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
|
+
};
|
|
386
411
|
}
|
|
387
|
-
|
|
388
|
-
const currentTodos = getAllTodos(db);
|
|
389
|
-
return {
|
|
390
|
-
state: {
|
|
391
|
-
headings: currentHeadings,
|
|
392
|
-
todos: currentTodos,
|
|
393
|
-
syncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
394
|
-
},
|
|
395
|
-
conflicts
|
|
396
|
-
};
|
|
397
|
-
});
|
|
412
|
+
);
|
|
398
413
|
app.delete("/reset", async (request) => {
|
|
399
414
|
const userId = request.user.id;
|
|
400
415
|
const result = resetUserData(db, userId);
|
|
@@ -409,7 +424,9 @@ function registerRoutes(app, db) {
|
|
|
409
424
|
}
|
|
410
425
|
|
|
411
426
|
// src/cli.ts
|
|
412
|
-
var pkg = JSON.parse(
|
|
427
|
+
var pkg = JSON.parse(
|
|
428
|
+
fs2.readFileSync(new URL("../package.json", import.meta.url), "utf-8")
|
|
429
|
+
);
|
|
413
430
|
var updateCheckInterval = 1e3 * 60 * 60;
|
|
414
431
|
var notifier = updateNotifier({ pkg, updateCheckInterval });
|
|
415
432
|
if (notifier.update) {
|
|
@@ -439,8 +456,10 @@ if (notifier.update && notifier.update.current !== notifier.update.latest) {
|
|
|
439
456
|
process.on("exit", () => {
|
|
440
457
|
if (notifier.update && notifier.update.current !== notifier.update.latest) {
|
|
441
458
|
console.error(
|
|
442
|
-
chalk.yellow(
|
|
443
|
-
|
|
459
|
+
chalk.yellow(
|
|
460
|
+
`
|
|
461
|
+
Update available: ${notifier.update.current} \u2192 ${notifier.update.latest}`
|
|
462
|
+
) + chalk.dim(`
|
|
444
463
|
Run: npm i -g ${pkg.name}
|
|
445
464
|
`)
|
|
446
465
|
);
|
|
@@ -476,9 +495,13 @@ program.command("start").description("Start the sync server").option("-p, --port
|
|
|
476
495
|
if (!isChildProcess) {
|
|
477
496
|
const status = isServerRunning();
|
|
478
497
|
if (status.running) {
|
|
479
|
-
console.log(
|
|
480
|
-
|
|
481
|
-
|
|
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
|
+
);
|
|
482
505
|
return;
|
|
483
506
|
}
|
|
484
507
|
}
|
|
@@ -486,11 +509,15 @@ program.command("start").description("Start the sync server").option("-p, --port
|
|
|
486
509
|
ensureDataDir();
|
|
487
510
|
const logFd = fs2.openSync(LOG_FILE, "a");
|
|
488
511
|
const scriptPath = process.argv[1];
|
|
489
|
-
const child = spawn(
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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
|
+
);
|
|
494
521
|
fs2.writeFileSync(PID_FILE, String(child.pid));
|
|
495
522
|
child.unref();
|
|
496
523
|
fs2.closeSync(logFd);
|
|
@@ -499,13 +526,17 @@ program.command("start").description("Start the sync server").option("-p, --port
|
|
|
499
526
|
console.log(` ${chalk.dim("PID:")} ${child.pid}`);
|
|
500
527
|
console.log(` ${chalk.dim("URL:")} http://${HOST}:${PORT}`);
|
|
501
528
|
console.log(` ${chalk.dim("Logs:")} ${LOG_FILE}`);
|
|
502
|
-
console.log(
|
|
503
|
-
|
|
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
|
+
);
|
|
504
535
|
return;
|
|
505
536
|
}
|
|
506
537
|
const db = initDatabase();
|
|
507
538
|
const app = Fastify({
|
|
508
|
-
logger:
|
|
539
|
+
logger: true
|
|
509
540
|
});
|
|
510
541
|
await app.register(cors, {
|
|
511
542
|
origin: true
|
|
@@ -525,9 +556,11 @@ program.command("start").description("Start the sync server").option("-p, --port
|
|
|
525
556
|
try {
|
|
526
557
|
await app.listen({ port: PORT, host: HOST });
|
|
527
558
|
if (!process.env.SHARED_THINGS_DETACHED) {
|
|
528
|
-
console.log(
|
|
559
|
+
console.log(
|
|
560
|
+
chalk.green(`
|
|
529
561
|
\u2705 Server running at http://${HOST}:${PORT}
|
|
530
|
-
`)
|
|
562
|
+
`)
|
|
563
|
+
);
|
|
531
564
|
}
|
|
532
565
|
} catch (err) {
|
|
533
566
|
app.log.error(err);
|
|
@@ -599,7 +632,9 @@ program.command("logs").description("Show server logs").option("-f, --follow", "
|
|
|
599
632
|
process.exit(0);
|
|
600
633
|
});
|
|
601
634
|
} else {
|
|
602
|
-
const tail = spawn("tail", ["-n", options.lines, LOG_FILE], {
|
|
635
|
+
const tail = spawn("tail", ["-n", options.lines, LOG_FILE], {
|
|
636
|
+
stdio: "inherit"
|
|
637
|
+
});
|
|
603
638
|
tail.on("close", () => process.exit(0));
|
|
604
639
|
}
|
|
605
640
|
});
|
|
@@ -612,7 +647,8 @@ program.command("create-user").description("Create a new user and generate API k
|
|
|
612
647
|
message: "Username",
|
|
613
648
|
validate: (value) => {
|
|
614
649
|
if (!value.trim()) return "Username is required";
|
|
615
|
-
if (userExists(db, value.trim()))
|
|
650
|
+
if (userExists(db, value.trim()))
|
|
651
|
+
return `User "${value.trim()}" already exists`;
|
|
616
652
|
return true;
|
|
617
653
|
}
|
|
618
654
|
});
|
|
@@ -628,14 +664,18 @@ program.command("create-user").description("Create a new user and generate API k
|
|
|
628
664
|
console.log(` ${chalk.dim("ID:")} ${id}`);
|
|
629
665
|
console.log(` ${chalk.dim("Name:")} ${name}`);
|
|
630
666
|
console.log(` ${chalk.dim("API Key:")} ${chalk.cyan(apiKey)}`);
|
|
631
|
-
console.log(
|
|
667
|
+
console.log(
|
|
668
|
+
chalk.yellow("\n\u26A0\uFE0F Save this API key - it cannot be retrieved later!\n")
|
|
669
|
+
);
|
|
632
670
|
});
|
|
633
671
|
program.command("list-users").description("List all users").action(async () => {
|
|
634
672
|
const db = initDatabase();
|
|
635
673
|
const users = listUsers(db);
|
|
636
674
|
if (users.length === 0) {
|
|
637
675
|
console.log(chalk.yellow("\nNo users found.\n"));
|
|
638
|
-
console.log(
|
|
676
|
+
console.log(
|
|
677
|
+
chalk.dim("Create a user with: shared-things-server create-user\n")
|
|
678
|
+
);
|
|
639
679
|
} else {
|
|
640
680
|
console.log(chalk.bold(`
|
|
641
681
|
\u{1F465} Users (${users.length})
|
|
@@ -666,7 +706,8 @@ program.command("delete-user").description("Delete a user").option("-n, --name <
|
|
|
666
706
|
message: "Username to delete",
|
|
667
707
|
validate: (value) => {
|
|
668
708
|
if (!value.trim()) return "Username is required";
|
|
669
|
-
if (!users.find((u) => u.name === value.trim()))
|
|
709
|
+
if (!users.find((u) => u.name === value.trim()))
|
|
710
|
+
return "User not found";
|
|
670
711
|
return true;
|
|
671
712
|
}
|
|
672
713
|
});
|
|
@@ -724,7 +765,7 @@ ${title}
|
|
|
724
765
|
const statusColor = todo.status === "completed" ? chalk.green : todo.status === "canceled" ? chalk.red : chalk.white;
|
|
725
766
|
console.log(` ${statusColor(statusIcon)} ${chalk.white(todo.title)}`);
|
|
726
767
|
if (todo.notes) {
|
|
727
|
-
const shortNotes = todo.notes.length > 50 ? todo.notes.substring(0, 50)
|
|
768
|
+
const shortNotes = todo.notes.length > 50 ? `${todo.notes.substring(0, 50)}...` : todo.notes;
|
|
728
769
|
console.log(` ${chalk.dim("Notes:")} ${shortNotes}`);
|
|
729
770
|
}
|
|
730
771
|
if (todo.dueDate) {
|
|
@@ -733,7 +774,9 @@ ${title}
|
|
|
733
774
|
if (todo.tags && todo.tags.length > 0) {
|
|
734
775
|
console.log(` ${chalk.dim("Tags:")} ${todo.tags.join(", ")}`);
|
|
735
776
|
}
|
|
736
|
-
console.log(
|
|
777
|
+
console.log(
|
|
778
|
+
` ${chalk.dim("Status:")} ${todo.status} ${chalk.dim("|")} ${chalk.dim("By:")} ${userName} ${chalk.dim("|")} ${todo.updatedAt}`
|
|
779
|
+
);
|
|
737
780
|
console.log();
|
|
738
781
|
}
|
|
739
782
|
});
|
|
@@ -760,7 +803,9 @@ program.command("reset").description("Delete all todos and headings (keeps users
|
|
|
760
803
|
db.prepare("DELETE FROM todos").run();
|
|
761
804
|
db.prepare("DELETE FROM headings").run();
|
|
762
805
|
db.prepare("DELETE FROM deleted_items").run();
|
|
763
|
-
console.log(
|
|
806
|
+
console.log(
|
|
807
|
+
chalk.green("\n\u2705 All todos and headings deleted. Users preserved.\n")
|
|
808
|
+
);
|
|
764
809
|
});
|
|
765
810
|
program.command("purge").description("Delete entire database (all data including users)").action(async () => {
|
|
766
811
|
const dataDir = process.env.DATA_DIR || path2.join(os2.homedir(), ".shared-things-server");
|
|
@@ -781,8 +826,12 @@ program.command("purge").description("Delete entire database (all data including
|
|
|
781
826
|
return;
|
|
782
827
|
}
|
|
783
828
|
fs2.unlinkSync(dbPath);
|
|
784
|
-
if (fs2.existsSync(dbPath
|
|
785
|
-
if (fs2.existsSync(dbPath
|
|
786
|
-
console.log(
|
|
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
|
+
);
|
|
787
836
|
});
|
|
788
837
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shared-things-server",
|
|
3
|
-
"version": "1.0
|
|
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": [
|
|
@@ -55,6 +56,8 @@
|
|
|
55
56
|
},
|
|
56
57
|
"scripts": {
|
|
57
58
|
"build": "tsup",
|
|
58
|
-
"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"
|
|
59
62
|
}
|
|
60
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
|
+
}
|