@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 +18 -0
- package/README.md +62 -24
- package/dist/index.js +397 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
-
| `--
|
|
127
|
-
| `--
|
|
128
|
-
| `--
|
|
129
|
-
| `--
|
|
130
|
-
| `--
|
|
131
|
-
| `--
|
|
132
|
-
| `--
|
|
133
|
-
| `--
|
|
134
|
-
| `--
|
|
135
|
-
| `--
|
|
136
|
-
| `--
|
|
137
|
-
| `--
|
|
138
|
-
| `--
|
|
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 -
|
|
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,
|
|
164
|
-
4.
|
|
165
|
-
5.
|
|
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
|
|
246
|
+
| Daemon mode | Optional | No | Yes |
|
|
247
|
+
| Log capture | Yes | No | Yes |
|
|
210
248
|
|
|
211
249
|
## Requirements
|
|
212
250
|
|
|
213
|
-
- Node.js >=
|
|
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
|
-
--
|
|
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 {
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
545
|
-
|
|
546
|
-
|
|
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
|
-
|
|
587
|
-
await
|
|
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
|
|
649
|
-
|
|
650
|
-
|
|
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":"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","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.
|
|
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": ">=
|
|
19
|
+
"node": ">=20.0.0"
|
|
20
20
|
},
|
|
21
21
|
"scripts": {
|
|
22
22
|
"dev": "tsup --watch",
|