@radleta/just-one 1.2.0 → 1.3.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/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [1.3.0](https://github.com/radleta/just-one/compare/v1.2.0...v1.3.0) (2026-02-12)
6
+
7
+ ### Features
8
+
9
+ - **cli:** add daemon mode with log capture, viewing, and real-time follow ([4b6b7ae](https://github.com/radleta/just-one/commit/4b6b7ae84117c895782d272aa0ae62f107fb2e13))
10
+ - **process:** add graceful kill with SIGKILL escalation and --grace flag ([84f90d9](https://github.com/radleta/just-one/commit/84f90d90652f376bcac9ae781967578a657c3dd4))
11
+
12
+ ### Bug Fixes
13
+
14
+ - **ci:** enforce LF line endings to fix Windows format check ([e208149](https://github.com/radleta/just-one/commit/e2081493b21663011ef9431944bd25eb22d121aa))
15
+ - **log:** replace fs.watchFile with setInterval polling in tailLogFile ([26200fc](https://github.com/radleta/just-one/commit/26200fcb47632800fecb508b85696ad3ceffadea))
16
+ - **process:** pass args array to spawn on Windows instead of joining ([5e3d13a](https://github.com/radleta/just-one/commit/5e3d13ace04175dfb92404bd06aac2e1dadcf04d))
17
+ - **process:** remove shell: true from daemon mode on Windows ([2411f5f](https://github.com/radleta/just-one/commit/2411f5f8a7ee43aac00d784a360d996d02fb715d))
18
+ - **tests:** increase tailLogFile poll timeout for slow CI runners ([bde9a92](https://github.com/radleta/just-one/commit/bde9a92839f986e0270ca9fb5f81ea98d9228aef))
19
+ - **tests:** stabilize flaky polling-based tests ([02d4639](https://github.com/radleta/just-one/commit/02d463928b3e4f59829babd24bd74549c6fc9a68))
20
+ - **tests:** stabilize Windows E2E tests for daemon mode ([92dee91](https://github.com/radleta/just-one/commit/92dee9100d0eae133d5db045e0c65425fb73669f))
21
+ - **tests:** use script files instead of node -e in daemon tests ([bc47ab5](https://github.com/radleta/just-one/commit/bc47ab5ab98076813bedb58dd3a7d869d3e738e7))
22
+
5
23
  ## [1.2.0](https://github.com/radleta/just-one/compare/v1.1.0...v1.2.0) (2026-02-11)
6
24
 
7
25
  ### Features
package/README.md CHANGED
@@ -22,6 +22,9 @@ Existing solutions have drawbacks:
22
22
 
23
23
  - **Named process tracking** - Each process gets a unique name for precise targeting
24
24
  - **Automatic cleanup** - Previous instance killed before starting new one
25
+ - **Daemon mode** - Run processes in the background with log file capture
26
+ - **Log viewing** - View captured logs, follow in real-time (like `tail -f`)
27
+ - **Log rotation** - Automatic rotation at 10MB (keeps 1 backup)
25
28
  - **Cross-platform** - Works on Windows, macOS, and Linux
26
29
  - **Minimal dependencies** - Only [pidusage](https://github.com/soyuka/pidusage) for process verification
27
30
  - **PID file management** - Survives terminal closes and system restarts
@@ -61,6 +64,31 @@ just-one -n myapp -- node server.js
61
64
  just-one -n vite -e -- npm run dev
62
65
  ```
63
66
 
67
+ ### Daemon mode (background with log capture)
68
+
69
+ ```bash
70
+ # Run in background — parent exits immediately, output captured to log file
71
+ just-one -n myapp -D -- npm start
72
+
73
+ # Logs are written to .just-one/myapp.log
74
+ ```
75
+
76
+ ### Viewing logs
77
+
78
+ ```bash
79
+ # View all captured logs
80
+ just-one -L myapp
81
+
82
+ # View last 50 lines
83
+ just-one -L myapp --lines 50
84
+
85
+ # Follow logs in real-time (like tail -f) — auto-exits when process dies
86
+ just-one -L myapp -f
87
+
88
+ # Show last 20 lines then follow
89
+ just-one -L myapp -f --lines 20
90
+ ```
91
+
64
92
  ### Check if a process is running
65
93
 
66
94
  ```bash
@@ -102,10 +130,10 @@ just-one -l
102
130
  just-one --list
103
131
  ```
104
132
 
105
- ### Clean up stale PID files
133
+ ### Clean up stale PID files and logs
106
134
 
107
135
  ```bash
108
- just-one --clean # removes PID files for processes that are no longer running
136
+ just-one --clean # removes stale PID files and their associated log files
109
137
  ```
110
138
 
111
139
  ### Specify custom PID directory
@@ -120,22 +148,27 @@ just-one -n storybook -d /tmp -- npx storybook dev
120
148
 
121
149
  ## CLI Options
122
150
 
123
- | Option | Alias | Description |
124
- | ------------------ | ----- | ------------------------------------------------- |
125
- | `--name <name>` | `-n` | Required for run. Name to identify this process |
126
- | `--kill <name>` | `-k` | Kill the named process and exit |
127
- | `--kill-all` | `-K` | Kill all tracked processes |
128
- | `--status <name>` | `-s` | Check if a named process is running (exit 0/1) |
129
- | `--ensure` | `-e` | Only start if not already running (use with `-n`) |
130
- | `--pid <name>` | `-p` | Print the PID of a named process |
131
- | `--wait <name>` | `-w` | Wait for a named process to exit |
132
- | `--timeout <secs>` | `-t` | Timeout in seconds (use with `--wait`) |
133
- | `--clean` | | Remove stale PID files |
134
- | `--list` | `-l` | List all tracked processes and their status |
135
- | `--pid-dir <dir>` | `-d` | Directory for PID files (default: `.just-one/`) |
136
- | `--quiet` | `-q` | Suppress output |
137
- | `--help` | `-h` | Show help |
138
- | `--version` | `-v` | Show version |
151
+ | Option | Alias | Description |
152
+ | ------------------ | ----- | -------------------------------------------------- |
153
+ | `--name <name>` | `-n` | Required for run. Name to identify this process |
154
+ | `--daemon` | `-D` | Run in background with output captured to log file |
155
+ | `--logs <name>` | `-L` | View captured logs for a named process |
156
+ | `--tail` | `-f` | Follow log output in real-time (use with `--logs`) |
157
+ | `--lines <n>` | | Number of lines to show (use with `--logs`) |
158
+ | `--kill <name>` | `-k` | Kill the named process and exit |
159
+ | `--kill-all` | `-K` | Kill all tracked processes |
160
+ | `--status <name>` | `-s` | Check if a named process is running (exit 0/1) |
161
+ | `--ensure` | `-e` | Only start if not already running (use with `-n`) |
162
+ | `--pid <name>` | `-p` | Print the PID of a named process |
163
+ | `--wait <name>` | `-w` | Wait for a named process to exit |
164
+ | `--timeout <secs>` | `-t` | Timeout in seconds (use with `--wait`) |
165
+ | `--grace <secs>` | `-g` | Grace period before force kill (default: 5s) |
166
+ | `--clean` | | Remove stale PID files and orphaned log files |
167
+ | `--list` | `-l` | List all tracked processes and their status |
168
+ | `--pid-dir <dir>` | `-d` | Directory for PID files (default: `.just-one/`) |
169
+ | `--quiet` | `-q` | Suppress output |
170
+ | `--help` | `-h` | Show help |
171
+ | `--version` | `-v` | Show version |
139
172
 
140
173
  ## package.json Scripts
141
174
 
@@ -144,7 +177,8 @@ just-one -n storybook -d /tmp -- npx storybook dev
144
177
  "scripts": {
145
178
  "storybook": "just-one -n storybook -- storybook dev -p 6006",
146
179
  "dev": "just-one -n vite -e -- vite",
147
- "dev:api": "just-one -n api -e -- node server.js",
180
+ "dev:api": "just-one -n api -D -- node server.js",
181
+ "dev:logs": "just-one -L api -f",
148
182
  "stop": "just-one -K"
149
183
  }
150
184
  }
@@ -155,14 +189,17 @@ just-one -n storybook -d /tmp -- npx storybook dev
155
189
  ```
156
190
  .just-one/
157
191
  storybook.pid # Contains: 12345
192
+ storybook.log # Captured output (daemon mode only)
193
+ storybook.log.1 # Rotated backup (auto-managed)
158
194
  vite.pid # Contains: 67890
159
195
  ```
160
196
 
161
197
  1. Check if a PID file exists for that name
162
198
  2. If yes, verify it's the same process we started (by comparing start times)
163
- 3. If verified, kill that specific process (and its children)
164
- 4. Start the new process
165
- 5. Save its PID for next time
199
+ 3. If verified, send SIGTERM and wait up to 5 seconds (configurable with `--grace`)
200
+ 4. If still alive, escalate to SIGKILL (force kill)
201
+ 5. Start the new process
202
+ 6. Save its PID for next time
166
203
 
167
204
  ### PID Reuse Protection
168
205
 
@@ -206,11 +243,12 @@ just-one -n storybook-docs -- storybook dev -p 6007
206
243
  | Zero config | Yes | Yes | No |
207
244
  | Remembers processes | Yes (PID file) | No | Yes (daemon) |
208
245
  | Lightweight | Yes (1 dep) | Yes | Heavy |
209
- | Daemon required | No | No | Yes |
246
+ | Daemon mode | Optional | No | Yes |
247
+ | Log capture | Yes | No | Yes |
210
248
 
211
249
  ## Requirements
212
250
 
213
- - Node.js >= 18.0.0
251
+ - Node.js >= 20.0.0
214
252
 
215
253
  ## Development
216
254
 
package/dist/index.js CHANGED
@@ -39,6 +39,11 @@ function parseArgs(args) {
39
39
  pid: void 0,
40
40
  wait: void 0,
41
41
  timeout: void 0,
42
+ grace: void 0,
43
+ daemon: false,
44
+ logs: void 0,
45
+ tail: false,
46
+ lines: void 0,
42
47
  pidDir: DEFAULT_PID_DIR,
43
48
  quiet: false,
44
49
  help: false,
@@ -177,6 +182,44 @@ function parseArgs(args) {
177
182
  i += 2;
178
183
  continue;
179
184
  }
185
+ if (arg === "--daemon" || arg === "-D") {
186
+ options.daemon = true;
187
+ i++;
188
+ continue;
189
+ }
190
+ if (arg === "--logs" || arg === "-L") {
191
+ const value = args[i + 1];
192
+ if (!value || value.startsWith("-")) {
193
+ return { success: false, error: "Option --logs requires a value" };
194
+ }
195
+ if (!isValidName(value)) {
196
+ return {
197
+ success: false,
198
+ error: "Invalid name: must not contain path separators or be too long"
199
+ };
200
+ }
201
+ options.logs = value;
202
+ i += 2;
203
+ continue;
204
+ }
205
+ if (arg === "--tail" || arg === "-f") {
206
+ options.tail = true;
207
+ i++;
208
+ continue;
209
+ }
210
+ if (arg === "--lines") {
211
+ const value = args[i + 1];
212
+ if (!value || value.startsWith("-")) {
213
+ return { success: false, error: "Option --lines requires a positive integer" };
214
+ }
215
+ const num = Number(value);
216
+ if (!Number.isInteger(num) || num <= 0) {
217
+ return { success: false, error: "Option --lines requires a positive integer" };
218
+ }
219
+ options.lines = num;
220
+ i += 2;
221
+ continue;
222
+ }
180
223
  if (arg === "--timeout" || arg === "-t") {
181
224
  const value = args[i + 1];
182
225
  if (!value || value.startsWith("-")) {
@@ -190,6 +233,19 @@ function parseArgs(args) {
190
233
  i += 2;
191
234
  continue;
192
235
  }
236
+ if (arg === "--grace" || arg === "-g") {
237
+ const value = args[i + 1];
238
+ if (!value || value.startsWith("-")) {
239
+ return { success: false, error: "Option --grace requires a positive number (seconds)" };
240
+ }
241
+ const num = Number(value);
242
+ if (isNaN(num) || num <= 0) {
243
+ return { success: false, error: "Option --grace requires a positive number (seconds)" };
244
+ }
245
+ options.grace = num;
246
+ i += 2;
247
+ continue;
248
+ }
193
249
  if (arg.startsWith("-")) {
194
250
  return { success: false, error: `Unknown option: ${arg}` };
195
251
  }
@@ -207,6 +263,15 @@ function validateOptions(options) {
207
263
  if (options.kill) {
208
264
  return { success: true, options };
209
265
  }
266
+ if (options.logs) {
267
+ return { success: true, options };
268
+ }
269
+ if (options.tail) {
270
+ return { success: false, error: "Option --tail can only be used with --logs" };
271
+ }
272
+ if (options.lines !== void 0) {
273
+ return { success: false, error: "Option --lines can only be used with --logs" };
274
+ }
210
275
  if (options.status) {
211
276
  return { success: true, options };
212
277
  }
@@ -242,16 +307,23 @@ function getHelpText() {
242
307
  Usage:
243
308
  just-one -n <name> -- <command> Run command, killing any previous instance
244
309
  just-one -n <name> -e -- <command> Run only if not already running (ensure mode)
310
+ just-one -n <name> -D -- <command> Run in daemon mode (background, logs to file)
311
+ just-one -L <name> View captured logs for a named process
312
+ just-one -L <name> -f Follow logs in real-time (auto-exits on process death)
245
313
  just-one -k <name> Kill a named process
246
314
  just-one -K Kill all tracked processes
247
315
  just-one -s <name> Check if a named process is running
248
316
  just-one -p <name> Print the PID of a named process
249
317
  just-one -w <name> Wait for a named process to exit
250
318
  just-one -l List all tracked processes
251
- just-one --clean Remove stale PID files
319
+ just-one --clean Remove stale PID files and orphaned log files
252
320
 
253
321
  Options:
254
322
  -n, --name <name> Name to identify this process (required for running)
323
+ -D, --daemon Run in background with output captured to log file
324
+ -L, --logs <name> View captured logs for a named process
325
+ -f, --tail Follow log output in real-time (use with --logs)
326
+ --lines <n> Number of lines to show (use with --logs, default: all)
255
327
  -k, --kill <name> Kill the named process and exit
256
328
  -K, --kill-all Kill all tracked processes
257
329
  -s, --status <name> Check if a named process is running (exit 0=running, 1=stopped)
@@ -259,7 +331,8 @@ Options:
259
331
  -p, --pid <name> Print the PID of a named process
260
332
  -w, --wait <name> Wait for a named process to exit
261
333
  -t, --timeout <secs> Timeout in seconds (use with --wait)
262
- --clean Remove stale PID files
334
+ -g, --grace <secs> Grace period before force kill (default: 5s)
335
+ --clean Remove stale PID files and orphaned log files
263
336
  -l, --list List all tracked processes and their status
264
337
  -d, --pid-dir <dir> Directory for PID files (default: .just-one/)
265
338
  -q, --quiet Suppress output
@@ -273,6 +346,18 @@ Examples:
273
346
  # Run vite dev server only if not already running
274
347
  just-one -n vite -e -- npm run dev
275
348
 
349
+ # Run in daemon mode (background with log capture)
350
+ just-one -n myapp -D -- npm start
351
+
352
+ # View captured logs
353
+ just-one -L myapp
354
+
355
+ # View last 50 lines of logs
356
+ just-one -L myapp --lines 50
357
+
358
+ # Follow logs in real-time (like tail -f)
359
+ just-one -L myapp -f
360
+
276
361
  # Check if a process is running
277
362
  just-one -s storybook
278
363
 
@@ -285,7 +370,7 @@ Examples:
285
370
  # Wait for a process to exit (with 30s timeout)
286
371
  just-one -w myapp -t 30
287
372
 
288
- # Clean up stale PID files
373
+ # Clean up stale PID files and orphaned logs
289
374
  just-one --clean
290
375
 
291
376
  # Kill a named process
@@ -297,7 +382,15 @@ Examples:
297
382
  }
298
383
 
299
384
  // src/lib/pid.ts
300
- import { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
385
+ import {
386
+ readFileSync,
387
+ writeFileSync,
388
+ unlinkSync,
389
+ existsSync,
390
+ mkdirSync,
391
+ readdirSync,
392
+ statSync
393
+ } from "fs";
301
394
  import { join, dirname } from "path";
302
395
  function getPidFilePath(name, pidDir) {
303
396
  return join(pidDir, `${name}.pid`);
@@ -366,9 +459,11 @@ function listPids(pidDir) {
366
459
 
367
460
  // src/lib/process.ts
368
461
  import { spawn, execSync } from "child_process";
462
+ import { openSync, closeSync } from "fs";
369
463
  import pidusage from "pidusage";
370
464
  var isWindows = process.platform === "win32";
371
- var DEFAULT_WAIT_TIMEOUT_MS = 2e3;
465
+ var DEFAULT_GRACE_PERIOD_MS = 5e3;
466
+ var FORCE_KILL_WAIT_MS = 2e3;
372
467
  var CHECK_INTERVAL_MS = 100;
373
468
  function isValidPid(pid) {
374
469
  return Number.isInteger(pid) && pid > 0 && pid <= 4194304;
@@ -440,7 +535,7 @@ function tryKillUnix(pid) {
440
535
  return false;
441
536
  }
442
537
  }
443
- async function waitForProcessToDie(pid, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
538
+ async function waitForProcessToDie(pid, timeoutMs = DEFAULT_GRACE_PERIOD_MS) {
444
539
  const startTime = Date.now();
445
540
  while (Date.now() - startTime < timeoutMs) {
446
541
  if (!isProcessAlive(pid)) {
@@ -450,10 +545,52 @@ async function waitForProcessToDie(pid, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS) {
450
545
  }
451
546
  return !isProcessAlive(pid);
452
547
  }
548
+ function forceKillProcess(pid) {
549
+ if (!isValidPid(pid) || !isProcessAlive(pid)) {
550
+ return false;
551
+ }
552
+ try {
553
+ if (isWindows) {
554
+ execSync(`taskkill /PID ${pid} /T /F`, {
555
+ stdio: ["pipe", "pipe", "pipe"]
556
+ });
557
+ } else {
558
+ let killed = false;
559
+ try {
560
+ process.kill(-pid, "SIGKILL");
561
+ killed = true;
562
+ } catch {
563
+ }
564
+ try {
565
+ process.kill(pid, "SIGKILL");
566
+ killed = true;
567
+ } catch {
568
+ }
569
+ if (!killed) return false;
570
+ }
571
+ return true;
572
+ } catch {
573
+ return false;
574
+ }
575
+ }
576
+ async function terminateProcess(pid, gracePeriodMs) {
577
+ const grace = gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;
578
+ if (!isValidPid(pid)) {
579
+ return false;
580
+ }
581
+ if (!isProcessAlive(pid)) {
582
+ return true;
583
+ }
584
+ killProcess(pid);
585
+ const died = await waitForProcessToDie(pid, grace);
586
+ if (died) {
587
+ return true;
588
+ }
589
+ forceKillProcess(pid);
590
+ return await waitForProcessToDie(pid, FORCE_KILL_WAIT_MS);
591
+ }
453
592
  function spawnCommand(command, args) {
454
- const spawnCmd = isWindows ? `${command} ${args.join(" ")}` : command;
455
- const spawnArgs = isWindows ? [] : args;
456
- const child = spawn(spawnCmd, spawnArgs, {
593
+ const child = spawn(command, args, {
457
594
  stdio: "inherit",
458
595
  shell: isWindows,
459
596
  detached: !isWindows
@@ -466,6 +603,30 @@ function spawnCommand(command, args) {
466
603
  pid: child.pid
467
604
  };
468
605
  }
606
+ function spawnCommandDaemon(command, args, logFilePath) {
607
+ const logFd = openSync(logFilePath, "a");
608
+ try {
609
+ const stdio = ["ignore", logFd, logFd];
610
+ const child = spawn(command, args, {
611
+ stdio,
612
+ // Don't use shell on Windows for daemon mode. With shell: true, Node spawns
613
+ // cmd.exe which doesn't reliably pass fd-based stdio to grandchild processes
614
+ // when combined with detached: true (known Node.js issue on Windows).
615
+ // CreateProcess still searches PATH, so executables are found without a shell.
616
+ detached: true
617
+ });
618
+ if (child.pid === void 0) {
619
+ throw new Error("Failed to spawn daemon process");
620
+ }
621
+ child.unref();
622
+ return {
623
+ child,
624
+ pid: child.pid
625
+ };
626
+ } finally {
627
+ closeSync(logFd);
628
+ }
629
+ }
469
630
  var WINDOWS_GRACEFUL_TIMEOUT_MS = 2e3;
470
631
  function setupSignalHandlers(child, onExit) {
471
632
  let forceKillTimer = null;
@@ -512,6 +673,160 @@ function setupSignalHandlers(child, onExit) {
512
673
  });
513
674
  }
514
675
 
676
+ // src/index.ts
677
+ import { existsSync as existsSync3 } from "fs";
678
+
679
+ // src/lib/log.ts
680
+ import {
681
+ existsSync as existsSync2,
682
+ statSync as statSync2,
683
+ renameSync,
684
+ unlinkSync as unlinkSync2,
685
+ readFileSync as readFileSync2,
686
+ openSync as openSync2,
687
+ readSync,
688
+ closeSync as closeSync2
689
+ } from "fs";
690
+ import { join as join2 } from "path";
691
+ var DEFAULT_MAX_LOG_SIZE = 10 * 1024 * 1024;
692
+ function getLogFilePath(name, pidDir) {
693
+ return join2(pidDir, `${name}.log`);
694
+ }
695
+ function getBackupLogFilePath(name, pidDir) {
696
+ return join2(pidDir, `${name}.log.1`);
697
+ }
698
+ function getLogFileSize(name, pidDir) {
699
+ const logPath = getLogFilePath(name, pidDir);
700
+ try {
701
+ const stats = statSync2(logPath);
702
+ return stats.size;
703
+ } catch {
704
+ return 0;
705
+ }
706
+ }
707
+ function rotateLogIfNeeded(name, pidDir, maxSize = DEFAULT_MAX_LOG_SIZE) {
708
+ const size = getLogFileSize(name, pidDir);
709
+ if (size <= maxSize) {
710
+ return false;
711
+ }
712
+ const logPath = getLogFilePath(name, pidDir);
713
+ const backupPath = getBackupLogFilePath(name, pidDir);
714
+ try {
715
+ if (existsSync2(backupPath)) {
716
+ unlinkSync2(backupPath);
717
+ }
718
+ } catch {
719
+ }
720
+ renameSync(logPath, backupPath);
721
+ return true;
722
+ }
723
+ function readLogLines(name, pidDir, lastN) {
724
+ const logPath = getLogFilePath(name, pidDir);
725
+ if (!existsSync2(logPath)) {
726
+ return [];
727
+ }
728
+ try {
729
+ const content = readFileSync2(logPath, "utf8");
730
+ if (content.length === 0) {
731
+ return [];
732
+ }
733
+ const lines = content.split("\n");
734
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
735
+ lines.pop();
736
+ }
737
+ if (lastN === void 0) {
738
+ return lines;
739
+ }
740
+ if (lastN === 0) {
741
+ return [];
742
+ }
743
+ return lines.slice(-lastN);
744
+ } catch {
745
+ return [];
746
+ }
747
+ }
748
+ function tailLogFile(name, pidDir, options) {
749
+ const logPath = getLogFilePath(name, pidDir);
750
+ const pollIntervalMs = options.pollIntervalMs ?? 500;
751
+ if (options.initialLines !== void 0 && options.initialLines > 0) {
752
+ const initial = readLogLines(name, pidDir, options.initialLines);
753
+ for (const line of initial) {
754
+ options.onLine(line);
755
+ }
756
+ }
757
+ let offset = 0;
758
+ try {
759
+ if (existsSync2(logPath)) {
760
+ offset = statSync2(logPath).size;
761
+ }
762
+ } catch {
763
+ }
764
+ let partialLine = "";
765
+ const checkForChanges = () => {
766
+ let newSize;
767
+ try {
768
+ if (!existsSync2(logPath)) return;
769
+ newSize = statSync2(logPath).size;
770
+ } catch {
771
+ return;
772
+ }
773
+ if (newSize <= offset) {
774
+ offset = newSize;
775
+ return;
776
+ }
777
+ const bytesToRead = newSize - offset;
778
+ try {
779
+ const fd = openSync2(logPath, "r");
780
+ try {
781
+ const buf = Buffer.alloc(bytesToRead);
782
+ readSync(fd, buf, 0, bytesToRead, offset);
783
+ offset = newSize;
784
+ const chunk = buf.toString("utf8");
785
+ const parts = chunk.split("\n");
786
+ if (parts.length > 0) {
787
+ parts[0] = partialLine + parts[0];
788
+ partialLine = "";
789
+ }
790
+ const lastPart = parts.pop();
791
+ if (lastPart !== void 0 && lastPart !== "") {
792
+ partialLine = lastPart;
793
+ }
794
+ for (const line of parts) {
795
+ options.onLine(line);
796
+ }
797
+ } finally {
798
+ closeSync2(fd);
799
+ }
800
+ } catch (err) {
801
+ if (options.onError && err instanceof Error) {
802
+ options.onError(err);
803
+ }
804
+ }
805
+ };
806
+ const intervalId = setInterval(checkForChanges, pollIntervalMs);
807
+ return {
808
+ stop: () => {
809
+ clearInterval(intervalId);
810
+ }
811
+ };
812
+ }
813
+ function deleteLogFiles(name, pidDir) {
814
+ const logPath = getLogFilePath(name, pidDir);
815
+ const backupPath = getBackupLogFilePath(name, pidDir);
816
+ try {
817
+ if (existsSync2(logPath)) {
818
+ unlinkSync2(logPath);
819
+ }
820
+ } catch {
821
+ }
822
+ try {
823
+ if (existsSync2(backupPath)) {
824
+ unlinkSync2(backupPath);
825
+ }
826
+ } catch {
827
+ }
828
+ }
829
+
515
830
  // src/index.ts
516
831
  var require2 = createRequire(import.meta.url);
517
832
  var { version: VERSION } = require2("../package.json");
@@ -541,9 +856,9 @@ async function handleKill(name, options) {
541
856
  return 0;
542
857
  }
543
858
  log(`Killing process ${name} (PID: ${pid})...`, options);
544
- const killed = killProcess(pid);
545
- if (killed) {
546
- await waitForProcessToDie(pid);
859
+ const graceMs = options.grace !== void 0 ? options.grace * 1e3 : void 0;
860
+ const terminated = await terminateProcess(pid, graceMs);
861
+ if (terminated) {
547
862
  deletePid(name, options.pidDir);
548
863
  log(`Process ${name} killed`, options);
549
864
  return 0;
@@ -583,8 +898,11 @@ async function handleRun(options) {
583
898
  return 0;
584
899
  }
585
900
  log(`Killing existing process ${name} (PID: ${existingPid})...`, options);
586
- killProcess(existingPid);
587
- await waitForProcessToDie(existingPid);
901
+ const graceMs = options.grace !== void 0 ? options.grace * 1e3 : void 0;
902
+ const terminated = await terminateProcess(existingPid, graceMs);
903
+ if (!terminated) {
904
+ logError(`Warning: process ${existingPid} may still be running`);
905
+ }
588
906
  } else if (isProcessAlive(existingPid)) {
589
907
  log(
590
908
  `Stale PID file detected (PID ${existingPid} belongs to different process), skipping kill`,
@@ -595,6 +913,15 @@ async function handleRun(options) {
595
913
  }
596
914
  log(`Starting: ${command} ${args.join(" ")}`, options);
597
915
  try {
916
+ if (options.daemon) {
917
+ rotateLogIfNeeded(name, options.pidDir);
918
+ const logPath = getLogFilePath(name, options.pidDir);
919
+ const { pid: pid2 } = spawnCommandDaemon(command, args, logPath);
920
+ writePid(name, pid2, options.pidDir);
921
+ log(`Daemon started with PID: ${pid2}`, options);
922
+ log(`Logs: ${logPath}`, options);
923
+ return 0;
924
+ }
598
925
  const { child, pid } = spawnCommand(command, args);
599
926
  writePid(name, pid, options.pidDir);
600
927
  log(`Process started with PID: ${pid}`, options);
@@ -645,9 +972,9 @@ async function handleKillAll(options) {
645
972
  continue;
646
973
  }
647
974
  log(`Killing process ${info.name} (PID: ${info.pid})...`, options);
648
- const killed = killProcess(info.pid);
649
- if (killed) {
650
- await waitForProcessToDie(info.pid);
975
+ const graceMs = options.grace !== void 0 ? options.grace * 1e3 : void 0;
976
+ const terminated = await terminateProcess(info.pid, graceMs);
977
+ if (terminated) {
651
978
  deletePid(info.name, options.pidDir);
652
979
  log(`Process ${info.name} killed`, options);
653
980
  } else {
@@ -667,6 +994,7 @@ async function handleClean(options) {
667
994
  for (const info of pids) {
668
995
  if (!info.exists || info.pid <= 0) {
669
996
  deletePid(info.name, options.pidDir);
997
+ deleteLogFiles(info.name, options.pidDir);
670
998
  cleaned++;
671
999
  continue;
672
1000
  }
@@ -675,6 +1003,7 @@ async function handleClean(options) {
675
1003
  if (!isSameInstance) {
676
1004
  log(`Removing stale PID file: ${info.name} (PID: ${info.pid})`, options);
677
1005
  deletePid(info.name, options.pidDir);
1006
+ deleteLogFiles(info.name, options.pidDir);
678
1007
  cleaned++;
679
1008
  }
680
1009
  }
@@ -724,6 +1053,54 @@ async function handleWait(name, options) {
724
1053
  log(`Process ${name} (PID: ${pid}) has exited`, options);
725
1054
  return 0;
726
1055
  }
1056
+ async function handleLogs(name, options) {
1057
+ const logPath = getLogFilePath(name, options.pidDir);
1058
+ if (!existsSync3(logPath)) {
1059
+ logError(`No logs found for process: ${name}`);
1060
+ return 1;
1061
+ }
1062
+ if (!options.tail) {
1063
+ const lines = readLogLines(name, options.pidDir, options.lines);
1064
+ for (const line of lines) {
1065
+ console.log(line);
1066
+ }
1067
+ return 0;
1068
+ }
1069
+ const initialLines = options.lines ?? 10;
1070
+ const initial = readLogLines(name, options.pidDir, initialLines);
1071
+ for (const line of initial) {
1072
+ console.log(line);
1073
+ }
1074
+ const handle = tailLogFile(name, options.pidDir, {
1075
+ onLine: (line) => console.log(line),
1076
+ pollIntervalMs: 500
1077
+ });
1078
+ const pid = readPid(name, options.pidDir);
1079
+ const pidPollInterval = setInterval(() => {
1080
+ if (pid !== null && !isProcessAlive(pid)) {
1081
+ handle.stop();
1082
+ clearInterval(pidPollInterval);
1083
+ log(`Process ${name} has exited`, options);
1084
+ process.exit(0);
1085
+ }
1086
+ const currentPid = readPid(name, options.pidDir);
1087
+ if (currentPid === null) {
1088
+ handle.stop();
1089
+ clearInterval(pidPollInterval);
1090
+ log(`Process ${name} is no longer tracked`, options);
1091
+ process.exit(0);
1092
+ }
1093
+ }, 1e3);
1094
+ const cleanup = () => {
1095
+ handle.stop();
1096
+ clearInterval(pidPollInterval);
1097
+ process.exit(0);
1098
+ };
1099
+ process.on("SIGINT", cleanup);
1100
+ process.on("SIGTERM", cleanup);
1101
+ return new Promise(() => {
1102
+ });
1103
+ }
727
1104
  async function main() {
728
1105
  const args = process.argv.slice(2);
729
1106
  const parseResult = parseArgs(args);
@@ -759,6 +1136,9 @@ async function main() {
759
1136
  if (options.status) {
760
1137
  return await handleStatus(options.status, options);
761
1138
  }
1139
+ if (options.logs) {
1140
+ return await handleLogs(options.logs, options);
1141
+ }
762
1142
  if (options.clean) {
763
1143
  return await handleClean(options);
764
1144
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/lib/cli.ts","../src/lib/pid.ts","../src/lib/process.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * just-one - Ensure only one instance of a command runs at a time\n */\n\nimport { createRequire } from 'module';\nimport { parseArgs, validateOptions, getHelpText, type CliOptions } from './lib/cli.js';\nimport { readPid, writePid, deletePid, listPids, getPidFileMtime } from './lib/pid.js';\nimport {\n isProcessAlive,\n killProcess,\n waitForProcessToDie,\n spawnCommand,\n setupSignalHandlers,\n isSameProcessInstance,\n} from './lib/process.js';\n\n// Read version from package.json at runtime\nconst require = createRequire(import.meta.url);\nconst { version: VERSION } = require('../package.json');\n\nfunction log(message: string, options: CliOptions): void {\n if (!options.quiet) {\n console.log(message);\n }\n}\n\nfunction logError(message: string): void {\n console.error(message);\n}\n\nasync function handleKill(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 0;\n }\n\n // Verify this is the same process we originally started (prevents killing\n // unrelated processes that reused the same PID)\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (!isSameInstance) {\n if (isProcessAlive(pid)) {\n log(`PID ${pid} belongs to a different process, not killing`, options);\n } else {\n log(`Process ${name} (PID: ${pid}) is not running, cleaning up PID file`, options);\n }\n deletePid(name, options.pidDir);\n return 0;\n }\n\n log(`Killing process ${name} (PID: ${pid})...`, options);\n const killed = killProcess(pid);\n\n if (killed) {\n await waitForProcessToDie(pid);\n deletePid(name, options.pidDir);\n log(`Process ${name} killed`, options);\n return 0;\n } else {\n logError(`Failed to kill process ${name} (PID: ${pid})`);\n return 1;\n }\n}\n\nfunction handleList(options: CliOptions): number {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No tracked processes', options);\n return 0;\n }\n\n log('Tracked processes:', options);\n for (const info of pids) {\n const status = info.exists && isProcessAlive(info.pid) ? 'running' : 'stopped';\n const pidStr = info.pid > 0 ? String(info.pid) : 'unknown';\n log(` ${info.name}: PID ${pidStr} (${status})`, options);\n }\n\n return 0;\n}\n\nasync function handleRun(options: CliOptions): Promise<number> {\n const name = options.name!;\n const [command, ...args] = options.command;\n\n if (!command) {\n logError('No command specified');\n return 1;\n }\n\n // Check for existing process\n const existingPid = readPid(name, options.pidDir);\n if (existingPid !== null) {\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const shouldKill =\n pidFileMtime !== null && (await isSameProcessInstance(existingPid, pidFileMtime));\n\n if (shouldKill) {\n // In ensure mode, if the process is verified running, skip restart\n if (options.ensure) {\n log(`Process ${name} is already running (PID: ${existingPid}), skipping`, options);\n return 0;\n }\n log(`Killing existing process ${name} (PID: ${existingPid})...`, options);\n killProcess(existingPid);\n await waitForProcessToDie(existingPid);\n } else if (isProcessAlive(existingPid)) {\n // PID exists but doesn't match our process - likely PID reuse\n log(\n `Stale PID file detected (PID ${existingPid} belongs to different process), skipping kill`,\n options\n );\n }\n deletePid(name, options.pidDir);\n }\n\n // Spawn the new process\n log(`Starting: ${command} ${args.join(' ')}`, options);\n\n try {\n const { child, pid } = spawnCommand(command, args);\n\n // Save PID\n writePid(name, pid, options.pidDir);\n log(`Process started with PID: ${pid}`, options);\n\n // Set up signal handlers\n // Note: We intentionally do NOT delete the PID file on exit.\n // If the process exits unexpectedly, the PID file allows the next run\n // to find and kill any orphaned processes.\n setupSignalHandlers(child);\n\n // The process will keep running until it exits or is killed\n // The exit handler in setupSignalHandlers will call process.exit\n return 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logError(`Failed to start process: ${message}`);\n return 1;\n }\n}\n\nasync function handleStatus(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`Process ${name}: not tracked`, options);\n return 1;\n }\n\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (isSameInstance) {\n log(`Process ${name}: running (PID ${pid})`, options);\n return 0;\n }\n\n if (isProcessAlive(pid)) {\n log(`Process ${name}: stopped (PID ${pid} belongs to a different process)`, options);\n } else {\n log(`Process ${name}: stopped`, options);\n }\n return 1;\n}\n\nasync function handleKillAll(options: CliOptions): Promise<number> {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No tracked processes', options);\n return 0;\n }\n\n let failed = false;\n for (const info of pids) {\n if (!info.exists || info.pid <= 0) {\n deletePid(info.name, options.pidDir);\n continue;\n }\n\n const pidFileMtime = getPidFileMtime(info.name, options.pidDir);\n const isSameInstance =\n pidFileMtime !== null && (await isSameProcessInstance(info.pid, pidFileMtime));\n\n if (!isSameInstance) {\n log(`Process ${info.name} (PID: ${info.pid}) is stale, cleaning up`, options);\n deletePid(info.name, options.pidDir);\n continue;\n }\n\n log(`Killing process ${info.name} (PID: ${info.pid})...`, options);\n const killed = killProcess(info.pid);\n\n if (killed) {\n await waitForProcessToDie(info.pid);\n deletePid(info.name, options.pidDir);\n log(`Process ${info.name} killed`, options);\n } else {\n logError(`Failed to kill process ${info.name} (PID: ${info.pid})`);\n failed = true;\n }\n }\n\n return failed ? 1 : 0;\n}\n\nasync function handleClean(options: CliOptions): Promise<number> {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No PID files to clean', options);\n return 0;\n }\n\n let cleaned = 0;\n for (const info of pids) {\n if (!info.exists || info.pid <= 0) {\n deletePid(info.name, options.pidDir);\n cleaned++;\n continue;\n }\n\n const pidFileMtime = getPidFileMtime(info.name, options.pidDir);\n const isSameInstance =\n pidFileMtime !== null && (await isSameProcessInstance(info.pid, pidFileMtime));\n\n if (!isSameInstance) {\n log(`Removing stale PID file: ${info.name} (PID: ${info.pid})`, options);\n deletePid(info.name, options.pidDir);\n cleaned++;\n }\n }\n\n if (cleaned === 0) {\n log('No stale PID files found', options);\n } else {\n log(`Cleaned ${cleaned} stale PID file${cleaned === 1 ? '' : 's'}`, options);\n }\n\n return 0;\n}\n\nasync function handlePid(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 1;\n }\n\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (isSameInstance) {\n log(String(pid), options);\n return 0;\n }\n\n log(`Process ${name} is not running`, options);\n return 1;\n}\n\nasync function handleWait(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 1;\n }\n\n // Check if process is alive first, then verify identity if possible.\n // Wait is non-destructive (we only poll), so we can be lenient with identity checks.\n if (!isProcessAlive(pid)) {\n log(`Process ${name} (PID: ${pid}) is not running`, options);\n return 1;\n }\n\n log(`Waiting for process ${name} (PID: ${pid}) to exit...`, options);\n\n const timeoutMs = options.timeout !== undefined ? options.timeout * 1000 : undefined;\n const startTime = Date.now();\n const pollInterval = 500;\n\n while (isProcessAlive(pid)) {\n if (timeoutMs !== undefined && Date.now() - startTime >= timeoutMs) {\n log(`Timeout waiting for process ${name} (PID: ${pid})`, options);\n return 1;\n }\n await new Promise(resolve => setTimeout(resolve, pollInterval));\n }\n\n log(`Process ${name} (PID: ${pid}) has exited`, options);\n return 0;\n}\n\nasync function main(): Promise<number> {\n const args = process.argv.slice(2);\n\n // Parse arguments\n const parseResult = parseArgs(args);\n if (!parseResult.success) {\n logError(`Error: ${parseResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n const options = parseResult.options;\n\n // Validate options\n const validateResult = validateOptions(options);\n if (!validateResult.success) {\n logError(`Error: ${validateResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n // Handle help\n if (options.help) {\n console.log(getHelpText());\n return 0;\n }\n\n // Handle version\n if (options.version) {\n console.log(`just-one v${VERSION}`);\n return 0;\n }\n\n // Handle list\n if (options.list) {\n return handleList(options);\n }\n\n // Handle kill\n if (options.kill) {\n return await handleKill(options.kill, options);\n }\n\n // Handle kill all\n if (options.killAll) {\n return await handleKillAll(options);\n }\n\n // Handle status\n if (options.status) {\n return await handleStatus(options.status, options);\n }\n\n // Handle clean\n if (options.clean) {\n return await handleClean(options);\n }\n\n // Handle pid\n if (options.pid) {\n return await handlePid(options.pid, options);\n }\n\n // Handle wait\n if (options.wait) {\n return await handleWait(options.wait, options);\n }\n\n // Handle run (with optional --ensure modifier)\n return await handleRun(options);\n}\n\n// Run the CLI\nmain()\n .then(code => {\n // Only exit if we're not running a child process\n // The child process exit handler will call process.exit\n if (code !== 0) {\n process.exit(code);\n }\n })\n .catch(err => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n\n// Export for testing\nexport { main };\n","/**\n * CLI argument parsing for just-one\n */\n\nexport interface CliOptions {\n name?: string;\n kill?: string;\n list: boolean;\n status?: string;\n killAll: boolean;\n ensure: boolean;\n clean: boolean;\n pid?: string;\n wait?: string;\n timeout?: number;\n pidDir: string;\n quiet: boolean;\n help: boolean;\n version: boolean;\n command: string[];\n}\n\nexport interface ParseResult {\n success: true;\n options: CliOptions;\n}\n\nexport interface ParseError {\n success: false;\n error: string;\n}\n\nexport type ParseOutput = ParseResult | ParseError;\n\nconst DEFAULT_PID_DIR = '.just-one';\nconst MAX_NAME_LENGTH = 255;\n\n/**\n * Validate a process name for safe file operations\n * Rejects names containing path separators or traversal sequences\n */\nfunction isValidName(name: string): boolean {\n if (!name || name.length > MAX_NAME_LENGTH) {\n return false;\n }\n // Reject path separators and traversal sequences\n if (name.includes('/') || name.includes('\\\\') || name.includes('..')) {\n return false;\n }\n // Reject names that are only dots or whitespace\n if (/^[\\s.]*$/.test(name)) {\n return false;\n }\n return true;\n}\n\n/**\n * Validate a PID directory path for safe file operations\n * Rejects paths containing traversal sequences\n */\nfunction isValidPidDir(dir: string): boolean {\n if (!dir || dir.length > 1024) {\n return false;\n }\n // Reject path traversal sequences\n if (dir.includes('..')) {\n return false;\n }\n return true;\n}\n\n/**\n * Parse command line arguments\n */\nexport function parseArgs(args: string[]): ParseOutput {\n const options: CliOptions = {\n name: undefined,\n kill: undefined,\n list: false,\n status: undefined,\n killAll: false,\n ensure: false,\n clean: false,\n pid: undefined,\n wait: undefined,\n timeout: undefined,\n pidDir: DEFAULT_PID_DIR,\n quiet: false,\n help: false,\n version: false,\n command: [],\n };\n\n let i = 0;\n while (i < args.length) {\n // TypeScript requires this check due to noUncheckedIndexedAccess\n const arg = args[i]!;\n\n // Everything after -- is the command\n if (arg === '--') {\n options.command = args.slice(i + 1);\n break;\n }\n\n // Help\n if (arg === '--help' || arg === '-h') {\n options.help = true;\n i++;\n continue;\n }\n\n // Version\n if (arg === '--version' || arg === '-v') {\n options.version = true;\n i++;\n continue;\n }\n\n // List\n if (arg === '--list' || arg === '-l') {\n options.list = true;\n i++;\n continue;\n }\n\n // Quiet\n if (arg === '--quiet' || arg === '-q') {\n options.quiet = true;\n i++;\n continue;\n }\n\n // Name (requires value)\n if (arg === '--name' || arg === '-n') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --name requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.name = value;\n i += 2;\n continue;\n }\n\n // Kill (requires value)\n if (arg === '--kill' || arg === '-k') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --kill requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.kill = value;\n i += 2;\n continue;\n }\n\n // PID directory (requires value)\n if (arg === '--pid-dir' || arg === '-d') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --pid-dir requires a value' };\n }\n if (!isValidPidDir(value)) {\n return {\n success: false,\n error: 'Invalid PID directory: must not contain path traversal sequences',\n };\n }\n options.pidDir = value;\n i += 2;\n continue;\n }\n\n // Status (requires value)\n if (arg === '--status' || arg === '-s') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --status requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.status = value;\n i += 2;\n continue;\n }\n\n // Kill All\n if (arg === '--kill-all' || arg === '-K') {\n options.killAll = true;\n i++;\n continue;\n }\n\n // Ensure\n if (arg === '--ensure' || arg === '-e') {\n options.ensure = true;\n i++;\n continue;\n }\n\n // Clean\n if (arg === '--clean') {\n options.clean = true;\n i++;\n continue;\n }\n\n // PID output (requires value)\n if (arg === '--pid' || arg === '-p') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --pid requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.pid = value;\n i += 2;\n continue;\n }\n\n // Wait (requires value)\n if (arg === '--wait' || arg === '-w') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --wait requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.wait = value;\n i += 2;\n continue;\n }\n\n // Timeout (requires numeric value)\n if (arg === '--timeout' || arg === '-t') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n const num = Number(value);\n if (isNaN(num) || num <= 0) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n options.timeout = num;\n i += 2;\n continue;\n }\n\n // Unknown option\n if (arg.startsWith('-')) {\n return { success: false, error: `Unknown option: ${arg}` };\n }\n\n // Unexpected positional argument\n return { success: false, error: `Unexpected argument: ${arg}` };\n }\n\n return { success: true, options };\n}\n\n/**\n * Validate parsed options\n */\nexport function validateOptions(options: CliOptions): ParseOutput {\n // Help and version don't need validation\n if (options.help || options.version) {\n return { success: true, options };\n }\n\n // List doesn't need name or command\n if (options.list) {\n return { success: true, options };\n }\n\n // Kill only needs a name\n if (options.kill) {\n return { success: true, options };\n }\n\n // Standalone operations that don't need name or command\n if (options.status) {\n return { success: true, options };\n }\n if (options.killAll) {\n return { success: true, options };\n }\n if (options.clean) {\n return { success: true, options };\n }\n if (options.pid) {\n return { success: true, options };\n }\n if (options.wait) {\n if (options.timeout !== undefined && options.timeout <= 0) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n return { success: true, options };\n }\n\n // Timeout without wait is an error\n if (options.timeout !== undefined && !options.wait) {\n return { success: false, error: 'Option --timeout can only be used with --wait' };\n }\n\n // Running a command requires both name and command\n if (!options.name) {\n return { success: false, error: 'Option --name is required when running a command' };\n }\n\n if (options.command.length === 0) {\n return { success: false, error: 'No command specified. Use: just-one -n <name> -- <command>' };\n }\n\n return { success: true, options };\n}\n\n/**\n * Get help text\n */\nexport function getHelpText(): string {\n return `just-one - Ensure only one instance of a command runs at a time\n\nUsage:\n just-one -n <name> -- <command> Run command, killing any previous instance\n just-one -n <name> -e -- <command> Run only if not already running (ensure mode)\n just-one -k <name> Kill a named process\n just-one -K Kill all tracked processes\n just-one -s <name> Check if a named process is running\n just-one -p <name> Print the PID of a named process\n just-one -w <name> Wait for a named process to exit\n just-one -l List all tracked processes\n just-one --clean Remove stale PID files\n\nOptions:\n -n, --name <name> Name to identify this process (required for running)\n -k, --kill <name> Kill the named process and exit\n -K, --kill-all Kill all tracked processes\n -s, --status <name> Check if a named process is running (exit 0=running, 1=stopped)\n -e, --ensure Only start if not already running (use with -n and command)\n -p, --pid <name> Print the PID of a named process\n -w, --wait <name> Wait for a named process to exit\n -t, --timeout <secs> Timeout in seconds (use with --wait)\n --clean Remove stale PID files\n -l, --list List all tracked processes and their status\n -d, --pid-dir <dir> Directory for PID files (default: .just-one/)\n -q, --quiet Suppress output\n -h, --help Show this help message\n -v, --version Show version number\n\nExamples:\n # Run storybook, killing any previous instance\n just-one -n storybook -- npx storybook dev -p 6006\n\n # Run vite dev server only if not already running\n just-one -n vite -e -- npm run dev\n\n # Check if a process is running\n just-one -s storybook\n\n # Get the PID for scripting\n pid=$(just-one -p storybook -q)\n\n # Kill all tracked processes\n just-one -K\n\n # Wait for a process to exit (with 30s timeout)\n just-one -w myapp -t 30\n\n # Clean up stale PID files\n just-one --clean\n\n # Kill a named process\n just-one -k storybook\n\n # List all tracked processes\n just-one -l\n`;\n}\n","/**\r\n * PID file operations for just-one\r\n */\r\n\r\nimport { readFileSync, writeFileSync, unlinkSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';\r\nimport { join, dirname } from 'path';\r\n\r\nexport interface PidInfo {\r\n name: string;\r\n pid: number;\r\n exists: boolean;\r\n}\r\n\r\n/**\r\n * Get the path to a PID file for a given name\r\n */\r\nexport function getPidFilePath(name: string, pidDir: string): string {\r\n return join(pidDir, `${name}.pid`);\r\n}\r\n\r\n/**\r\n * Read the PID from a PID file\r\n * Returns null if the file doesn't exist or is invalid\r\n */\r\nexport function readPid(name: string, pidDir: string): number | null {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n\r\n if (!existsSync(pidFile)) {\r\n return null;\r\n }\r\n\r\n try {\r\n const content = readFileSync(pidFile, 'utf8').trim();\r\n const pid = parseInt(content, 10);\r\n\r\n if (isNaN(pid) || pid <= 0) {\r\n return null;\r\n }\r\n\r\n return pid;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Write a PID to a PID file\r\n * Creates the directory if it doesn't exist\r\n */\r\nexport function writePid(name: string, pid: number, pidDir: string): void {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n const dir = dirname(pidFile);\r\n\r\n if (!existsSync(dir)) {\r\n mkdirSync(dir, { recursive: true });\r\n }\r\n\r\n writeFileSync(pidFile, String(pid), 'utf8');\r\n}\r\n\r\n/**\r\n * Delete a PID file\r\n * Returns true if the file was deleted, false if it didn't exist\r\n */\r\nexport function deletePid(name: string, pidDir: string): boolean {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n\r\n if (!existsSync(pidFile)) {\r\n return false;\r\n }\r\n\r\n try {\r\n unlinkSync(pidFile);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get the modification time of a PID file as Unix timestamp (milliseconds)\r\n * Returns null if file doesn't exist\r\n */\r\nexport function getPidFileMtime(name: string, pidDir: string): number | null {\r\n const pidFile = getPidFilePath(name, pidDir);\r\n try {\r\n const stats = statSync(pidFile);\r\n return stats.mtimeMs;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * List all PID files in the directory\r\n * Returns information about each tracked process\r\n */\r\nexport function listPids(pidDir: string): PidInfo[] {\r\n if (!existsSync(pidDir)) {\r\n return [];\r\n }\r\n\r\n const files = readdirSync(pidDir);\r\n const pidFiles = files.filter(f => f.endsWith('.pid'));\r\n\r\n return pidFiles.map(file => {\r\n // Remove .pid suffix (use slice to only remove from end)\r\n const name = file.slice(0, -4);\r\n const pid = readPid(name, pidDir);\r\n\r\n return {\r\n name,\r\n pid: pid ?? 0,\r\n exists: pid !== null,\r\n };\r\n });\r\n}\r\n","/**\n * Cross-platform process handling for just-one\n */\n\nimport { spawn, execSync, ChildProcess } from 'child_process';\nimport pidusage from 'pidusage';\n\nconst isWindows = process.platform === 'win32';\n\n// Constants for process polling\nconst DEFAULT_WAIT_TIMEOUT_MS = 2000;\nconst CHECK_INTERVAL_MS = 100;\n\n/**\n * Validate that a PID is a safe positive integer for use in system calls\n */\nexport function isValidPid(pid: number): boolean {\n return Number.isInteger(pid) && pid > 0 && pid <= 4194304; // Max PID on most systems\n}\n\n// Tolerance for comparing PID file mtime with process start time\nconst START_TIME_TOLERANCE_MS = 5000; // 5 seconds\n\n/**\n * Get the start time of a process as Unix timestamp (milliseconds)\n * Returns null if process doesn't exist or start time can't be determined\n */\nexport async function getProcessStartTime(pid: number): Promise<number | null> {\n if (!isValidPid(pid)) {\n return null;\n }\n\n try {\n const stats = await pidusage(pid);\n // Calculate start time from current timestamp minus elapsed time\n return stats.timestamp - stats.elapsed;\n } catch {\n return null; // Process doesn't exist or can't get stats\n }\n}\n\n/**\n * Check if a running process is the same instance we originally spawned.\n * Compares process start time with PID file modification time.\n *\n * Returns true if:\n * - Process exists AND start time is within tolerance of pidFileMtime\n *\n * Returns false if:\n * - Process doesn't exist\n * - Can't determine process start time\n * - Start time doesn't match (likely PID reuse)\n */\nexport async function isSameProcessInstance(pid: number, pidFileMtimeMs: number): Promise<boolean> {\n const processStartTime = await getProcessStartTime(pid);\n if (processStartTime === null) {\n return false;\n }\n\n const diff = Math.abs(processStartTime - pidFileMtimeMs);\n return diff <= START_TIME_TOLERANCE_MS;\n}\n\n/**\n * Check if a process with the given PID is still running\n */\nexport function isProcessAlive(pid: number): boolean {\n try {\n if (!isValidPid(pid)) {\n return false;\n }\n if (isWindows) {\n // Windows: tasklist returns exit code 0 if process found\n // PID is validated as a safe integer above before interpolation\n const output = execSync(`tasklist /FI \"PID eq ${pid}\" /NH`, {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return output.includes(String(pid));\n } else {\n // Unix/Mac: kill -0 checks if process exists without killing it\n process.kill(pid, 0);\n return true;\n }\n } catch {\n return false;\n }\n}\n\n/**\n * Kill a process by PID\n * Returns true if the process was killed, false if it wasn't running\n */\nexport function killProcess(pid: number): boolean {\n if (!isValidPid(pid) || !isProcessAlive(pid)) {\n return false;\n }\n\n try {\n if (isWindows) {\n // Windows: taskkill with /T kills the process tree, /F forces\n // PID is validated as a safe integer above before interpolation\n execSync(`taskkill /PID ${pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } else {\n // Unix: try to kill process group first (catches child processes),\n // fall back to killing just the process if group kill fails\n const killed = tryKillUnix(-pid) || tryKillUnix(pid);\n if (!killed) {\n return false;\n }\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Helper to attempt Unix kill with error handling\n */\nfunction tryKillUnix(pid: number): boolean {\n try {\n process.kill(pid, 'SIGTERM');\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Wait for a process to die, with timeout\n * @param pid - Process ID to wait for\n * @param timeoutMs - Maximum time to wait (default: 2000ms)\n */\nexport async function waitForProcessToDie(\n pid: number,\n timeoutMs: number = DEFAULT_WAIT_TIMEOUT_MS\n): Promise<boolean> {\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (!isProcessAlive(pid)) {\n return true;\n }\n await new Promise(resolve => setTimeout(resolve, CHECK_INTERVAL_MS));\n }\n\n return !isProcessAlive(pid);\n}\n\nexport interface SpawnResult {\n child: ChildProcess;\n pid: number;\n}\n\n/**\n * Spawn a command with stdio forwarding\n */\nexport function spawnCommand(command: string, args: string[]): SpawnResult {\n // On Windows, pass entire command as a single string to avoid escaping issues\n // with shell: true (DEP0190 warning and argument handling)\n const spawnCmd = isWindows ? `${command} ${args.join(' ')}` : command;\n const spawnArgs = isWindows ? [] : args;\n\n const child = spawn(spawnCmd, spawnArgs, {\n stdio: 'inherit',\n shell: isWindows,\n detached: !isWindows,\n });\n\n if (child.pid === undefined) {\n throw new Error('Failed to spawn process');\n }\n\n return {\n child,\n pid: child.pid,\n };\n}\n\n// Grace period for Windows child process to exit before force-killing\nconst WINDOWS_GRACEFUL_TIMEOUT_MS = 2000;\n\n/**\n * Set up signal handlers to forward signals to child process\n *\n * Unix: forwards SIGTERM to child for graceful shutdown.\n *\n * Windows: the child shares the console (stdio: 'inherit'), so when the user\n * presses Ctrl+C, Windows delivers CTRL_C_EVENT to the child directly — no\n * forwarding needed. We just set a force-kill timeout as a safety net in case\n * the child doesn't exit on its own. process.kill(pid, 'SIGINT') on Windows\n * calls TerminateProcess (not GenerateConsoleCtrlEvent), so we intentionally\n * avoid calling it to give the child time to handle the OS-delivered signal.\n */\nexport function setupSignalHandlers(child: ChildProcess, onExit?: () => void): void {\n let forceKillTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceKillWindows = () => {\n if (child.pid && isValidPid(child.pid) && isProcessAlive(child.pid)) {\n try {\n execSync(`taskkill /PID ${child.pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch {\n // Process might already be dead\n }\n }\n };\n\n const handleSignal = (_signal: NodeJS.Signals) => {\n if (child.pid && isValidPid(child.pid)) {\n if (isWindows) {\n // On Windows, the child already received CTRL_C_EVENT from the OS\n // (since it shares our console via stdio: 'inherit').\n // Don't call process.kill() — it uses TerminateProcess which would\n // prevent the child from running its cleanup handlers.\n // Just set a force-kill timeout as a safety net.\n if (forceKillTimer === null) {\n forceKillTimer = setTimeout(forceKillWindows, WINDOWS_GRACEFUL_TIMEOUT_MS);\n forceKillTimer.unref();\n }\n } else {\n // Forward as SIGTERM for graceful shutdown\n child.kill('SIGTERM');\n }\n }\n };\n\n // Forward both SIGINT (Ctrl+C) and SIGTERM to child\n process.on('SIGINT', () => handleSignal('SIGINT'));\n process.on('SIGTERM', () => handleSignal('SIGTERM'));\n\n child.on('exit', (code, signal) => {\n // Child exited gracefully — cancel the force-kill timer if pending\n if (forceKillTimer !== null) {\n clearTimeout(forceKillTimer);\n forceKillTimer = null;\n }\n if (onExit) {\n onExit();\n }\n if (signal) {\n process.exit(128 + (signal === 'SIGTERM' ? 15 : signal === 'SIGINT' ? 2 : 1));\n }\n process.exit(code ?? 0);\n });\n\n child.on('error', err => {\n console.error(`Failed to start process: ${err.message}`);\n process.exit(1);\n });\n}\n"],"mappings":";;;AAKA,SAAS,qBAAqB;;;AC6B9B,IAAM,kBAAkB;AACxB,IAAM,kBAAkB;AAMxB,SAAS,YAAY,MAAuB;AAC1C,MAAI,CAAC,QAAQ,KAAK,SAAS,iBAAiB;AAC1C,WAAO;AAAA,EACT;AAEA,MAAI,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG;AACpE,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,KAAK,IAAI,GAAG;AACzB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAMA,SAAS,cAAc,KAAsB;AAC3C,MAAI,CAAC,OAAO,IAAI,SAAS,MAAM;AAC7B,WAAO;AAAA,EACT;AAEA,MAAI,IAAI,SAAS,IAAI,GAAG;AACtB,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAKO,SAAS,UAAU,MAA6B;AACrD,QAAM,UAAsB;AAAA,IAC1B,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,KAAK;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,CAAC;AAAA,EACZ;AAEA,MAAI,IAAI;AACR,SAAO,IAAI,KAAK,QAAQ;AAEtB,UAAM,MAAM,KAAK,CAAC;AAGlB,QAAI,QAAQ,MAAM;AAChB,cAAQ,UAAU,KAAK,MAAM,IAAI,CAAC;AAClC;AAAA,IACF;AAGA,QAAI,QAAQ,YAAY,QAAQ,MAAM;AACpC,cAAQ,OAAO;AACf;AACA;AAAA,IACF;AAGA,QAAI,QAAQ,eAAe,QAAQ,MAAM;AACvC,cAAQ,UAAU;AAClB;AACA;AAAA,IACF;AAGA,QAAI,QAAQ,YAAY,QAAQ,MAAM;AACpC,cAAQ,OAAO;AACf;AACA;AAAA,IACF;AAGA,QAAI,QAAQ,aAAa,QAAQ,MAAM;AACrC,cAAQ,QAAQ;AAChB;AACA;AAAA,IACF;AAGA,QAAI,QAAQ,YAAY,QAAQ,MAAM;AACpC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG,GAAG;AACnC,eAAO,EAAE,SAAS,OAAO,OAAO,iCAAiC;AAAA,MACnE;AACA,UAAI,CAAC,YAAY,KAAK,GAAG;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AACA,cAAQ,OAAO;AACf,WAAK;AACL;AAAA,IACF;AAGA,QAAI,QAAQ,YAAY,QAAQ,MAAM;AACpC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG,GAAG;AACnC,eAAO,EAAE,SAAS,OAAO,OAAO,iCAAiC;AAAA,MACnE;AACA,UAAI,CAAC,YAAY,KAAK,GAAG;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AACA,cAAQ,OAAO;AACf,WAAK;AACL;AAAA,IACF;AAGA,QAAI,QAAQ,eAAe,QAAQ,MAAM;AACvC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG,GAAG;AACnC,eAAO,EAAE,SAAS,OAAO,OAAO,oCAAoC;AAAA,MACtE;AACA,UAAI,CAAC,cAAc,KAAK,GAAG;AACzB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AACA,cAAQ,SAAS;AACjB,WAAK;AACL;AAAA,IACF;AAGA,QAAI,QAAQ,cAAc,QAAQ,MAAM;AACtC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG,GAAG;AACnC,eAAO,EAAE,SAAS,OAAO,OAAO,mCAAmC;AAAA,MACrE;AACA,UAAI,CAAC,YAAY,KAAK,GAAG;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AACA,cAAQ,SAAS;AACjB,WAAK;AACL;AAAA,IACF;AAGA,QAAI,QAAQ,gBAAgB,QAAQ,MAAM;AACxC,cAAQ,UAAU;AAClB;AACA;AAAA,IACF;AAGA,QAAI,QAAQ,cAAc,QAAQ,MAAM;AACtC,cAAQ,SAAS;AACjB;AACA;AAAA,IACF;AAGA,QAAI,QAAQ,WAAW;AACrB,cAAQ,QAAQ;AAChB;AACA;AAAA,IACF;AAGA,QAAI,QAAQ,WAAW,QAAQ,MAAM;AACnC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG,GAAG;AACnC,eAAO,EAAE,SAAS,OAAO,OAAO,gCAAgC;AAAA,MAClE;AACA,UAAI,CAAC,YAAY,KAAK,GAAG;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AACA,cAAQ,MAAM;AACd,WAAK;AACL;AAAA,IACF;AAGA,QAAI,QAAQ,YAAY,QAAQ,MAAM;AACpC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG,GAAG;AACnC,eAAO,EAAE,SAAS,OAAO,OAAO,iCAAiC;AAAA,MACnE;AACA,UAAI,CAAC,YAAY,KAAK,GAAG;AACvB,eAAO;AAAA,UACL,SAAS;AAAA,UACT,OAAO;AAAA,QACT;AAAA,MACF;AACA,cAAQ,OAAO;AACf,WAAK;AACL;AAAA,IACF;AAGA,QAAI,QAAQ,eAAe,QAAQ,MAAM;AACvC,YAAM,QAAQ,KAAK,IAAI,CAAC;AACxB,UAAI,CAAC,SAAS,MAAM,WAAW,GAAG,GAAG;AACnC,eAAO,EAAE,SAAS,OAAO,OAAO,8CAA8C;AAAA,MAChF;AACA,YAAM,MAAM,OAAO,KAAK;AACxB,UAAI,MAAM,GAAG,KAAK,OAAO,GAAG;AAC1B,eAAO,EAAE,SAAS,OAAO,OAAO,8CAA8C;AAAA,MAChF;AACA,cAAQ,UAAU;AAClB,WAAK;AACL;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,GAAG,GAAG;AACvB,aAAO,EAAE,SAAS,OAAO,OAAO,mBAAmB,GAAG,GAAG;AAAA,IAC3D;AAGA,WAAO,EAAE,SAAS,OAAO,OAAO,wBAAwB,GAAG,GAAG;AAAA,EAChE;AAEA,SAAO,EAAE,SAAS,MAAM,QAAQ;AAClC;AAKO,SAAS,gBAAgB,SAAkC;AAEhE,MAAI,QAAQ,QAAQ,QAAQ,SAAS;AACnC,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AAGA,MAAI,QAAQ,MAAM;AAChB,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AAGA,MAAI,QAAQ,MAAM;AAChB,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AAGA,MAAI,QAAQ,QAAQ;AAClB,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AACA,MAAI,QAAQ,SAAS;AACnB,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AACA,MAAI,QAAQ,OAAO;AACjB,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AACA,MAAI,QAAQ,KAAK;AACf,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AACA,MAAI,QAAQ,MAAM;AAChB,QAAI,QAAQ,YAAY,UAAa,QAAQ,WAAW,GAAG;AACzD,aAAO,EAAE,SAAS,OAAO,OAAO,8CAA8C;AAAA,IAChF;AACA,WAAO,EAAE,SAAS,MAAM,QAAQ;AAAA,EAClC;AAGA,MAAI,QAAQ,YAAY,UAAa,CAAC,QAAQ,MAAM;AAClD,WAAO,EAAE,SAAS,OAAO,OAAO,gDAAgD;AAAA,EAClF;AAGA,MAAI,CAAC,QAAQ,MAAM;AACjB,WAAO,EAAE,SAAS,OAAO,OAAO,mDAAmD;AAAA,EACrF;AAEA,MAAI,QAAQ,QAAQ,WAAW,GAAG;AAChC,WAAO,EAAE,SAAS,OAAO,OAAO,6DAA6D;AAAA,EAC/F;AAEA,SAAO,EAAE,SAAS,MAAM,QAAQ;AAClC;AAKO,SAAS,cAAsB;AACpC,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAyDT;;;AC3YA,SAAS,cAAc,eAAe,YAAY,YAAY,WAAW,aAAa,gBAAgB;AACtG,SAAS,MAAM,eAAe;AAWvB,SAAS,eAAe,MAAc,QAAwB;AACnE,SAAO,KAAK,QAAQ,GAAG,IAAI,MAAM;AACnC;AAMO,SAAS,QAAQ,MAAc,QAA+B;AACnE,QAAM,UAAU,eAAe,MAAM,MAAM;AAE3C,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,UAAU,aAAa,SAAS,MAAM,EAAE,KAAK;AACnD,UAAM,MAAM,SAAS,SAAS,EAAE;AAEhC,QAAI,MAAM,GAAG,KAAK,OAAO,GAAG;AAC1B,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,SAAS,MAAc,KAAa,QAAsB;AACxE,QAAM,UAAU,eAAe,MAAM,MAAM;AAC3C,QAAM,MAAM,QAAQ,OAAO;AAE3B,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACpC;AAEA,gBAAc,SAAS,OAAO,GAAG,GAAG,MAAM;AAC5C;AAMO,SAAS,UAAU,MAAc,QAAyB;AAC/D,QAAM,UAAU,eAAe,MAAM,MAAM;AAE3C,MAAI,CAAC,WAAW,OAAO,GAAG;AACxB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,eAAW,OAAO;AAClB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,gBAAgB,MAAc,QAA+B;AAC3E,QAAM,UAAU,eAAe,MAAM,MAAM;AAC3C,MAAI;AACF,UAAM,QAAQ,SAAS,OAAO;AAC9B,WAAO,MAAM;AAAA,EACf,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,SAAS,QAA2B;AAClD,MAAI,CAAC,WAAW,MAAM,GAAG;AACvB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAQ,YAAY,MAAM;AAChC,QAAM,WAAW,MAAM,OAAO,OAAK,EAAE,SAAS,MAAM,CAAC;AAErD,SAAO,SAAS,IAAI,UAAQ;AAE1B,UAAM,OAAO,KAAK,MAAM,GAAG,EAAE;AAC7B,UAAM,MAAM,QAAQ,MAAM,MAAM;AAEhC,WAAO;AAAA,MACL;AAAA,MACA,KAAK,OAAO;AAAA,MACZ,QAAQ,QAAQ;AAAA,IAClB;AAAA,EACF,CAAC;AACH;;;AChHA,SAAS,OAAO,gBAA8B;AAC9C,OAAO,cAAc;AAErB,IAAM,YAAY,QAAQ,aAAa;AAGvC,IAAM,0BAA0B;AAChC,IAAM,oBAAoB;AAKnB,SAAS,WAAW,KAAsB;AAC/C,SAAO,OAAO,UAAU,GAAG,KAAK,MAAM,KAAK,OAAO;AACpD;AAGA,IAAM,0BAA0B;AAMhC,eAAsB,oBAAoB,KAAqC;AAC7E,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,WAAO;AAAA,EACT;AAEA,MAAI;AACF,UAAM,QAAQ,MAAM,SAAS,GAAG;AAEhC,WAAO,MAAM,YAAY,MAAM;AAAA,EACjC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAcA,eAAsB,sBAAsB,KAAa,gBAA0C;AACjG,QAAM,mBAAmB,MAAM,oBAAoB,GAAG;AACtD,MAAI,qBAAqB,MAAM;AAC7B,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,KAAK,IAAI,mBAAmB,cAAc;AACvD,SAAO,QAAQ;AACjB;AAKO,SAAS,eAAe,KAAsB;AACnD,MAAI;AACF,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB,aAAO;AAAA,IACT;AACA,QAAI,WAAW;AAGb,YAAM,SAAS,SAAS,wBAAwB,GAAG,SAAS;AAAA,QAC1D,UAAU;AAAA,QACV,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAChC,CAAC;AACD,aAAO,OAAO,SAAS,OAAO,GAAG,CAAC;AAAA,IACpC,OAAO;AAEL,cAAQ,KAAK,KAAK,CAAC;AACnB,aAAO;AAAA,IACT;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,YAAY,KAAsB;AAChD,MAAI,CAAC,WAAW,GAAG,KAAK,CAAC,eAAe,GAAG,GAAG;AAC5C,WAAO;AAAA,EACT;AAEA,MAAI;AACF,QAAI,WAAW;AAGb,eAAS,iBAAiB,GAAG,UAAU;AAAA,QACrC,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAChC,CAAC;AAAA,IACH,OAAO;AAGL,YAAM,SAAS,YAAY,CAAC,GAAG,KAAK,YAAY,GAAG;AACnD,UAAI,CAAC,QAAQ;AACX,eAAO;AAAA,MACT;AAAA,IACF;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKA,SAAS,YAAY,KAAsB;AACzC,MAAI;AACF,YAAQ,KAAK,KAAK,SAAS;AAC3B,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,oBACpB,KACA,YAAoB,yBACF;AAClB,QAAM,YAAY,KAAK,IAAI;AAE3B,SAAO,KAAK,IAAI,IAAI,YAAY,WAAW;AACzC,QAAI,CAAC,eAAe,GAAG,GAAG;AACxB,aAAO;AAAA,IACT;AACA,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,iBAAiB,CAAC;AAAA,EACrE;AAEA,SAAO,CAAC,eAAe,GAAG;AAC5B;AAUO,SAAS,aAAa,SAAiB,MAA6B;AAGzE,QAAM,WAAW,YAAY,GAAG,OAAO,IAAI,KAAK,KAAK,GAAG,CAAC,KAAK;AAC9D,QAAM,YAAY,YAAY,CAAC,IAAI;AAEnC,QAAM,QAAQ,MAAM,UAAU,WAAW;AAAA,IACvC,OAAO;AAAA,IACP,OAAO;AAAA,IACP,UAAU,CAAC;AAAA,EACb,CAAC;AAED,MAAI,MAAM,QAAQ,QAAW;AAC3B,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,SAAO;AAAA,IACL;AAAA,IACA,KAAK,MAAM;AAAA,EACb;AACF;AAGA,IAAM,8BAA8B;AAc7B,SAAS,oBAAoB,OAAqB,QAA2B;AAClF,MAAI,iBAAuD;AAE3D,QAAM,mBAAmB,MAAM;AAC7B,QAAI,MAAM,OAAO,WAAW,MAAM,GAAG,KAAK,eAAe,MAAM,GAAG,GAAG;AACnE,UAAI;AACF,iBAAS,iBAAiB,MAAM,GAAG,UAAU;AAAA,UAC3C,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,QAChC,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,YAA4B;AAChD,QAAI,MAAM,OAAO,WAAW,MAAM,GAAG,GAAG;AACtC,UAAI,WAAW;AAMb,YAAI,mBAAmB,MAAM;AAC3B,2BAAiB,WAAW,kBAAkB,2BAA2B;AACzE,yBAAe,MAAM;AAAA,QACvB;AAAA,MACF,OAAO;AAEL,cAAM,KAAK,SAAS;AAAA,MACtB;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,GAAG,UAAU,MAAM,aAAa,QAAQ,CAAC;AACjD,UAAQ,GAAG,WAAW,MAAM,aAAa,SAAS,CAAC;AAEnD,QAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AAEjC,QAAI,mBAAmB,MAAM;AAC3B,mBAAa,cAAc;AAC3B,uBAAiB;AAAA,IACnB;AACA,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AACA,QAAI,QAAQ;AACV,cAAQ,KAAK,OAAO,WAAW,YAAY,KAAK,WAAW,WAAW,IAAI,EAAE;AAAA,IAC9E;AACA,YAAQ,KAAK,QAAQ,CAAC;AAAA,EACxB,CAAC;AAED,QAAM,GAAG,SAAS,SAAO;AACvB,YAAQ,MAAM,4BAA4B,IAAI,OAAO,EAAE;AACvD,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;;;AH5OA,IAAMA,WAAU,cAAc,YAAY,GAAG;AAC7C,IAAM,EAAE,SAAS,QAAQ,IAAIA,SAAQ,iBAAiB;AAEtD,SAAS,IAAI,SAAiB,SAA2B;AACvD,MAAI,CAAC,QAAQ,OAAO;AAClB,YAAQ,IAAI,OAAO;AAAA,EACrB;AACF;AAEA,SAAS,SAAS,SAAuB;AACvC,UAAQ,MAAM,OAAO;AACvB;AAEA,eAAe,WAAW,MAAc,SAAsC;AAC5E,QAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAExC,MAAI,QAAQ,MAAM;AAChB,QAAI,+BAA+B,IAAI,IAAI,OAAO;AAClD,WAAO;AAAA,EACT;AAIA,QAAM,eAAe,gBAAgB,MAAM,QAAQ,MAAM;AACzD,QAAM,iBAAiB,iBAAiB,QAAS,MAAM,sBAAsB,KAAK,YAAY;AAE9F,MAAI,CAAC,gBAAgB;AACnB,QAAI,eAAe,GAAG,GAAG;AACvB,UAAI,OAAO,GAAG,gDAAgD,OAAO;AAAA,IACvE,OAAO;AACL,UAAI,WAAW,IAAI,UAAU,GAAG,0CAA0C,OAAO;AAAA,IACnF;AACA,cAAU,MAAM,QAAQ,MAAM;AAC9B,WAAO;AAAA,EACT;AAEA,MAAI,mBAAmB,IAAI,UAAU,GAAG,QAAQ,OAAO;AACvD,QAAM,SAAS,YAAY,GAAG;AAE9B,MAAI,QAAQ;AACV,UAAM,oBAAoB,GAAG;AAC7B,cAAU,MAAM,QAAQ,MAAM;AAC9B,QAAI,WAAW,IAAI,WAAW,OAAO;AACrC,WAAO;AAAA,EACT,OAAO;AACL,aAAS,0BAA0B,IAAI,UAAU,GAAG,GAAG;AACvD,WAAO;AAAA,EACT;AACF;AAEA,SAAS,WAAW,SAA6B;AAC/C,QAAM,OAAO,SAAS,QAAQ,MAAM;AAEpC,MAAI,KAAK,WAAW,GAAG;AACrB,QAAI,wBAAwB,OAAO;AACnC,WAAO;AAAA,EACT;AAEA,MAAI,sBAAsB,OAAO;AACjC,aAAW,QAAQ,MAAM;AACvB,UAAM,SAAS,KAAK,UAAU,eAAe,KAAK,GAAG,IAAI,YAAY;AACrE,UAAM,SAAS,KAAK,MAAM,IAAI,OAAO,KAAK,GAAG,IAAI;AACjD,QAAI,KAAK,KAAK,IAAI,SAAS,MAAM,KAAK,MAAM,KAAK,OAAO;AAAA,EAC1D;AAEA,SAAO;AACT;AAEA,eAAe,UAAU,SAAsC;AAC7D,QAAM,OAAO,QAAQ;AACrB,QAAM,CAAC,SAAS,GAAG,IAAI,IAAI,QAAQ;AAEnC,MAAI,CAAC,SAAS;AACZ,aAAS,sBAAsB;AAC/B,WAAO;AAAA,EACT;AAGA,QAAM,cAAc,QAAQ,MAAM,QAAQ,MAAM;AAChD,MAAI,gBAAgB,MAAM;AACxB,UAAM,eAAe,gBAAgB,MAAM,QAAQ,MAAM;AACzD,UAAM,aACJ,iBAAiB,QAAS,MAAM,sBAAsB,aAAa,YAAY;AAEjF,QAAI,YAAY;AAEd,UAAI,QAAQ,QAAQ;AAClB,YAAI,WAAW,IAAI,6BAA6B,WAAW,eAAe,OAAO;AACjF,eAAO;AAAA,MACT;AACA,UAAI,4BAA4B,IAAI,UAAU,WAAW,QAAQ,OAAO;AACxE,kBAAY,WAAW;AACvB,YAAM,oBAAoB,WAAW;AAAA,IACvC,WAAW,eAAe,WAAW,GAAG;AAEtC;AAAA,QACE,gCAAgC,WAAW;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AACA,cAAU,MAAM,QAAQ,MAAM;AAAA,EAChC;AAGA,MAAI,aAAa,OAAO,IAAI,KAAK,KAAK,GAAG,CAAC,IAAI,OAAO;AAErD,MAAI;AACF,UAAM,EAAE,OAAO,IAAI,IAAI,aAAa,SAAS,IAAI;AAGjD,aAAS,MAAM,KAAK,QAAQ,MAAM;AAClC,QAAI,6BAA6B,GAAG,IAAI,OAAO;AAM/C,wBAAoB,KAAK;AAIzB,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,aAAS,4BAA4B,OAAO,EAAE;AAC9C,WAAO;AAAA,EACT;AACF;AAEA,eAAe,aAAa,MAAc,SAAsC;AAC9E,QAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAExC,MAAI,QAAQ,MAAM;AAChB,QAAI,WAAW,IAAI,iBAAiB,OAAO;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,gBAAgB,MAAM,QAAQ,MAAM;AACzD,QAAM,iBAAiB,iBAAiB,QAAS,MAAM,sBAAsB,KAAK,YAAY;AAE9F,MAAI,gBAAgB;AAClB,QAAI,WAAW,IAAI,kBAAkB,GAAG,KAAK,OAAO;AACpD,WAAO;AAAA,EACT;AAEA,MAAI,eAAe,GAAG,GAAG;AACvB,QAAI,WAAW,IAAI,kBAAkB,GAAG,oCAAoC,OAAO;AAAA,EACrF,OAAO;AACL,QAAI,WAAW,IAAI,aAAa,OAAO;AAAA,EACzC;AACA,SAAO;AACT;AAEA,eAAe,cAAc,SAAsC;AACjE,QAAM,OAAO,SAAS,QAAQ,MAAM;AAEpC,MAAI,KAAK,WAAW,GAAG;AACrB,QAAI,wBAAwB,OAAO;AACnC,WAAO;AAAA,EACT;AAEA,MAAI,SAAS;AACb,aAAW,QAAQ,MAAM;AACvB,QAAI,CAAC,KAAK,UAAU,KAAK,OAAO,GAAG;AACjC,gBAAU,KAAK,MAAM,QAAQ,MAAM;AACnC;AAAA,IACF;AAEA,UAAM,eAAe,gBAAgB,KAAK,MAAM,QAAQ,MAAM;AAC9D,UAAM,iBACJ,iBAAiB,QAAS,MAAM,sBAAsB,KAAK,KAAK,YAAY;AAE9E,QAAI,CAAC,gBAAgB;AACnB,UAAI,WAAW,KAAK,IAAI,UAAU,KAAK,GAAG,2BAA2B,OAAO;AAC5E,gBAAU,KAAK,MAAM,QAAQ,MAAM;AACnC;AAAA,IACF;AAEA,QAAI,mBAAmB,KAAK,IAAI,UAAU,KAAK,GAAG,QAAQ,OAAO;AACjE,UAAM,SAAS,YAAY,KAAK,GAAG;AAEnC,QAAI,QAAQ;AACV,YAAM,oBAAoB,KAAK,GAAG;AAClC,gBAAU,KAAK,MAAM,QAAQ,MAAM;AACnC,UAAI,WAAW,KAAK,IAAI,WAAW,OAAO;AAAA,IAC5C,OAAO;AACL,eAAS,0BAA0B,KAAK,IAAI,UAAU,KAAK,GAAG,GAAG;AACjE,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO,SAAS,IAAI;AACtB;AAEA,eAAe,YAAY,SAAsC;AAC/D,QAAM,OAAO,SAAS,QAAQ,MAAM;AAEpC,MAAI,KAAK,WAAW,GAAG;AACrB,QAAI,yBAAyB,OAAO;AACpC,WAAO;AAAA,EACT;AAEA,MAAI,UAAU;AACd,aAAW,QAAQ,MAAM;AACvB,QAAI,CAAC,KAAK,UAAU,KAAK,OAAO,GAAG;AACjC,gBAAU,KAAK,MAAM,QAAQ,MAAM;AACnC;AACA;AAAA,IACF;AAEA,UAAM,eAAe,gBAAgB,KAAK,MAAM,QAAQ,MAAM;AAC9D,UAAM,iBACJ,iBAAiB,QAAS,MAAM,sBAAsB,KAAK,KAAK,YAAY;AAE9E,QAAI,CAAC,gBAAgB;AACnB,UAAI,4BAA4B,KAAK,IAAI,UAAU,KAAK,GAAG,KAAK,OAAO;AACvE,gBAAU,KAAK,MAAM,QAAQ,MAAM;AACnC;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,GAAG;AACjB,QAAI,4BAA4B,OAAO;AAAA,EACzC,OAAO;AACL,QAAI,WAAW,OAAO,kBAAkB,YAAY,IAAI,KAAK,GAAG,IAAI,OAAO;AAAA,EAC7E;AAEA,SAAO;AACT;AAEA,eAAe,UAAU,MAAc,SAAsC;AAC3E,QAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAExC,MAAI,QAAQ,MAAM;AAChB,QAAI,+BAA+B,IAAI,IAAI,OAAO;AAClD,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,gBAAgB,MAAM,QAAQ,MAAM;AACzD,QAAM,iBAAiB,iBAAiB,QAAS,MAAM,sBAAsB,KAAK,YAAY;AAE9F,MAAI,gBAAgB;AAClB,QAAI,OAAO,GAAG,GAAG,OAAO;AACxB,WAAO;AAAA,EACT;AAEA,MAAI,WAAW,IAAI,mBAAmB,OAAO;AAC7C,SAAO;AACT;AAEA,eAAe,WAAW,MAAc,SAAsC;AAC5E,QAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAExC,MAAI,QAAQ,MAAM;AAChB,QAAI,+BAA+B,IAAI,IAAI,OAAO;AAClD,WAAO;AAAA,EACT;AAIA,MAAI,CAAC,eAAe,GAAG,GAAG;AACxB,QAAI,WAAW,IAAI,UAAU,GAAG,oBAAoB,OAAO;AAC3D,WAAO;AAAA,EACT;AAEA,MAAI,uBAAuB,IAAI,UAAU,GAAG,gBAAgB,OAAO;AAEnE,QAAM,YAAY,QAAQ,YAAY,SAAY,QAAQ,UAAU,MAAO;AAC3E,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,eAAe;AAErB,SAAO,eAAe,GAAG,GAAG;AAC1B,QAAI,cAAc,UAAa,KAAK,IAAI,IAAI,aAAa,WAAW;AAClE,UAAI,+BAA+B,IAAI,UAAU,GAAG,KAAK,OAAO;AAChE,aAAO;AAAA,IACT;AACA,UAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,YAAY,CAAC;AAAA,EAChE;AAEA,MAAI,WAAW,IAAI,UAAU,GAAG,gBAAgB,OAAO;AACvD,SAAO;AACT;AAEA,eAAe,OAAwB;AACrC,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAGjC,QAAM,cAAc,UAAU,IAAI;AAClC,MAAI,CAAC,YAAY,SAAS;AACxB,aAAS,UAAU,YAAY,KAAK,EAAE;AACtC,aAAS,kCAAkC;AAC3C,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,YAAY;AAG5B,QAAM,iBAAiB,gBAAgB,OAAO;AAC9C,MAAI,CAAC,eAAe,SAAS;AAC3B,aAAS,UAAU,eAAe,KAAK,EAAE;AACzC,aAAS,kCAAkC;AAC3C,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,MAAM;AAChB,YAAQ,IAAI,YAAY,CAAC;AACzB,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,SAAS;AACnB,YAAQ,IAAI,aAAa,OAAO,EAAE;AAClC,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,MAAM;AAChB,WAAO,WAAW,OAAO;AAAA,EAC3B;AAGA,MAAI,QAAQ,MAAM;AAChB,WAAO,MAAM,WAAW,QAAQ,MAAM,OAAO;AAAA,EAC/C;AAGA,MAAI,QAAQ,SAAS;AACnB,WAAO,MAAM,cAAc,OAAO;AAAA,EACpC;AAGA,MAAI,QAAQ,QAAQ;AAClB,WAAO,MAAM,aAAa,QAAQ,QAAQ,OAAO;AAAA,EACnD;AAGA,MAAI,QAAQ,OAAO;AACjB,WAAO,MAAM,YAAY,OAAO;AAAA,EAClC;AAGA,MAAI,QAAQ,KAAK;AACf,WAAO,MAAM,UAAU,QAAQ,KAAK,OAAO;AAAA,EAC7C;AAGA,MAAI,QAAQ,MAAM;AAChB,WAAO,MAAM,WAAW,QAAQ,MAAM,OAAO;AAAA,EAC/C;AAGA,SAAO,MAAM,UAAU,OAAO;AAChC;AAGA,KAAK,EACF,KAAK,UAAQ;AAGZ,MAAI,SAAS,GAAG;AACd,YAAQ,KAAK,IAAI;AAAA,EACnB;AACF,CAAC,EACA,MAAM,SAAO;AACZ,UAAQ,MAAM,qBAAqB,GAAG;AACtC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["require"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/lib/cli.ts","../src/lib/pid.ts","../src/lib/process.ts","../src/lib/log.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * just-one - Ensure only one instance of a command runs at a time\n */\n\nimport { createRequire } from 'module';\nimport { parseArgs, validateOptions, getHelpText, type CliOptions } from './lib/cli.js';\nimport { readPid, writePid, deletePid, listPids, getPidFileMtime } from './lib/pid.js';\nimport {\n isProcessAlive,\n terminateProcess,\n spawnCommand,\n spawnCommandDaemon,\n setupSignalHandlers,\n isSameProcessInstance,\n} from './lib/process.js';\nimport { existsSync } from 'fs';\nimport {\n getLogFilePath,\n rotateLogIfNeeded,\n readLogLines,\n tailLogFile,\n deleteLogFiles,\n} from './lib/log.js';\n\n// Read version from package.json at runtime\nconst require = createRequire(import.meta.url);\nconst { version: VERSION } = require('../package.json');\n\nfunction log(message: string, options: CliOptions): void {\n if (!options.quiet) {\n console.log(message);\n }\n}\n\nfunction logError(message: string): void {\n console.error(message);\n}\n\nasync function handleKill(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 0;\n }\n\n // Verify this is the same process we originally started (prevents killing\n // unrelated processes that reused the same PID)\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (!isSameInstance) {\n if (isProcessAlive(pid)) {\n log(`PID ${pid} belongs to a different process, not killing`, options);\n } else {\n log(`Process ${name} (PID: ${pid}) is not running, cleaning up PID file`, options);\n }\n deletePid(name, options.pidDir);\n return 0;\n }\n\n log(`Killing process ${name} (PID: ${pid})...`, options);\n const graceMs = options.grace !== undefined ? options.grace * 1000 : undefined;\n const terminated = await terminateProcess(pid, graceMs);\n\n if (terminated) {\n deletePid(name, options.pidDir);\n log(`Process ${name} killed`, options);\n return 0;\n } else {\n logError(`Failed to kill process ${name} (PID: ${pid})`);\n return 1;\n }\n}\n\nfunction handleList(options: CliOptions): number {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No tracked processes', options);\n return 0;\n }\n\n log('Tracked processes:', options);\n for (const info of pids) {\n const status = info.exists && isProcessAlive(info.pid) ? 'running' : 'stopped';\n const pidStr = info.pid > 0 ? String(info.pid) : 'unknown';\n log(` ${info.name}: PID ${pidStr} (${status})`, options);\n }\n\n return 0;\n}\n\nasync function handleRun(options: CliOptions): Promise<number> {\n const name = options.name!;\n const [command, ...args] = options.command;\n\n if (!command) {\n logError('No command specified');\n return 1;\n }\n\n // Check for existing process\n const existingPid = readPid(name, options.pidDir);\n if (existingPid !== null) {\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const shouldKill =\n pidFileMtime !== null && (await isSameProcessInstance(existingPid, pidFileMtime));\n\n if (shouldKill) {\n // In ensure mode, if the process is verified running, skip restart\n if (options.ensure) {\n log(`Process ${name} is already running (PID: ${existingPid}), skipping`, options);\n return 0;\n }\n log(`Killing existing process ${name} (PID: ${existingPid})...`, options);\n const graceMs = options.grace !== undefined ? options.grace * 1000 : undefined;\n const terminated = await terminateProcess(existingPid, graceMs);\n if (!terminated) {\n logError(`Warning: process ${existingPid} may still be running`);\n }\n } else if (isProcessAlive(existingPid)) {\n // PID exists but doesn't match our process - likely PID reuse\n log(\n `Stale PID file detected (PID ${existingPid} belongs to different process), skipping kill`,\n options\n );\n }\n deletePid(name, options.pidDir);\n }\n\n // Spawn the new process\n log(`Starting: ${command} ${args.join(' ')}`, options);\n\n try {\n if (options.daemon) {\n // Daemon mode: run detached with log file capture\n rotateLogIfNeeded(name, options.pidDir);\n const logPath = getLogFilePath(name, options.pidDir);\n const { pid } = spawnCommandDaemon(command, args, logPath);\n\n writePid(name, pid, options.pidDir);\n log(`Daemon started with PID: ${pid}`, options);\n log(`Logs: ${logPath}`, options);\n return 0;\n }\n\n // Foreground mode (existing behavior)\n const { child, pid } = spawnCommand(command, args);\n\n // Save PID\n writePid(name, pid, options.pidDir);\n log(`Process started with PID: ${pid}`, options);\n\n // Set up signal handlers\n // Note: We intentionally do NOT delete the PID file on exit.\n // If the process exits unexpectedly, the PID file allows the next run\n // to find and kill any orphaned processes.\n setupSignalHandlers(child);\n\n // The process will keep running until it exits or is killed\n // The exit handler in setupSignalHandlers will call process.exit\n return 0;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logError(`Failed to start process: ${message}`);\n return 1;\n }\n}\n\nasync function handleStatus(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`Process ${name}: not tracked`, options);\n return 1;\n }\n\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (isSameInstance) {\n log(`Process ${name}: running (PID ${pid})`, options);\n return 0;\n }\n\n if (isProcessAlive(pid)) {\n log(`Process ${name}: stopped (PID ${pid} belongs to a different process)`, options);\n } else {\n log(`Process ${name}: stopped`, options);\n }\n return 1;\n}\n\nasync function handleKillAll(options: CliOptions): Promise<number> {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No tracked processes', options);\n return 0;\n }\n\n let failed = false;\n for (const info of pids) {\n if (!info.exists || info.pid <= 0) {\n deletePid(info.name, options.pidDir);\n continue;\n }\n\n const pidFileMtime = getPidFileMtime(info.name, options.pidDir);\n const isSameInstance =\n pidFileMtime !== null && (await isSameProcessInstance(info.pid, pidFileMtime));\n\n if (!isSameInstance) {\n log(`Process ${info.name} (PID: ${info.pid}) is stale, cleaning up`, options);\n deletePid(info.name, options.pidDir);\n continue;\n }\n\n log(`Killing process ${info.name} (PID: ${info.pid})...`, options);\n const graceMs = options.grace !== undefined ? options.grace * 1000 : undefined;\n const terminated = await terminateProcess(info.pid, graceMs);\n\n if (terminated) {\n deletePid(info.name, options.pidDir);\n log(`Process ${info.name} killed`, options);\n } else {\n logError(`Failed to kill process ${info.name} (PID: ${info.pid})`);\n failed = true;\n }\n }\n\n return failed ? 1 : 0;\n}\n\nasync function handleClean(options: CliOptions): Promise<number> {\n const pids = listPids(options.pidDir);\n\n if (pids.length === 0) {\n log('No PID files to clean', options);\n return 0;\n }\n\n let cleaned = 0;\n for (const info of pids) {\n if (!info.exists || info.pid <= 0) {\n deletePid(info.name, options.pidDir);\n deleteLogFiles(info.name, options.pidDir);\n cleaned++;\n continue;\n }\n\n const pidFileMtime = getPidFileMtime(info.name, options.pidDir);\n const isSameInstance =\n pidFileMtime !== null && (await isSameProcessInstance(info.pid, pidFileMtime));\n\n if (!isSameInstance) {\n log(`Removing stale PID file: ${info.name} (PID: ${info.pid})`, options);\n deletePid(info.name, options.pidDir);\n deleteLogFiles(info.name, options.pidDir);\n cleaned++;\n }\n }\n\n if (cleaned === 0) {\n log('No stale PID files found', options);\n } else {\n log(`Cleaned ${cleaned} stale PID file${cleaned === 1 ? '' : 's'}`, options);\n }\n\n return 0;\n}\n\nasync function handlePid(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 1;\n }\n\n const pidFileMtime = getPidFileMtime(name, options.pidDir);\n const isSameInstance = pidFileMtime !== null && (await isSameProcessInstance(pid, pidFileMtime));\n\n if (isSameInstance) {\n log(String(pid), options);\n return 0;\n }\n\n log(`Process ${name} is not running`, options);\n return 1;\n}\n\nasync function handleWait(name: string, options: CliOptions): Promise<number> {\n const pid = readPid(name, options.pidDir);\n\n if (pid === null) {\n log(`No process found with name: ${name}`, options);\n return 1;\n }\n\n // Check if process is alive first, then verify identity if possible.\n // Wait is non-destructive (we only poll), so we can be lenient with identity checks.\n if (!isProcessAlive(pid)) {\n log(`Process ${name} (PID: ${pid}) is not running`, options);\n return 1;\n }\n\n log(`Waiting for process ${name} (PID: ${pid}) to exit...`, options);\n\n const timeoutMs = options.timeout !== undefined ? options.timeout * 1000 : undefined;\n const startTime = Date.now();\n const pollInterval = 500;\n\n while (isProcessAlive(pid)) {\n if (timeoutMs !== undefined && Date.now() - startTime >= timeoutMs) {\n log(`Timeout waiting for process ${name} (PID: ${pid})`, options);\n return 1;\n }\n await new Promise(resolve => setTimeout(resolve, pollInterval));\n }\n\n log(`Process ${name} (PID: ${pid}) has exited`, options);\n return 0;\n}\n\nasync function handleLogs(name: string, options: CliOptions): Promise<number> {\n const logPath = getLogFilePath(name, options.pidDir);\n\n if (!existsSync(logPath)) {\n logError(`No logs found for process: ${name}`);\n return 1;\n }\n\n if (!options.tail) {\n // Static mode: print lines and exit\n const lines = readLogLines(name, options.pidDir, options.lines);\n for (const line of lines) {\n console.log(line);\n }\n return 0;\n }\n\n // Follow mode: print initial lines, then tail\n const initialLines = options.lines ?? 10;\n const initial = readLogLines(name, options.pidDir, initialLines);\n for (const line of initial) {\n console.log(line);\n }\n\n const handle = tailLogFile(name, options.pidDir, {\n onLine: line => console.log(line),\n pollIntervalMs: 500,\n });\n\n // Poll PID to auto-stop when process dies\n const pid = readPid(name, options.pidDir);\n const pidPollInterval = setInterval(() => {\n if (pid !== null && !isProcessAlive(pid)) {\n handle.stop();\n clearInterval(pidPollInterval);\n log(`Process ${name} has exited`, options);\n process.exit(0);\n }\n // Also stop if no PID file at all\n const currentPid = readPid(name, options.pidDir);\n if (currentPid === null) {\n handle.stop();\n clearInterval(pidPollInterval);\n log(`Process ${name} is no longer tracked`, options);\n process.exit(0);\n }\n }, 1000);\n\n // Handle SIGINT/SIGTERM for clean exit\n const cleanup = () => {\n handle.stop();\n clearInterval(pidPollInterval);\n process.exit(0);\n };\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n\n // Keep the process alive\n return new Promise<number>(() => {\n // Never resolves — exits via cleanup or process death detection\n });\n}\n\nasync function main(): Promise<number> {\n const args = process.argv.slice(2);\n\n // Parse arguments\n const parseResult = parseArgs(args);\n if (!parseResult.success) {\n logError(`Error: ${parseResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n const options = parseResult.options;\n\n // Validate options\n const validateResult = validateOptions(options);\n if (!validateResult.success) {\n logError(`Error: ${validateResult.error}`);\n logError('Use --help for usage information');\n return 1;\n }\n\n // Handle help\n if (options.help) {\n console.log(getHelpText());\n return 0;\n }\n\n // Handle version\n if (options.version) {\n console.log(`just-one v${VERSION}`);\n return 0;\n }\n\n // Handle list\n if (options.list) {\n return handleList(options);\n }\n\n // Handle kill\n if (options.kill) {\n return await handleKill(options.kill, options);\n }\n\n // Handle kill all\n if (options.killAll) {\n return await handleKillAll(options);\n }\n\n // Handle status\n if (options.status) {\n return await handleStatus(options.status, options);\n }\n\n // Handle logs\n if (options.logs) {\n return await handleLogs(options.logs, options);\n }\n\n // Handle clean\n if (options.clean) {\n return await handleClean(options);\n }\n\n // Handle pid\n if (options.pid) {\n return await handlePid(options.pid, options);\n }\n\n // Handle wait\n if (options.wait) {\n return await handleWait(options.wait, options);\n }\n\n // Handle run (with optional --ensure modifier)\n return await handleRun(options);\n}\n\n// Run the CLI\nmain()\n .then(code => {\n // Only exit if we're not running a child process\n // The child process exit handler will call process.exit\n if (code !== 0) {\n process.exit(code);\n }\n })\n .catch(err => {\n console.error('Unexpected error:', err);\n process.exit(1);\n });\n\n// Export for testing\nexport { main };\n","/**\n * CLI argument parsing for just-one\n */\n\nexport interface CliOptions {\n name?: string;\n kill?: string;\n list: boolean;\n status?: string;\n killAll: boolean;\n ensure: boolean;\n clean: boolean;\n pid?: string;\n wait?: string;\n timeout?: number;\n grace?: number;\n daemon: boolean;\n logs?: string;\n tail: boolean;\n lines?: number;\n pidDir: string;\n quiet: boolean;\n help: boolean;\n version: boolean;\n command: string[];\n}\n\nexport interface ParseResult {\n success: true;\n options: CliOptions;\n}\n\nexport interface ParseError {\n success: false;\n error: string;\n}\n\nexport type ParseOutput = ParseResult | ParseError;\n\nconst DEFAULT_PID_DIR = '.just-one';\nconst MAX_NAME_LENGTH = 255;\n\n/**\n * Validate a process name for safe file operations\n * Rejects names containing path separators or traversal sequences\n */\nfunction isValidName(name: string): boolean {\n if (!name || name.length > MAX_NAME_LENGTH) {\n return false;\n }\n // Reject path separators and traversal sequences\n if (name.includes('/') || name.includes('\\\\') || name.includes('..')) {\n return false;\n }\n // Reject names that are only dots or whitespace\n if (/^[\\s.]*$/.test(name)) {\n return false;\n }\n return true;\n}\n\n/**\n * Validate a PID directory path for safe file operations\n * Rejects paths containing traversal sequences\n */\nfunction isValidPidDir(dir: string): boolean {\n if (!dir || dir.length > 1024) {\n return false;\n }\n // Reject path traversal sequences\n if (dir.includes('..')) {\n return false;\n }\n return true;\n}\n\n/**\n * Parse command line arguments\n */\nexport function parseArgs(args: string[]): ParseOutput {\n const options: CliOptions = {\n name: undefined,\n kill: undefined,\n list: false,\n status: undefined,\n killAll: false,\n ensure: false,\n clean: false,\n pid: undefined,\n wait: undefined,\n timeout: undefined,\n grace: undefined,\n daemon: false,\n logs: undefined,\n tail: false,\n lines: undefined,\n pidDir: DEFAULT_PID_DIR,\n quiet: false,\n help: false,\n version: false,\n command: [],\n };\n\n let i = 0;\n while (i < args.length) {\n // TypeScript requires this check due to noUncheckedIndexedAccess\n const arg = args[i]!;\n\n // Everything after -- is the command\n if (arg === '--') {\n options.command = args.slice(i + 1);\n break;\n }\n\n // Help\n if (arg === '--help' || arg === '-h') {\n options.help = true;\n i++;\n continue;\n }\n\n // Version\n if (arg === '--version' || arg === '-v') {\n options.version = true;\n i++;\n continue;\n }\n\n // List\n if (arg === '--list' || arg === '-l') {\n options.list = true;\n i++;\n continue;\n }\n\n // Quiet\n if (arg === '--quiet' || arg === '-q') {\n options.quiet = true;\n i++;\n continue;\n }\n\n // Name (requires value)\n if (arg === '--name' || arg === '-n') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --name requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.name = value;\n i += 2;\n continue;\n }\n\n // Kill (requires value)\n if (arg === '--kill' || arg === '-k') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --kill requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.kill = value;\n i += 2;\n continue;\n }\n\n // PID directory (requires value)\n if (arg === '--pid-dir' || arg === '-d') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --pid-dir requires a value' };\n }\n if (!isValidPidDir(value)) {\n return {\n success: false,\n error: 'Invalid PID directory: must not contain path traversal sequences',\n };\n }\n options.pidDir = value;\n i += 2;\n continue;\n }\n\n // Status (requires value)\n if (arg === '--status' || arg === '-s') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --status requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.status = value;\n i += 2;\n continue;\n }\n\n // Kill All\n if (arg === '--kill-all' || arg === '-K') {\n options.killAll = true;\n i++;\n continue;\n }\n\n // Ensure\n if (arg === '--ensure' || arg === '-e') {\n options.ensure = true;\n i++;\n continue;\n }\n\n // Clean\n if (arg === '--clean') {\n options.clean = true;\n i++;\n continue;\n }\n\n // PID output (requires value)\n if (arg === '--pid' || arg === '-p') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --pid requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.pid = value;\n i += 2;\n continue;\n }\n\n // Wait (requires value)\n if (arg === '--wait' || arg === '-w') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --wait requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.wait = value;\n i += 2;\n continue;\n }\n\n // Daemon\n if (arg === '--daemon' || arg === '-D') {\n options.daemon = true;\n i++;\n continue;\n }\n\n // Logs (requires value)\n if (arg === '--logs' || arg === '-L') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --logs requires a value' };\n }\n if (!isValidName(value)) {\n return {\n success: false,\n error: 'Invalid name: must not contain path separators or be too long',\n };\n }\n options.logs = value;\n i += 2;\n continue;\n }\n\n // Tail (follow logs)\n if (arg === '--tail' || arg === '-f') {\n options.tail = true;\n i++;\n continue;\n }\n\n // Lines (requires positive integer value)\n if (arg === '--lines') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --lines requires a positive integer' };\n }\n const num = Number(value);\n if (!Number.isInteger(num) || num <= 0) {\n return { success: false, error: 'Option --lines requires a positive integer' };\n }\n options.lines = num;\n i += 2;\n continue;\n }\n\n // Timeout (requires numeric value)\n if (arg === '--timeout' || arg === '-t') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n const num = Number(value);\n if (isNaN(num) || num <= 0) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n options.timeout = num;\n i += 2;\n continue;\n }\n\n // Grace period for kill (requires numeric value)\n if (arg === '--grace' || arg === '-g') {\n const value = args[i + 1];\n if (!value || value.startsWith('-')) {\n return { success: false, error: 'Option --grace requires a positive number (seconds)' };\n }\n const num = Number(value);\n if (isNaN(num) || num <= 0) {\n return { success: false, error: 'Option --grace requires a positive number (seconds)' };\n }\n options.grace = num;\n i += 2;\n continue;\n }\n\n // Unknown option\n if (arg.startsWith('-')) {\n return { success: false, error: `Unknown option: ${arg}` };\n }\n\n // Unexpected positional argument\n return { success: false, error: `Unexpected argument: ${arg}` };\n }\n\n return { success: true, options };\n}\n\n/**\n * Validate parsed options\n */\nexport function validateOptions(options: CliOptions): ParseOutput {\n // Help and version don't need validation\n if (options.help || options.version) {\n return { success: true, options };\n }\n\n // List doesn't need name or command\n if (options.list) {\n return { success: true, options };\n }\n\n // Kill only needs a name\n if (options.kill) {\n return { success: true, options };\n }\n\n // Logs is a standalone operation\n if (options.logs) {\n return { success: true, options };\n }\n\n // --tail and --lines only valid with --logs\n if (options.tail) {\n return { success: false, error: 'Option --tail can only be used with --logs' };\n }\n if (options.lines !== undefined) {\n return { success: false, error: 'Option --lines can only be used with --logs' };\n }\n\n // Standalone operations that don't need name or command\n if (options.status) {\n return { success: true, options };\n }\n if (options.killAll) {\n return { success: true, options };\n }\n if (options.clean) {\n return { success: true, options };\n }\n if (options.pid) {\n return { success: true, options };\n }\n if (options.wait) {\n if (options.timeout !== undefined && options.timeout <= 0) {\n return { success: false, error: 'Option --timeout requires a positive number' };\n }\n return { success: true, options };\n }\n\n // Timeout without wait is an error\n if (options.timeout !== undefined && !options.wait) {\n return { success: false, error: 'Option --timeout can only be used with --wait' };\n }\n\n // Daemon requires name and command (validated below as part of normal run)\n // No special check needed here since daemon is a modifier for run\n\n // Running a command requires both name and command\n if (!options.name) {\n return { success: false, error: 'Option --name is required when running a command' };\n }\n\n if (options.command.length === 0) {\n return { success: false, error: 'No command specified. Use: just-one -n <name> -- <command>' };\n }\n\n return { success: true, options };\n}\n\n/**\n * Get help text\n */\nexport function getHelpText(): string {\n return `just-one - Ensure only one instance of a command runs at a time\n\nUsage:\n just-one -n <name> -- <command> Run command, killing any previous instance\n just-one -n <name> -e -- <command> Run only if not already running (ensure mode)\n just-one -n <name> -D -- <command> Run in daemon mode (background, logs to file)\n just-one -L <name> View captured logs for a named process\n just-one -L <name> -f Follow logs in real-time (auto-exits on process death)\n just-one -k <name> Kill a named process\n just-one -K Kill all tracked processes\n just-one -s <name> Check if a named process is running\n just-one -p <name> Print the PID of a named process\n just-one -w <name> Wait for a named process to exit\n just-one -l List all tracked processes\n just-one --clean Remove stale PID files and orphaned log files\n\nOptions:\n -n, --name <name> Name to identify this process (required for running)\n -D, --daemon Run in background with output captured to log file\n -L, --logs <name> View captured logs for a named process\n -f, --tail Follow log output in real-time (use with --logs)\n --lines <n> Number of lines to show (use with --logs, default: all)\n -k, --kill <name> Kill the named process and exit\n -K, --kill-all Kill all tracked processes\n -s, --status <name> Check if a named process is running (exit 0=running, 1=stopped)\n -e, --ensure Only start if not already running (use with -n and command)\n -p, --pid <name> Print the PID of a named process\n -w, --wait <name> Wait for a named process to exit\n -t, --timeout <secs> Timeout in seconds (use with --wait)\n -g, --grace <secs> Grace period before force kill (default: 5s)\n --clean Remove stale PID files and orphaned log files\n -l, --list List all tracked processes and their status\n -d, --pid-dir <dir> Directory for PID files (default: .just-one/)\n -q, --quiet Suppress output\n -h, --help Show this help message\n -v, --version Show version number\n\nExamples:\n # Run storybook, killing any previous instance\n just-one -n storybook -- npx storybook dev -p 6006\n\n # Run vite dev server only if not already running\n just-one -n vite -e -- npm run dev\n\n # Run in daemon mode (background with log capture)\n just-one -n myapp -D -- npm start\n\n # View captured logs\n just-one -L myapp\n\n # View last 50 lines of logs\n just-one -L myapp --lines 50\n\n # Follow logs in real-time (like tail -f)\n just-one -L myapp -f\n\n # Check if a process is running\n just-one -s storybook\n\n # Get the PID for scripting\n pid=$(just-one -p storybook -q)\n\n # Kill all tracked processes\n just-one -K\n\n # Wait for a process to exit (with 30s timeout)\n just-one -w myapp -t 30\n\n # Clean up stale PID files and orphaned logs\n just-one --clean\n\n # Kill a named process\n just-one -k storybook\n\n # List all tracked processes\n just-one -l\n`;\n}\n","/**\n * PID file operations for just-one\n */\n\nimport {\n readFileSync,\n writeFileSync,\n unlinkSync,\n existsSync,\n mkdirSync,\n readdirSync,\n statSync,\n} from 'fs';\nimport { join, dirname } from 'path';\n\nexport interface PidInfo {\n name: string;\n pid: number;\n exists: boolean;\n}\n\n/**\n * Get the path to a PID file for a given name\n */\nexport function getPidFilePath(name: string, pidDir: string): string {\n return join(pidDir, `${name}.pid`);\n}\n\n/**\n * Read the PID from a PID file\n * Returns null if the file doesn't exist or is invalid\n */\nexport function readPid(name: string, pidDir: string): number | null {\n const pidFile = getPidFilePath(name, pidDir);\n\n if (!existsSync(pidFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(pidFile, 'utf8').trim();\n const pid = parseInt(content, 10);\n\n if (isNaN(pid) || pid <= 0) {\n return null;\n }\n\n return pid;\n } catch {\n return null;\n }\n}\n\n/**\n * Write a PID to a PID file\n * Creates the directory if it doesn't exist\n */\nexport function writePid(name: string, pid: number, pidDir: string): void {\n const pidFile = getPidFilePath(name, pidDir);\n const dir = dirname(pidFile);\n\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n writeFileSync(pidFile, String(pid), 'utf8');\n}\n\n/**\n * Delete a PID file\n * Returns true if the file was deleted, false if it didn't exist\n */\nexport function deletePid(name: string, pidDir: string): boolean {\n const pidFile = getPidFilePath(name, pidDir);\n\n if (!existsSync(pidFile)) {\n return false;\n }\n\n try {\n unlinkSync(pidFile);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Get the modification time of a PID file as Unix timestamp (milliseconds)\n * Returns null if file doesn't exist\n */\nexport function getPidFileMtime(name: string, pidDir: string): number | null {\n const pidFile = getPidFilePath(name, pidDir);\n try {\n const stats = statSync(pidFile);\n return stats.mtimeMs;\n } catch {\n return null;\n }\n}\n\n/**\n * List all PID files in the directory\n * Returns information about each tracked process\n */\nexport function listPids(pidDir: string): PidInfo[] {\n if (!existsSync(pidDir)) {\n return [];\n }\n\n const files = readdirSync(pidDir);\n const pidFiles = files.filter(f => f.endsWith('.pid'));\n\n return pidFiles.map(file => {\n // Remove .pid suffix (use slice to only remove from end)\n const name = file.slice(0, -4);\n const pid = readPid(name, pidDir);\n\n return {\n name,\n pid: pid ?? 0,\n exists: pid !== null,\n };\n });\n}\n","/**\n * Cross-platform process handling for just-one\n */\n\nimport { spawn, execSync, ChildProcess, type StdioOptions } from 'child_process';\nimport { openSync, closeSync } from 'fs';\nimport pidusage from 'pidusage';\n\nconst isWindows = process.platform === 'win32';\n\n// Constants for process termination\nconst DEFAULT_GRACE_PERIOD_MS = 5000; // How long to wait after SIGTERM before escalating\nconst FORCE_KILL_WAIT_MS = 2000; // How long to wait after SIGKILL for process to die\nconst CHECK_INTERVAL_MS = 100;\n\n/**\n * Validate that a PID is a safe positive integer for use in system calls\n */\nexport function isValidPid(pid: number): boolean {\n return Number.isInteger(pid) && pid > 0 && pid <= 4194304; // Max PID on most systems\n}\n\n// Tolerance for comparing PID file mtime with process start time\nconst START_TIME_TOLERANCE_MS = 5000; // 5 seconds\n\n/**\n * Get the start time of a process as Unix timestamp (milliseconds)\n * Returns null if process doesn't exist or start time can't be determined\n */\nexport async function getProcessStartTime(pid: number): Promise<number | null> {\n if (!isValidPid(pid)) {\n return null;\n }\n\n try {\n const stats = await pidusage(pid);\n // Calculate start time from current timestamp minus elapsed time\n return stats.timestamp - stats.elapsed;\n } catch {\n return null; // Process doesn't exist or can't get stats\n }\n}\n\n/**\n * Check if a running process is the same instance we originally spawned.\n * Compares process start time with PID file modification time.\n *\n * Returns true if:\n * - Process exists AND start time is within tolerance of pidFileMtime\n *\n * Returns false if:\n * - Process doesn't exist\n * - Can't determine process start time\n * - Start time doesn't match (likely PID reuse)\n */\nexport async function isSameProcessInstance(pid: number, pidFileMtimeMs: number): Promise<boolean> {\n const processStartTime = await getProcessStartTime(pid);\n if (processStartTime === null) {\n return false;\n }\n\n const diff = Math.abs(processStartTime - pidFileMtimeMs);\n return diff <= START_TIME_TOLERANCE_MS;\n}\n\n/**\n * Check if a process with the given PID is still running\n */\nexport function isProcessAlive(pid: number): boolean {\n try {\n if (!isValidPid(pid)) {\n return false;\n }\n if (isWindows) {\n // Windows: tasklist returns exit code 0 if process found\n // PID is validated as a safe integer above before interpolation\n const output = execSync(`tasklist /FI \"PID eq ${pid}\" /NH`, {\n encoding: 'utf8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return output.includes(String(pid));\n } else {\n // Unix/Mac: kill -0 checks if process exists without killing it\n process.kill(pid, 0);\n return true;\n }\n } catch {\n return false;\n }\n}\n\n/**\n * Kill a process by PID\n * Returns true if the process was killed, false if it wasn't running\n */\nexport function killProcess(pid: number): boolean {\n if (!isValidPid(pid) || !isProcessAlive(pid)) {\n return false;\n }\n\n try {\n if (isWindows) {\n // Windows: taskkill with /T kills the process tree, /F forces\n // PID is validated as a safe integer above before interpolation\n execSync(`taskkill /PID ${pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } else {\n // Unix: try to kill process group first (catches child processes),\n // fall back to killing just the process if group kill fails\n const killed = tryKillUnix(-pid) || tryKillUnix(pid);\n if (!killed) {\n return false;\n }\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Helper to attempt Unix kill with error handling\n */\nfunction tryKillUnix(pid: number): boolean {\n try {\n process.kill(pid, 'SIGTERM');\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Wait for a process to die, with timeout\n * @param pid - Process ID to wait for\n * @param timeoutMs - Maximum time to wait (default: 5000ms)\n */\nexport async function waitForProcessToDie(\n pid: number,\n timeoutMs: number = DEFAULT_GRACE_PERIOD_MS\n): Promise<boolean> {\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (!isProcessAlive(pid)) {\n return true;\n }\n await new Promise(resolve => setTimeout(resolve, CHECK_INTERVAL_MS));\n }\n\n return !isProcessAlive(pid);\n}\n\n/**\n * Force kill a process by PID using SIGKILL (Unix) or taskkill /F (Windows).\n * This is a last resort after SIGTERM fails.\n */\nexport function forceKillProcess(pid: number): boolean {\n if (!isValidPid(pid) || !isProcessAlive(pid)) {\n return false;\n }\n\n try {\n if (isWindows) {\n // PID is validated as a safe integer above before interpolation\n execSync(`taskkill /PID ${pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } else {\n // Try process group first, then individual PID\n let killed = false;\n try {\n process.kill(-pid, 'SIGKILL');\n killed = true;\n } catch {\n /* group kill may fail */\n }\n try {\n process.kill(pid, 'SIGKILL');\n killed = true;\n } catch {\n /* individual kill may fail */\n }\n if (!killed) return false;\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Terminate a process with graceful shutdown and SIGKILL escalation.\n *\n * Flow: SIGTERM → wait grace period → SIGKILL → wait 2s → give up\n *\n * @param pid - Process ID to terminate\n * @param gracePeriodMs - How long to wait after SIGTERM before escalating (default: 5000ms)\n * @returns true if process is dead, false if it could not be killed\n */\nexport async function terminateProcess(pid: number, gracePeriodMs?: number): Promise<boolean> {\n const grace = gracePeriodMs ?? DEFAULT_GRACE_PERIOD_MS;\n\n if (!isValidPid(pid)) {\n return false;\n }\n\n // Already dead? Nothing to do.\n if (!isProcessAlive(pid)) {\n return true;\n }\n\n // Step 1: Send SIGTERM (or taskkill /F on Windows)\n killProcess(pid);\n\n // Step 2: Wait for graceful shutdown\n const died = await waitForProcessToDie(pid, grace);\n if (died) {\n return true;\n }\n\n // Step 3: Escalate to SIGKILL (Unix) / re-attempt taskkill (Windows)\n forceKillProcess(pid);\n return await waitForProcessToDie(pid, FORCE_KILL_WAIT_MS);\n}\n\nexport interface SpawnResult {\n child: ChildProcess;\n pid: number;\n}\n\n/**\n * Spawn a command with stdio forwarding\n */\nexport function spawnCommand(command: string, args: string[]): SpawnResult {\n const child = spawn(command, args, {\n stdio: 'inherit',\n shell: isWindows,\n detached: !isWindows,\n });\n\n if (child.pid === undefined) {\n throw new Error('Failed to spawn process');\n }\n\n return {\n child,\n pid: child.pid,\n };\n}\n\n/**\n * Spawn a command in daemon mode (detached, with output captured to log file).\n * The parent process does not wait for the child — it calls child.unref().\n */\nexport function spawnCommandDaemon(\n command: string,\n args: string[],\n logFilePath: string\n): SpawnResult {\n const logFd = openSync(logFilePath, 'a');\n\n try {\n const stdio: StdioOptions = ['ignore', logFd, logFd];\n\n const child = spawn(command, args, {\n stdio,\n // Don't use shell on Windows for daemon mode. With shell: true, Node spawns\n // cmd.exe which doesn't reliably pass fd-based stdio to grandchild processes\n // when combined with detached: true (known Node.js issue on Windows).\n // CreateProcess still searches PATH, so executables are found without a shell.\n detached: true,\n });\n\n if (child.pid === undefined) {\n throw new Error('Failed to spawn daemon process');\n }\n\n child.unref();\n\n return {\n child,\n pid: child.pid,\n };\n } finally {\n closeSync(logFd);\n }\n}\n\n// Grace period for Windows child process to exit before force-killing\nconst WINDOWS_GRACEFUL_TIMEOUT_MS = 2000;\n\n/**\n * Set up signal handlers to forward signals to child process\n *\n * Unix: forwards SIGTERM to child for graceful shutdown.\n *\n * Windows: the child shares the console (stdio: 'inherit'), so when the user\n * presses Ctrl+C, Windows delivers CTRL_C_EVENT to the child directly — no\n * forwarding needed. We just set a force-kill timeout as a safety net in case\n * the child doesn't exit on its own. process.kill(pid, 'SIGINT') on Windows\n * calls TerminateProcess (not GenerateConsoleCtrlEvent), so we intentionally\n * avoid calling it to give the child time to handle the OS-delivered signal.\n */\nexport function setupSignalHandlers(child: ChildProcess, onExit?: () => void): void {\n let forceKillTimer: ReturnType<typeof setTimeout> | null = null;\n\n const forceKillWindows = () => {\n if (child.pid && isValidPid(child.pid) && isProcessAlive(child.pid)) {\n try {\n execSync(`taskkill /PID ${child.pid} /T /F`, {\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch {\n // Process might already be dead\n }\n }\n };\n\n const handleSignal = (_signal: NodeJS.Signals) => {\n if (child.pid && isValidPid(child.pid)) {\n if (isWindows) {\n // On Windows, the child already received CTRL_C_EVENT from the OS\n // (since it shares our console via stdio: 'inherit').\n // Don't call process.kill() — it uses TerminateProcess which would\n // prevent the child from running its cleanup handlers.\n // Just set a force-kill timeout as a safety net.\n if (forceKillTimer === null) {\n forceKillTimer = setTimeout(forceKillWindows, WINDOWS_GRACEFUL_TIMEOUT_MS);\n forceKillTimer.unref();\n }\n } else {\n // Forward as SIGTERM for graceful shutdown\n child.kill('SIGTERM');\n }\n }\n };\n\n // Forward both SIGINT (Ctrl+C) and SIGTERM to child\n process.on('SIGINT', () => handleSignal('SIGINT'));\n process.on('SIGTERM', () => handleSignal('SIGTERM'));\n\n child.on('exit', (code, signal) => {\n // Child exited gracefully — cancel the force-kill timer if pending\n if (forceKillTimer !== null) {\n clearTimeout(forceKillTimer);\n forceKillTimer = null;\n }\n if (onExit) {\n onExit();\n }\n if (signal) {\n process.exit(128 + (signal === 'SIGTERM' ? 15 : signal === 'SIGINT' ? 2 : 1));\n }\n process.exit(code ?? 0);\n });\n\n child.on('error', err => {\n console.error(`Failed to start process: ${err.message}`);\n process.exit(1);\n });\n}\n","/**\n * Log file operations for just-one daemon mode\n */\n\nimport {\n existsSync,\n statSync,\n renameSync,\n unlinkSync,\n readFileSync,\n openSync,\n readSync,\n closeSync,\n} from 'fs';\nimport { join } from 'path';\n\nconst DEFAULT_MAX_LOG_SIZE = 10 * 1024 * 1024; // 10MB\n\n/**\n * Get the path to a log file for a given name\n */\nexport function getLogFilePath(name: string, pidDir: string): string {\n return join(pidDir, `${name}.log`);\n}\n\n/**\n * Get the path to a backup log file for a given name\n */\nexport function getBackupLogFilePath(name: string, pidDir: string): string {\n return join(pidDir, `${name}.log.1`);\n}\n\n/**\n * Get the size of a log file in bytes\n * Returns 0 if the file doesn't exist\n */\nexport function getLogFileSize(name: string, pidDir: string): number {\n const logPath = getLogFilePath(name, pidDir);\n try {\n const stats = statSync(logPath);\n return stats.size;\n } catch {\n return 0;\n }\n}\n\n/**\n * Rotate the log file if it exceeds the maximum size.\n * Renames current .log to .log.1 (overwriting any existing backup).\n * Returns true if rotation occurred.\n */\nexport function rotateLogIfNeeded(\n name: string,\n pidDir: string,\n maxSize: number = DEFAULT_MAX_LOG_SIZE\n): boolean {\n const size = getLogFileSize(name, pidDir);\n if (size <= maxSize) {\n return false;\n }\n\n const logPath = getLogFilePath(name, pidDir);\n const backupPath = getBackupLogFilePath(name, pidDir);\n\n // Remove existing backup if present\n try {\n if (existsSync(backupPath)) {\n unlinkSync(backupPath);\n }\n } catch {\n // Ignore errors removing old backup\n }\n\n renameSync(logPath, backupPath);\n return true;\n}\n\n/**\n * Read lines from a log file.\n * If lastN is provided, returns only the last N lines.\n * Returns empty array if the file doesn't exist.\n */\nexport function readLogLines(name: string, pidDir: string, lastN?: number): string[] {\n const logPath = getLogFilePath(name, pidDir);\n\n if (!existsSync(logPath)) {\n return [];\n }\n\n try {\n const content = readFileSync(logPath, 'utf8');\n if (content.length === 0) {\n return [];\n }\n\n const lines = content.split('\\n');\n // Remove trailing empty line from final newline\n if (lines.length > 0 && lines[lines.length - 1] === '') {\n lines.pop();\n }\n\n if (lastN === undefined) {\n return lines;\n }\n if (lastN === 0) {\n return [];\n }\n return lines.slice(-lastN);\n } catch {\n return [];\n }\n}\n\nexport interface TailOptions {\n onLine: (line: string) => void;\n onError?: (err: Error) => void;\n initialLines?: number;\n pollIntervalMs?: number;\n}\n\nexport interface TailHandle {\n stop: () => void;\n}\n\n/**\n * Follow a log file in real-time using setInterval polling.\n * Reads from last byte offset on each change, calling onLine for each new line.\n * Optionally emits the last N lines of existing content first.\n */\nexport function tailLogFile(name: string, pidDir: string, options: TailOptions): TailHandle {\n const logPath = getLogFilePath(name, pidDir);\n const pollIntervalMs = options.pollIntervalMs ?? 500;\n\n // Emit initial lines if requested\n if (options.initialLines !== undefined && options.initialLines > 0) {\n const initial = readLogLines(name, pidDir, options.initialLines);\n for (const line of initial) {\n options.onLine(line);\n }\n }\n\n // Track current file offset\n let offset = 0;\n try {\n if (existsSync(logPath)) {\n offset = statSync(logPath).size;\n }\n } catch {\n // File may not exist yet, start from 0\n }\n\n let partialLine = '';\n\n const checkForChanges = () => {\n let newSize: number;\n try {\n if (!existsSync(logPath)) return;\n newSize = statSync(logPath).size;\n } catch {\n return;\n }\n\n if (newSize <= offset) {\n // File was truncated or unchanged — reset to new size\n offset = newSize;\n return;\n }\n\n const bytesToRead = newSize - offset;\n try {\n const fd = openSync(logPath, 'r');\n try {\n const buf = Buffer.alloc(bytesToRead);\n readSync(fd, buf, 0, bytesToRead, offset);\n offset = newSize;\n\n const chunk = buf.toString('utf8');\n const parts = chunk.split('\\n');\n\n // Prepend any partial line from last read\n if (parts.length > 0) {\n parts[0] = partialLine + parts[0]!;\n partialLine = '';\n }\n\n // Last element is either empty (if chunk ended with \\n) or a partial line\n const lastPart = parts.pop();\n if (lastPart !== undefined && lastPart !== '') {\n partialLine = lastPart;\n }\n\n for (const line of parts) {\n options.onLine(line);\n }\n } finally {\n closeSync(fd);\n }\n } catch (err) {\n if (options.onError && err instanceof Error) {\n options.onError(err);\n }\n }\n };\n\n const intervalId = setInterval(checkForChanges, pollIntervalMs);\n\n return {\n stop: () => {\n clearInterval(intervalId);\n },\n };\n}\n\n/**\n * Delete log files (.log and .log.1) for a given name.\n * Silently ignores missing files.\n */\nexport function deleteLogFiles(name: string, pidDir: string): void {\n const logPath = getLogFilePath(name, pidDir);\n const backupPath = getBackupLogFilePath(name, pidDir);\n\n try {\n if (existsSync(logPath)) {\n unlinkSync(logPath);\n }\n } catch {\n // Ignore\n }\n\n try {\n if (existsSync(backupPath)) {\n unlinkSync(backupPath);\n }\n } catch {\n // Ignore\n }\n}\n"],"mappings":"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","names":["existsSync","existsSync","statSync","unlinkSync","readFileSync","openSync","closeSync","join","require","pid","existsSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radleta/just-one",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "A CLI tool that ensures only one instance of a command runs at a time",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "engines": {
19
- "node": ">=18.0.0"
19
+ "node": ">=20.0.0"
20
20
  },
21
21
  "scripts": {
22
22
  "dev": "tsup --watch",