start-command 0.3.1 → 0.5.1
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 +51 -0
- package/README.md +8 -0
- package/experiments/debug-regex.js +1 -1
- package/experiments/test-screen-output.sh +27 -0
- package/experiments/test-substitution.js +1 -1
- package/package.json +1 -1
- package/scripts/changeset-version.mjs +1 -1
- package/scripts/check-file-size.mjs +1 -1
- package/scripts/create-github-release.mjs +1 -1
- package/scripts/create-manual-changeset.mjs +1 -1
- package/scripts/format-github-release.mjs +1 -1
- package/scripts/format-release-notes.mjs +1 -1
- package/scripts/instant-version-bump.mjs +1 -1
- package/scripts/publish-to-npm.mjs +1 -1
- package/scripts/setup-npm.mjs +1 -1
- package/scripts/validate-changeset.mjs +1 -1
- package/scripts/version-and-commit.mjs +1 -1
- package/src/bin/cli.js +58 -19
- package/src/lib/isolation.js +106 -0
- package/test/args-parser.test.js +1 -1
- package/test/isolation.test.js +1 -1
- package/test/substitution.test.js +1 -1
- package/.changeset/isolation-support.md +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# start-command
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Test patch release
|
|
8
|
+
|
|
9
|
+
## 0.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 95d8760: Unify output experience for isolation mode
|
|
14
|
+
- Change terminology from "Backend" to "Environment" in isolation output
|
|
15
|
+
- Add unified logging with timestamps for isolation modes (screen, tmux, docker, zellij)
|
|
16
|
+
- Save log files for all execution modes with consistent format
|
|
17
|
+
- Display start/end timestamps, exit code, and log file path uniformly across all modes
|
|
18
|
+
|
|
19
|
+
## 0.4.1
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 73635f9: Make it bun first - update shebangs and installation docs
|
|
24
|
+
|
|
25
|
+
## 0.4.0
|
|
26
|
+
|
|
27
|
+
### Minor Changes
|
|
28
|
+
|
|
29
|
+
- e8bec3c: Add process isolation support with --isolated option
|
|
30
|
+
|
|
31
|
+
This release adds the ability to run commands in isolated environments:
|
|
32
|
+
|
|
33
|
+
**New Features:**
|
|
34
|
+
- `--isolated` / `-i` option to run commands in screen, tmux, zellij, or docker
|
|
35
|
+
- `--attached` / `-a` and `--detached` / `-d` modes for foreground/background execution
|
|
36
|
+
- `--session` / `-s` option for custom session names
|
|
37
|
+
- `--image` option for Docker container image specification
|
|
38
|
+
- Two command syntax patterns: `$ [options] -- [command]` or `$ [options] command`
|
|
39
|
+
|
|
40
|
+
**Supported Backends:**
|
|
41
|
+
- GNU Screen - classic terminal multiplexer
|
|
42
|
+
- tmux - modern terminal multiplexer
|
|
43
|
+
- zellij - modern terminal workspace
|
|
44
|
+
- Docker - container isolation
|
|
45
|
+
|
|
46
|
+
**Examples:**
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
$ --isolated tmux -- npm start
|
|
50
|
+
$ -i screen -d npm start
|
|
51
|
+
$ --isolated docker --image node:20 -- npm install
|
|
52
|
+
```
|
|
53
|
+
|
|
3
54
|
## 0.3.1
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -4,6 +4,14 @@ Gamification of coding - execute any command with automatic logging and ability
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
+
We recommend using [Bun](https://bun.sh) for the best performance:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun install -g start-command
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or using npm:
|
|
14
|
+
|
|
7
15
|
```bash
|
|
8
16
|
npm install -g start-command
|
|
9
17
|
```
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Experiment to understand screen output behavior
|
|
3
|
+
|
|
4
|
+
echo "=== Test 1: Direct screen with command ==="
|
|
5
|
+
screen -S test1 /bin/sh -c 'echo "hello from test1"'
|
|
6
|
+
|
|
7
|
+
echo ""
|
|
8
|
+
echo "=== Test 2: Screen with -L logging ==="
|
|
9
|
+
cd /tmp
|
|
10
|
+
screen -L -Logfile screen-test.log -S test2 /bin/sh -c 'echo "hello from test2"'
|
|
11
|
+
echo "Log contents:"
|
|
12
|
+
cat /tmp/screen-test.log 2>/dev/null || echo "No log file created"
|
|
13
|
+
|
|
14
|
+
echo ""
|
|
15
|
+
echo "=== Test 3: Screen detached then capture ==="
|
|
16
|
+
screen -dmS test3 /bin/sh -c 'echo "hello from test3" > /tmp/screen-test3-out.txt'
|
|
17
|
+
sleep 0.5
|
|
18
|
+
echo "Output from test3:"
|
|
19
|
+
cat /tmp/screen-test3-out.txt 2>/dev/null || echo "No output file"
|
|
20
|
+
|
|
21
|
+
echo ""
|
|
22
|
+
echo "=== Test 4: Screen with wrap using script command ==="
|
|
23
|
+
script -q /dev/null -c 'screen -S test4 /bin/sh -c "echo hello from test4"' 2>/dev/null || echo "Script method failed"
|
|
24
|
+
|
|
25
|
+
echo ""
|
|
26
|
+
echo "Cleanup"
|
|
27
|
+
rm -f /tmp/screen-test.log /tmp/screen-test3-out.txt
|
package/package.json
CHANGED
package/scripts/setup-npm.mjs
CHANGED
package/src/bin/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
const { spawn, execSync } = require('child_process');
|
|
4
4
|
const process = require('process');
|
|
@@ -13,7 +13,14 @@ const {
|
|
|
13
13
|
hasIsolation,
|
|
14
14
|
getEffectiveMode,
|
|
15
15
|
} = require('../lib/args-parser');
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
runIsolated,
|
|
18
|
+
getTimestamp,
|
|
19
|
+
createLogHeader,
|
|
20
|
+
createLogFooter,
|
|
21
|
+
writeLogFile,
|
|
22
|
+
createLogPath,
|
|
23
|
+
} = require('../lib/isolation');
|
|
17
24
|
|
|
18
25
|
// Configuration from environment variables
|
|
19
26
|
const config = {
|
|
@@ -55,7 +62,7 @@ function printUsage() {
|
|
|
55
62
|
console.log('');
|
|
56
63
|
console.log('Options:');
|
|
57
64
|
console.log(
|
|
58
|
-
' --isolated, -i <
|
|
65
|
+
' --isolated, -i <environment> Run in isolated environment (screen, tmux, docker, zellij)'
|
|
59
66
|
);
|
|
60
67
|
console.log(' --attached, -a Run in attached mode (foreground)');
|
|
61
68
|
console.log(' --detached, -d Run in detached mode (background)');
|
|
@@ -143,36 +150,70 @@ if (!config.disableSubstitutions) {
|
|
|
143
150
|
* @param {string} cmd - Command to execute
|
|
144
151
|
*/
|
|
145
152
|
async function runWithIsolation(options, cmd) {
|
|
146
|
-
const
|
|
153
|
+
const environment = options.isolated;
|
|
147
154
|
const mode = getEffectiveMode(options);
|
|
155
|
+
const startTime = getTimestamp();
|
|
156
|
+
|
|
157
|
+
// Create log file path
|
|
158
|
+
const logFilePath = createLogPath(environment);
|
|
159
|
+
|
|
160
|
+
// Get session name (will be generated by runIsolated if not provided)
|
|
161
|
+
const sessionName =
|
|
162
|
+
options.session ||
|
|
163
|
+
`${environment}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
164
|
+
|
|
165
|
+
// Print start message (unified format)
|
|
166
|
+
console.log(`[${startTime}] Starting: ${cmd}`);
|
|
167
|
+
console.log('');
|
|
148
168
|
|
|
149
169
|
// Log isolation info
|
|
150
|
-
console.log(`[Isolation]
|
|
170
|
+
console.log(`[Isolation] Environment: ${environment}, Mode: ${mode}`);
|
|
151
171
|
if (options.session) {
|
|
152
172
|
console.log(`[Isolation] Session: ${options.session}`);
|
|
153
173
|
}
|
|
154
174
|
if (options.image) {
|
|
155
175
|
console.log(`[Isolation] Image: ${options.image}`);
|
|
156
176
|
}
|
|
157
|
-
console.log(`[Isolation] Command: ${cmd}`);
|
|
158
177
|
console.log('');
|
|
159
178
|
|
|
179
|
+
// Create log content
|
|
180
|
+
let logContent = createLogHeader({
|
|
181
|
+
command: cmd,
|
|
182
|
+
environment,
|
|
183
|
+
mode,
|
|
184
|
+
sessionName,
|
|
185
|
+
image: options.image,
|
|
186
|
+
startTime,
|
|
187
|
+
});
|
|
188
|
+
|
|
160
189
|
// Run in isolation
|
|
161
|
-
const result = await runIsolated(
|
|
190
|
+
const result = await runIsolated(environment, cmd, {
|
|
162
191
|
session: options.session,
|
|
163
192
|
image: options.image,
|
|
164
193
|
detached: mode === 'detached',
|
|
165
194
|
});
|
|
166
195
|
|
|
167
|
-
//
|
|
196
|
+
// Get exit code
|
|
197
|
+
const exitCode =
|
|
198
|
+
result.exitCode !== undefined ? result.exitCode : result.success ? 0 : 1;
|
|
199
|
+
const endTime = getTimestamp();
|
|
200
|
+
|
|
201
|
+
// Add result to log content
|
|
202
|
+
logContent += `${result.message}\n`;
|
|
203
|
+
logContent += createLogFooter(endTime, exitCode);
|
|
204
|
+
|
|
205
|
+
// Write log file
|
|
206
|
+
writeLogFile(logFilePath, logContent);
|
|
207
|
+
|
|
208
|
+
// Print result and footer (unified format)
|
|
168
209
|
console.log('');
|
|
169
210
|
console.log(result.message);
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(`[${endTime}] Finished`);
|
|
213
|
+
console.log(`Exit code: ${exitCode}`);
|
|
214
|
+
console.log(`Log saved: ${logFilePath}`);
|
|
170
215
|
|
|
171
|
-
|
|
172
|
-
process.exit(result.exitCode || 0);
|
|
173
|
-
} else {
|
|
174
|
-
process.exit(1);
|
|
175
|
-
}
|
|
216
|
+
process.exit(exitCode);
|
|
176
217
|
}
|
|
177
218
|
|
|
178
219
|
/**
|
|
@@ -301,12 +342,10 @@ function runDirect(cmd) {
|
|
|
301
342
|
});
|
|
302
343
|
}
|
|
303
344
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
// Generate unique log filename
|
|
345
|
+
/**
|
|
346
|
+
* Generate unique log filename for direct execution
|
|
347
|
+
* @returns {string} Log filename
|
|
348
|
+
*/
|
|
310
349
|
function generateLogFilename() {
|
|
311
350
|
const timestamp = Date.now();
|
|
312
351
|
const random = Math.random().toString(36).substring(2, 8);
|
package/src/lib/isolation.js
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const { execSync, spawn } = require('child_process');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
12
15
|
const { generateSessionName } = require('./args-parser');
|
|
13
16
|
|
|
14
17
|
// Debug mode from environment
|
|
@@ -409,6 +412,101 @@ function runIsolated(backend, command, options = {}) {
|
|
|
409
412
|
}
|
|
410
413
|
}
|
|
411
414
|
|
|
415
|
+
/**
|
|
416
|
+
* Generate timestamp for logging
|
|
417
|
+
* @returns {string} ISO timestamp without 'T' and 'Z'
|
|
418
|
+
*/
|
|
419
|
+
function getTimestamp() {
|
|
420
|
+
return new Date().toISOString().replace('T', ' ').replace('Z', '');
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Generate unique log filename
|
|
425
|
+
* @param {string} environment - The isolation environment name
|
|
426
|
+
* @returns {string} Log filename
|
|
427
|
+
*/
|
|
428
|
+
function generateLogFilename(environment) {
|
|
429
|
+
const timestamp = Date.now();
|
|
430
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
431
|
+
return `start-command-${environment}-${timestamp}-${random}.log`;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Create log content header
|
|
436
|
+
* @param {object} params - Log parameters
|
|
437
|
+
* @param {string} params.command - The command being executed
|
|
438
|
+
* @param {string} params.environment - The isolation environment
|
|
439
|
+
* @param {string} params.mode - attached or detached
|
|
440
|
+
* @param {string} params.sessionName - Session/container name
|
|
441
|
+
* @param {string} [params.image] - Docker image (for docker environment)
|
|
442
|
+
* @param {string} params.startTime - Start timestamp
|
|
443
|
+
* @returns {string} Log header content
|
|
444
|
+
*/
|
|
445
|
+
function createLogHeader(params) {
|
|
446
|
+
let content = `=== Start Command Log ===\n`;
|
|
447
|
+
content += `Timestamp: ${params.startTime}\n`;
|
|
448
|
+
content += `Command: ${params.command}\n`;
|
|
449
|
+
content += `Environment: ${params.environment}\n`;
|
|
450
|
+
content += `Mode: ${params.mode}\n`;
|
|
451
|
+
content += `Session: ${params.sessionName}\n`;
|
|
452
|
+
if (params.image) {
|
|
453
|
+
content += `Image: ${params.image}\n`;
|
|
454
|
+
}
|
|
455
|
+
content += `Platform: ${process.platform}\n`;
|
|
456
|
+
content += `Node Version: ${process.version}\n`;
|
|
457
|
+
content += `Working Directory: ${process.cwd()}\n`;
|
|
458
|
+
content += `${'='.repeat(50)}\n\n`;
|
|
459
|
+
return content;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Create log content footer
|
|
464
|
+
* @param {string} endTime - End timestamp
|
|
465
|
+
* @param {number} exitCode - Exit code
|
|
466
|
+
* @returns {string} Log footer content
|
|
467
|
+
*/
|
|
468
|
+
function createLogFooter(endTime, exitCode) {
|
|
469
|
+
let content = `\n${'='.repeat(50)}\n`;
|
|
470
|
+
content += `Finished: ${endTime}\n`;
|
|
471
|
+
content += `Exit Code: ${exitCode}\n`;
|
|
472
|
+
return content;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Write log file
|
|
477
|
+
* @param {string} logPath - Path to log file
|
|
478
|
+
* @param {string} content - Log content
|
|
479
|
+
* @returns {boolean} Success status
|
|
480
|
+
*/
|
|
481
|
+
function writeLogFile(logPath, content) {
|
|
482
|
+
try {
|
|
483
|
+
fs.writeFileSync(logPath, content, 'utf8');
|
|
484
|
+
return true;
|
|
485
|
+
} catch (err) {
|
|
486
|
+
console.error(`\nWarning: Could not save log file: ${err.message}`);
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Get log directory from environment or use system temp
|
|
493
|
+
* @returns {string} Log directory path
|
|
494
|
+
*/
|
|
495
|
+
function getLogDir() {
|
|
496
|
+
return process.env.START_LOG_DIR || os.tmpdir();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Create log file path
|
|
501
|
+
* @param {string} environment - The isolation environment
|
|
502
|
+
* @returns {string} Full path to log file
|
|
503
|
+
*/
|
|
504
|
+
function createLogPath(environment) {
|
|
505
|
+
const logDir = getLogDir();
|
|
506
|
+
const logFilename = generateLogFilename(environment);
|
|
507
|
+
return path.join(logDir, logFilename);
|
|
508
|
+
}
|
|
509
|
+
|
|
412
510
|
module.exports = {
|
|
413
511
|
isCommandAvailable,
|
|
414
512
|
runInScreen,
|
|
@@ -416,4 +514,12 @@ module.exports = {
|
|
|
416
514
|
runInZellij,
|
|
417
515
|
runInDocker,
|
|
418
516
|
runIsolated,
|
|
517
|
+
// Export logging utilities for unified experience
|
|
518
|
+
getTimestamp,
|
|
519
|
+
generateLogFilename,
|
|
520
|
+
createLogHeader,
|
|
521
|
+
createLogFooter,
|
|
522
|
+
writeLogFile,
|
|
523
|
+
getLogDir,
|
|
524
|
+
createLogPath,
|
|
419
525
|
};
|
package/test/args-parser.test.js
CHANGED
package/test/isolation.test.js
CHANGED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
'start-command': minor
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
Add process isolation support with --isolated option
|
|
6
|
-
|
|
7
|
-
This release adds the ability to run commands in isolated environments:
|
|
8
|
-
|
|
9
|
-
**New Features:**
|
|
10
|
-
|
|
11
|
-
- `--isolated` / `-i` option to run commands in screen, tmux, zellij, or docker
|
|
12
|
-
- `--attached` / `-a` and `--detached` / `-d` modes for foreground/background execution
|
|
13
|
-
- `--session` / `-s` option for custom session names
|
|
14
|
-
- `--image` option for Docker container image specification
|
|
15
|
-
- Two command syntax patterns: `$ [options] -- [command]` or `$ [options] command`
|
|
16
|
-
|
|
17
|
-
**Supported Backends:**
|
|
18
|
-
|
|
19
|
-
- GNU Screen - classic terminal multiplexer
|
|
20
|
-
- tmux - modern terminal multiplexer
|
|
21
|
-
- zellij - modern terminal workspace
|
|
22
|
-
- Docker - container isolation
|
|
23
|
-
|
|
24
|
-
**Examples:**
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
$ --isolated tmux -- npm start
|
|
28
|
-
$ -i screen -d npm start
|
|
29
|
-
$ --isolated docker --image node:20 -- npm install
|
|
30
|
-
```
|