sh3-server 0.6.0 → 0.7.5

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.
@@ -80,6 +80,13 @@ export class ShellSession {
80
80
  // Claim the start slot synchronously so a concurrent submit during
81
81
  // the `await runner.start()` below cannot slip past the lock check.
82
82
  this.starting = true;
83
+ // Track whether the runner already exited before we latched `running`.
84
+ // Sync-failure paths (empty cmd, tokenize throw, Node 24's EINVAL for
85
+ // .cmd/.bat on Windows) can fire onExit via setImmediate BEFORE the
86
+ // await continuation runs — so we must not assign `this.running = handle`
87
+ // for a process that's already dead, or subsequent submits get wedged
88
+ // by the running-guard above. See runner.ts for the full race writeup.
89
+ let exited = false;
83
90
  try {
84
91
  this.history.append(line);
85
92
  // Emit a prompt event so every attached client echoes the line
@@ -94,24 +101,33 @@ export class ShellSession {
94
101
  cmd: line,
95
102
  cwd: this.cwd,
96
103
  env: this.env,
104
+ // Callback listeners — attached synchronously inside start() BEFORE
105
+ // any emit can fire. Do NOT switch back to `handle.events.on(...)`
106
+ // after await: that re-opens the race the callbacks exist to close.
107
+ onStdout: (data) => {
108
+ this.broadcast({ kind: 'stdout', data, ts: Date.now(), seq: 0 });
109
+ },
110
+ onStderr: (data) => {
111
+ this.broadcast({ kind: 'stderr', data, ts: Date.now(), seq: 0 });
112
+ },
113
+ onExit: (info) => {
114
+ this.broadcast({
115
+ kind: 'exit',
116
+ code: info.code,
117
+ signal: info.signal,
118
+ ts: Date.now(),
119
+ seq: 0,
120
+ });
121
+ exited = true;
122
+ this.running = null;
123
+ },
97
124
  });
98
- this.running = handle;
99
- handle.events.on('stdout', (data) => {
100
- this.broadcast({ kind: 'stdout', data, ts: Date.now(), seq: 0 });
101
- });
102
- handle.events.on('stderr', (data) => {
103
- this.broadcast({ kind: 'stderr', data, ts: Date.now(), seq: 0 });
104
- });
105
- handle.events.on('exit', (info) => {
106
- this.broadcast({
107
- kind: 'exit',
108
- code: info.code,
109
- signal: info.signal,
110
- ts: Date.now(),
111
- seq: 0,
112
- });
113
- this.running = null;
114
- });
125
+ // Only latch `running` if the process hasn't already died during the
126
+ // await boundary. Otherwise we'd resurrect a dead handle and wedge
127
+ // every subsequent submit.
128
+ if (!exited) {
129
+ this.running = handle;
130
+ }
115
131
  }
116
132
  finally {
117
133
  this.starting = false;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "sh3-server",
3
- "version": "0.6.0",
3
+ "version": "0.7.5",
4
4
  "type": "module",
5
5
  "bin": {
6
- "sh3-server": "./dist/cli.js"
6
+ "sh3-server": "dist/cli.js"
7
7
  },
8
8
  "exports": {
9
9
  ".": {
@@ -39,7 +39,7 @@
39
39
  "@types/node": "^20.0.0",
40
40
  "esbuild": "^0.21.5",
41
41
  "postject": "^1.0.0-alpha.6",
42
- "sh3-core": "0.6.0",
42
+ "sh3-core": "0.7.0",
43
43
  "svelte": "^5.0.0",
44
44
  "tsx": "^4.0.0",
45
45
  "typescript": "^5.6.0",