ugly-app 0.1.983 → 0.1.984

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.
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"AAmDA,wBAAsB,MAAM,CAC1B,IAAI,GAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAqC,GAC5E,OAAO,CAAC,IAAI,CAAC,CAyRf"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"AAkDA,wBAAsB,MAAM,CAC1B,IAAI,GAAE;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAqC,GAC5E,OAAO,CAAC,IAAI,CAAC,CAkQf"}
package/dist/cli/dev.js CHANGED
@@ -39,7 +39,6 @@ import { readUglyAppConfig } from './uglyappConfig.js';
39
39
  import { lintSummaryAsync } from './lint.js';
40
40
  import { checkDevLock, releaseDevLock, setAllowMultiple, stopHint, writeDevLock, } from './devLock.js';
41
41
  import { backgroundWait } from '../server/backgroundWait.js';
42
- import { killSubtree, startOrphanWatch } from './devReap.js';
43
42
  export async function runDev(opts = { watch: false, verbose: false }) {
44
43
  // ── Set universal dev env vars ─────────────────────────────────────────────
45
44
  if (!process.env['NODE_ENV'])
@@ -225,51 +224,29 @@ export async function runDev(opts = { watch: false, verbose: false }) {
225
224
  console.log(` [${c.name}] ${c.command}`);
226
225
  }
227
226
  }
228
- const { result } = concurrently(finalCommands, {
227
+ const { result, commands: runningCommands } = concurrently(finalCommands, {
229
228
  killOthersOn: ['failure'],
230
229
  handleInput: true,
231
230
  raw: !isWin,
232
231
  });
233
- // ── Take the whole tree down with us ──────────────────────────────────────
234
- // `concurrently` forwards Ctrl-C to the commands it started, and that is all.
235
- // It does not know about minio (spawned by ensureLocalInfra, a direct child
236
- // of ours), about esbuild under tsx, or about tsc under its `sh -c` wrapper —
237
- // and tsx/tsc routinely ignore SIGTERM. So on every exit path, walk our own
238
- // process table subtree and escalate. `killSubtree` never signals us, so this
239
- // is safe to call while we are the one exiting.
240
- //
241
- // Registered on 'exit' rather than only on the orphan path so an ordinary
242
- // crash or `throw` cleans up too. `killSubtree` is fully synchronous for
243
- // exactly this reason.
244
- if (!isWin) {
245
- let reaped = false;
246
- const reapChildren = () => {
247
- if (reaped)
248
- return;
249
- reaped = true;
250
- const forced = killSubtree(process.pid);
251
- if (forced.length > 0 && verbose) {
252
- console.log(`[ugly-app] SIGKILLed ${forced.length} stuck child(ren)`);
232
+ // Self-reap when orphaned. `concurrently` forwards Ctrl-C and cross-kills on
233
+ // failure, but nothing here notices the *launching* process dying: when the
234
+ // terminal / session that started `ugly-app dev` exits, the OS re-parents us
235
+ // to PID 1 and we'd otherwise keep tsx/tsc/minio watching files forever.
236
+ // macOS has no PR_SET_PDEATHSIG, so poll process.ppid and shut our own child
237
+ // watchers down the moment it flips to 1. Only meaningful in watch mode.
238
+ if (opts.watch && !isWin) {
239
+ const bornPpid = process.ppid; // guard: never fire if we started orphaned
240
+ const orphanWatch = setInterval(() => {
241
+ if (bornPpid !== 1 && process.ppid === 1) {
242
+ clearInterval(orphanWatch);
243
+ console.error('[ugly-app] launching process exited — stopping dev watchers');
244
+ for (const c of runningCommands)
245
+ c.kill('SIGTERM');
246
+ setTimeout(() => process.exit(0), 2000).unref();
253
247
  }
254
- };
255
- process.on('exit', reapChildren);
256
- // Self-reap when the session that launched us goes away. macOS has no
257
- // PR_SET_PDEATHSIG, so poll instead — and poll the whole ancestor *chain*,
258
- // not just our ppid. `npx ugly-app dev` runs us under an `npm exec`
259
- // wrapper that outlives the terminal, so our ppid stays valid forever and
260
- // a ppid===1 test never fires (that is how a ugly-bot dev stack and its
261
- // minio stayed up for two days). This runs in non-watch mode too: a plain
262
- // `ugly-app dev` leaks a `tsx server/index.ts` the same way, and two of
263
- // those were found 3 and 5.5 days old.
264
- //
265
- // Set UGLY_APP_NO_ORPHAN_REAP=1 to deliberately outlive your launcher.
266
- if (!process.env['UGLY_APP_NO_ORPHAN_REAP']) {
267
- startOrphanWatch((deadAncestor) => {
268
- console.error(`[ugly-app] launching process (pid ${deadAncestor}) exited — stopping the dev stack`);
269
- reapChildren();
270
- process.exit(0);
271
- });
272
- }
248
+ }, 3000);
249
+ orphanWatch.unref();
273
250
  }
274
251
  try {
275
252
  await result;
@@ -1 +1 @@
1
- {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../src/cli/dev.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.1.983";
1
+ export declare const CLI_VERSION = "0.1.984";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.983";
2
+ export const CLI_VERSION = "0.1.984";
3
3
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConsolePanel.d.ts","sourceRoot":"","sources":["../../../../src/inspect/ui/panels/ConsolePanel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAkBrD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,+BA0GA"}
1
+ {"version":3,"file":"ConsolePanel.d.ts","sourceRoot":"","sources":["../../../../src/inspect/ui/panels/ConsolePanel.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAwBrD,wBAAgB,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB,+BAsHA"}
@@ -15,12 +15,24 @@ export function ConsolePanel({ client, targetId, }) {
15
15
  const [repl, setRepl] = useState([]);
16
16
  const [input, setInput] = useState('');
17
17
  const scrollRef = useRef(null);
18
+ // ⚠️ NOT `seq` — THAT IS NOT UNIQUE HERE. `seq` counts inside the page's
19
+ // console agent, and a fresh document gets a fresh agent, so it RESTARTS AT 1
20
+ // on every navigation. This list spans navigations (the target is a tab, and
21
+ // the tab did not change), so keying rows by seq gave two `c1`s: React warned
22
+ // that children "may be duplicated and/or omitted" and then omitted them —
23
+ // reported as "I cannot scroll to the end of the console", because the end was
24
+ // what stopped rendering. This counter is monotonic for the life of the list,
25
+ // which is the scope the keys have to be unique in.
26
+ const uid = useRef(0);
18
27
  useEffect(() => {
19
28
  let live = true;
20
29
  setEntries([]);
21
30
  setRepl([]);
22
- void client.consoleBuffer(targetId).then((b) => live && setEntries(b));
23
- const off = client.onConsole(targetId, (e) => setEntries((cur) => [...cur, e].slice(-2000)));
31
+ const row = (e) => ({ uid: uid.current++, e });
32
+ void client
33
+ .consoleBuffer(targetId)
34
+ .then((b) => live && setEntries(b.map(row)));
35
+ const off = client.onConsole(targetId, (e) => setEntries((cur) => [...cur, row(e)].slice(-2000)));
24
36
  return () => {
25
37
  live = false;
26
38
  off();
@@ -48,7 +60,7 @@ export function ConsolePanel({ client, targetId, }) {
48
60
  : [...cur, { kind: 'error', text: res.exception?.text ?? 'error' }]);
49
61
  };
50
62
  const expand = (objectId) => client.expand(targetId, objectId);
51
- return (_jsxs("div", { className: "ins-panel", children: [_jsxs("div", { className: "ins-scroll", ref: scrollRef, children: [entries.length === 0 && repl.length === 0 && (_jsxs("div", { className: "ins-empty", children: [_jsx("b", { children: "Console" }), "console output streams here \u00B7 type below to evaluate"] })), entries.map((e) => (_jsxs("div", { className: "ins-log-row", "data-level": e.level, children: [_jsx("span", { className: "ins-log-gutter", children: GUTTER[e.level] ?? '·' }), _jsx("span", { className: "ins-log-body", children: e.text }), e.source?.url && (_jsxs("span", { className: "ins-log-src", children: [shortUrl(e.source.url), ":", e.source.line] }))] }, `c${e.seq}`))), repl.map((line, i) => (_jsxs("div", { className: "ins-log-row", "data-level": line.kind === 'error' ? 'error' : 'log', children: [_jsx("span", { className: "ins-log-gutter", children: line.kind === 'input' ? '›' : line.kind === 'error' ? '✕' : '‹' }), _jsx("span", { className: "ins-log-body", children: line.value ? (_jsx(RemoteValueView, { value: line.value, expand: expand, defaultOpen: true })) : (line.text) })] }, `r${i}`)))] }), _jsx("form", { className: "ins-repl", onSubmit: (e) => {
63
+ return (_jsxs("div", { className: "ins-panel", children: [_jsxs("div", { className: "ins-scroll", ref: scrollRef, children: [entries.length === 0 && repl.length === 0 && (_jsxs("div", { className: "ins-empty", children: [_jsx("b", { children: "Console" }), "console output streams here \u00B7 type below to evaluate"] })), entries.map(({ uid, e }) => (_jsxs("div", { className: "ins-log-row", "data-level": e.level, children: [_jsx("span", { className: "ins-log-gutter", children: GUTTER[e.level] ?? '·' }), _jsx("span", { className: "ins-log-body", children: e.text }), e.source?.url && (_jsxs("span", { className: "ins-log-src", children: [shortUrl(e.source.url), ":", e.source.line] }))] }, `c${uid}`))), repl.map((line, i) => (_jsxs("div", { className: "ins-log-row", "data-level": line.kind === 'error' ? 'error' : 'log', children: [_jsx("span", { className: "ins-log-gutter", children: line.kind === 'input' ? '›' : line.kind === 'error' ? '✕' : '‹' }), _jsx("span", { className: "ins-log-body", children: line.value ? (_jsx(RemoteValueView, { value: line.value, expand: expand, defaultOpen: true })) : (line.text) })] }, `r${i}`)))] }), _jsx("form", { className: "ins-repl", onSubmit: (e) => {
52
64
  e.preventDefault();
53
65
  void run();
54
66
  }, children: _jsx("input", { value: input, onChange: (e) => setInput(e.target.value), placeholder: "evaluate expression\u2026", spellCheck: false, autoComplete: "off" }) })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"ConsolePanel.js","sourceRoot":"","sources":["../../../../src/inspect/ui/panels/ConsolePanel.tsx"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,gFAAgF;AAEhF,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,MAAM,MAAM,GAA2B;IACrC,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,GAAG;IACT,KAAK,EAAE,GAAG;IACV,KAAK,EAAE,GAAG;CACX,CAAC;AAQF,MAAM,UAAU,YAAY,CAAC,EAC3B,MAAM,EACN,QAAQ,GAIT;IACC,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAiB,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAa,EAAE,CAAC,CAAC;IACjD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvC,MAAM,SAAS,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAE/C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,IAAI,GAAG,IAAI,CAAC;QAChB,UAAU,CAAC,EAAE,CAAC,CAAC;QACf,OAAO,CAAC,EAAE,CAAC,CAAC;QACZ,KAAK,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;QACvE,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAC3C,UAAU,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAC9C,CAAC;QACF,OAAO,GAAG,EAAE;YACV,IAAI,GAAG,KAAK,CAAC;YACb,GAAG,EAAE,CAAC;QACR,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEvB,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC;QAC7B,IAAI,EAAE;YAAE,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC,YAAY,CAAC;IACzC,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;IAEpB,MAAM,GAAG,GAAG,KAAK,IAAmB,EAAE;QACpC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,QAAQ,CAAC,EAAE,CAAC,CAAC;QACb,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC;YAC5B,QAAQ;YACR,UAAU,EAAE,IAAI;YAChB,YAAY,EAAE,IAAI;YAClB,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QACH,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CACd,GAAG,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;YAChD,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS,EAAE,IAAI,IAAI,OAAO,EAAE,CAAC,CACtE,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,MAAM,GAAG,CAAC,QAAgB,EAA0B,EAAE,CAC1D,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAEpC,OAAO,CACL,eAAK,SAAS,EAAC,WAAW,aACxB,eAAK,SAAS,EAAC,YAAY,EAAC,GAAG,EAAE,SAAS,aACvC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAC5C,eAAK,SAAS,EAAC,WAAW,aACxB,kCAAc,iEAEV,CACP,EACA,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAClB,eAAK,SAAS,EAAC,aAAa,gBAAa,CAAC,CAAC,KAAK,aAC9C,eAAM,SAAS,EAAC,gBAAgB,YAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,GAAG,GAAQ,EAChE,eAAM,SAAS,EAAC,cAAc,YAAE,CAAC,CAAC,IAAI,GAAQ,EAC7C,CAAC,CAAC,MAAM,EAAE,GAAG,IAAI,CAChB,gBAAM,SAAS,EAAC,aAAa,aAC1B,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,OAAG,CAAC,CAAC,MAAM,CAAC,IAAI,IAClC,CACR,KAPoD,IAAI,CAAC,CAAC,GAAG,EAAE,CAQ5D,CACP,CAAC,EACD,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,CACrB,eACE,SAAS,EAAC,aAAa,gBACX,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,aAGnD,eAAM,SAAS,EAAC,gBAAgB,YAC7B,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAC3D,EACP,eAAM,SAAS,EAAC,cAAc,YAC3B,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CACZ,KAAC,eAAe,IACd,KAAK,EAAE,IAAI,CAAC,KAAK,EACjB,MAAM,EAAE,MAAM,EACd,WAAW,SACX,CACH,CAAC,CAAC,CAAC,CACF,IAAI,CAAC,IAAI,CACV,GACI,KAfF,IAAI,CAAC,EAAE,CAgBR,CACP,CAAC,IACE,EACN,eACE,SAAS,EAAC,UAAU,EACpB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;oBACd,CAAC,CAAC,cAAc,EAAE,CAAC;oBACnB,KAAK,GAAG,EAAE,CAAC;gBACb,CAAC,YAED,gBACE,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EACzC,WAAW,EAAC,2BAAsB,EAClC,UAAU,EAAE,KAAK,EACjB,YAAY,EAAC,KAAK,GAClB,GACG,IACH,CACP,CAAC;AACJ,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,MAAM,KAAK,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpC,OAAO,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;IACxB,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"ConsolePanel.js","sourceRoot":"","sources":["../../../../src/inspect/ui/panels/ConsolePanel.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=ConsolePanel.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConsolePanel.test.d.ts","sourceRoot":"","sources":["../../../../src/inspect/ui/panels/ConsolePanel.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // @vitest-environment jsdom
3
+ import { cleanup, render, act } from '@testing-library/react';
4
+ import { afterEach, describe, expect, it, vi } from 'vitest';
5
+ import { ConsolePanel } from './ConsolePanel.js';
6
+ afterEach(cleanup);
7
+ const entry = (seq, text) => ({ seq, level: 'log', text, ts: 0, args: [] });
8
+ /** A client whose live console stream we drive by hand. */
9
+ function fakeClient(buffer = []) {
10
+ let emit = () => { };
11
+ const client = {
12
+ consoleBuffer: vi.fn(async () => buffer),
13
+ onConsole: vi.fn((_t, h) => {
14
+ emit = h;
15
+ return () => { };
16
+ }),
17
+ eval: vi.fn(),
18
+ expand: vi.fn(),
19
+ };
20
+ return { client, push: (e) => act(() => emit(e)) };
21
+ }
22
+ describe('ConsolePanel', () => {
23
+ /**
24
+ * `seq` is a closure counter inside the page's console agent, so it RESTARTS
25
+ * AT 1 on every navigation — a fresh document gets a fresh agent. The panel
26
+ * keeps accumulating across those navigations (the target id, a tab, never
27
+ * changed), so a keyed-by-seq list ends up with two `c1`s, React warns that
28
+ * children "may be duplicated and/or omitted", and the tail of the log stops
29
+ * rendering: "I cannot scroll to the end of the console."
30
+ */
31
+ it('renders every entry after a navigation resets the agent seq', async () => {
32
+ const warn = vi.spyOn(console, 'error').mockImplementation(() => { });
33
+ const { client, push } = fakeClient([entry(1, 'before-nav')]);
34
+ const { container } = render(_jsx(ConsolePanel, { client: client, targetId: "tab-1" }));
35
+ await act(async () => { });
36
+ // The page navigates; the new document's agent starts counting from 1 again.
37
+ push(entry(1, 'after-nav-a'));
38
+ push(entry(2, 'after-nav-b'));
39
+ const rows = Array.from(container.querySelectorAll('.ins-log-row'), (r) => r.querySelector('.ins-log-body')?.textContent);
40
+ expect(rows).toEqual(['before-nav', 'after-nav-a', 'after-nav-b']);
41
+ // React reports duplicate keys through console.error — none may be raised.
42
+ const dupWarning = warn.mock.calls
43
+ .flat()
44
+ .some((a) => String(a).includes('same key'));
45
+ expect(dupWarning, 'React warned about duplicate keys').toBe(false);
46
+ warn.mockRestore();
47
+ });
48
+ it('keeps the newest entry last so the pinned scroll lands on it', async () => {
49
+ const { client, push } = fakeClient([entry(7, 'old')]);
50
+ const { container } = render(_jsx(ConsolePanel, { client: client, targetId: "tab-1" }));
51
+ await act(async () => { });
52
+ push(entry(1, 'newest'));
53
+ const rows = Array.from(container.querySelectorAll('.ins-log-body'), (r) => r.textContent);
54
+ expect(rows.at(-1)).toBe('newest');
55
+ });
56
+ });
57
+ //# sourceMappingURL=ConsolePanel.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConsolePanel.test.js","sourceRoot":"","sources":["../../../../src/inspect/ui/panels/ConsolePanel.test.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ugly-app",
3
3
  "packageManager": "pnpm@10.34.5",
4
- "version": "0.1.983",
4
+ "version": "0.1.984",
5
5
  "type": "module",
6
6
  "comment:files": "Allowlist what ships to npm. dist = runtime; src = sourcemap targets (dist/*.js.map reference ../../src/); templates = CLI scaffold. Everything else at repo root (.pgdata local Postgres, coverage, assets/icons sources, test/, test-results/) is excluded by omission. The !negations strip the scaffold's installed deps + cruft (templates/node_modules is 200MB+ and must never ship). package.json/README/LICENSE ship automatically.",
7
7
  "files": [
package/src/cli/dev.ts CHANGED
@@ -47,7 +47,6 @@ import {
47
47
  writeDevLock,
48
48
  } from './devLock.js';
49
49
  import { backgroundWait } from '../server/backgroundWait.js';
50
- import { killSubtree, startOrphanWatch } from './devReap.js';
51
50
 
52
51
  export async function runDev(
53
52
  opts: { watch: boolean; verbose: boolean } = { watch: false, verbose: false },
@@ -269,54 +268,31 @@ export async function runDev(
269
268
  }
270
269
  }
271
270
 
272
- const { result } = concurrently(finalCommands, {
271
+ const { result, commands: runningCommands } = concurrently(finalCommands, {
273
272
  killOthersOn: ['failure'],
274
273
  handleInput: true,
275
274
  raw: !isWin,
276
275
  });
277
276
 
278
- // ── Take the whole tree down with us ──────────────────────────────────────
279
- // `concurrently` forwards Ctrl-C to the commands it started, and that is all.
280
- // It does not know about minio (spawned by ensureLocalInfra, a direct child
281
- // of ours), about esbuild under tsx, or about tsc under its `sh -c` wrapper —
282
- // and tsx/tsc routinely ignore SIGTERM. So on every exit path, walk our own
283
- // process table subtree and escalate. `killSubtree` never signals us, so this
284
- // is safe to call while we are the one exiting.
285
- //
286
- // Registered on 'exit' rather than only on the orphan path so an ordinary
287
- // crash or `throw` cleans up too. `killSubtree` is fully synchronous for
288
- // exactly this reason.
289
- if (!isWin) {
290
- let reaped = false;
291
- const reapChildren = (): void => {
292
- if (reaped) return;
293
- reaped = true;
294
- const forced = killSubtree(process.pid);
295
- if (forced.length > 0 && verbose) {
296
- console.log(`[ugly-app] SIGKILLed ${forced.length} stuck child(ren)`);
297
- }
298
- };
299
- process.on('exit', reapChildren);
300
-
301
- // Self-reap when the session that launched us goes away. macOS has no
302
- // PR_SET_PDEATHSIG, so poll instead — and poll the whole ancestor *chain*,
303
- // not just our ppid. `npx ugly-app dev` runs us under an `npm exec`
304
- // wrapper that outlives the terminal, so our ppid stays valid forever and
305
- // a ppid===1 test never fires (that is how a ugly-bot dev stack and its
306
- // minio stayed up for two days). This runs in non-watch mode too: a plain
307
- // `ugly-app dev` leaks a `tsx server/index.ts` the same way, and two of
308
- // those were found 3 and 5.5 days old.
309
- //
310
- // Set UGLY_APP_NO_ORPHAN_REAP=1 to deliberately outlive your launcher.
311
- if (!process.env['UGLY_APP_NO_ORPHAN_REAP']) {
312
- startOrphanWatch((deadAncestor) => {
277
+ // Self-reap when orphaned. `concurrently` forwards Ctrl-C and cross-kills on
278
+ // failure, but nothing here notices the *launching* process dying: when the
279
+ // terminal / session that started `ugly-app dev` exits, the OS re-parents us
280
+ // to PID 1 and we'd otherwise keep tsx/tsc/minio watching files forever.
281
+ // macOS has no PR_SET_PDEATHSIG, so poll process.ppid and shut our own child
282
+ // watchers down the moment it flips to 1. Only meaningful in watch mode.
283
+ if (opts.watch && !isWin) {
284
+ const bornPpid = process.ppid; // guard: never fire if we started orphaned
285
+ const orphanWatch = setInterval(() => {
286
+ if (bornPpid !== 1 && process.ppid === 1) {
287
+ clearInterval(orphanWatch);
313
288
  console.error(
314
- `[ugly-app] launching process (pid ${deadAncestor}) exited — stopping the dev stack`,
289
+ '[ugly-app] launching process exited — stopping dev watchers',
315
290
  );
316
- reapChildren();
317
- process.exit(0);
318
- });
319
- }
291
+ for (const c of runningCommands) c.kill('SIGTERM');
292
+ setTimeout(() => process.exit(0), 2000).unref();
293
+ }
294
+ }, 3000);
295
+ orphanWatch.unref();
320
296
  }
321
297
 
322
298
  try {
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by prebuild — do not edit manually
2
- export const CLI_VERSION = "0.1.983";
2
+ export const CLI_VERSION = "0.1.984";
@@ -0,0 +1,76 @@
1
+ // @vitest-environment jsdom
2
+ import { cleanup, render, act } from '@testing-library/react';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+ import { ConsolePanel } from './ConsolePanel.js';
5
+ import type { InspectClient } from '../../client.js';
6
+ import type { ConsoleEntry } from '../../protocol.js';
7
+
8
+ afterEach(cleanup);
9
+
10
+ const entry = (seq: number, text: string): ConsoleEntry =>
11
+ ({ seq, level: 'log', text, ts: 0, args: [] }) as unknown as ConsoleEntry;
12
+
13
+ /** A client whose live console stream we drive by hand. */
14
+ function fakeClient(buffer: ConsoleEntry[] = []) {
15
+ let emit: (e: ConsoleEntry) => void = () => {};
16
+ const client = {
17
+ consoleBuffer: vi.fn(async () => buffer),
18
+ onConsole: vi.fn((_t: string, h: (e: ConsoleEntry) => void) => {
19
+ emit = h;
20
+ return () => {};
21
+ }),
22
+ eval: vi.fn(),
23
+ expand: vi.fn(),
24
+ } as unknown as InspectClient;
25
+ return { client, push: (e: ConsoleEntry) => act(() => emit(e)) };
26
+ }
27
+
28
+ describe('ConsolePanel', () => {
29
+ /**
30
+ * `seq` is a closure counter inside the page's console agent, so it RESTARTS
31
+ * AT 1 on every navigation — a fresh document gets a fresh agent. The panel
32
+ * keeps accumulating across those navigations (the target id, a tab, never
33
+ * changed), so a keyed-by-seq list ends up with two `c1`s, React warns that
34
+ * children "may be duplicated and/or omitted", and the tail of the log stops
35
+ * rendering: "I cannot scroll to the end of the console."
36
+ */
37
+ it('renders every entry after a navigation resets the agent seq', async () => {
38
+ const warn = vi.spyOn(console, 'error').mockImplementation(() => {});
39
+ const { client, push } = fakeClient([entry(1, 'before-nav')]);
40
+ const { container } = render(
41
+ <ConsolePanel client={client} targetId="tab-1" />,
42
+ );
43
+ await act(async () => {});
44
+
45
+ // The page navigates; the new document's agent starts counting from 1 again.
46
+ push(entry(1, 'after-nav-a'));
47
+ push(entry(2, 'after-nav-b'));
48
+
49
+ const rows = Array.from(
50
+ container.querySelectorAll('.ins-log-row'),
51
+ (r) => r.querySelector('.ins-log-body')?.textContent,
52
+ );
53
+ expect(rows).toEqual(['before-nav', 'after-nav-a', 'after-nav-b']);
54
+
55
+ // React reports duplicate keys through console.error — none may be raised.
56
+ const dupWarning = warn.mock.calls
57
+ .flat()
58
+ .some((a) => String(a).includes('same key'));
59
+ expect(dupWarning, 'React warned about duplicate keys').toBe(false);
60
+ warn.mockRestore();
61
+ });
62
+
63
+ it('keeps the newest entry last so the pinned scroll lands on it', async () => {
64
+ const { client, push } = fakeClient([entry(7, 'old')]);
65
+ const { container } = render(
66
+ <ConsolePanel client={client} targetId="tab-1" />,
67
+ );
68
+ await act(async () => {});
69
+ push(entry(1, 'newest'));
70
+ const rows = Array.from(
71
+ container.querySelectorAll('.ins-log-body'),
72
+ (r) => r.textContent,
73
+ );
74
+ expect(rows.at(-1)).toBe('newest');
75
+ });
76
+ });
@@ -20,6 +20,12 @@ interface ReplLine {
20
20
  value?: RemoteValue | undefined;
21
21
  }
22
22
 
23
+ /** A captured entry plus a key that is unique in THIS list — see `uid` below. */
24
+ interface Row {
25
+ uid: number;
26
+ e: ConsoleEntry;
27
+ }
28
+
23
29
  export function ConsolePanel({
24
30
  client,
25
31
  targetId,
@@ -27,18 +33,30 @@ export function ConsolePanel({
27
33
  client: InspectClient;
28
34
  targetId: string;
29
35
  }) {
30
- const [entries, setEntries] = useState<ConsoleEntry[]>([]);
36
+ const [entries, setEntries] = useState<Row[]>([]);
31
37
  const [repl, setRepl] = useState<ReplLine[]>([]);
32
38
  const [input, setInput] = useState('');
33
39
  const scrollRef = useRef<HTMLDivElement>(null);
40
+ // ⚠️ NOT `seq` — THAT IS NOT UNIQUE HERE. `seq` counts inside the page's
41
+ // console agent, and a fresh document gets a fresh agent, so it RESTARTS AT 1
42
+ // on every navigation. This list spans navigations (the target is a tab, and
43
+ // the tab did not change), so keying rows by seq gave two `c1`s: React warned
44
+ // that children "may be duplicated and/or omitted" and then omitted them —
45
+ // reported as "I cannot scroll to the end of the console", because the end was
46
+ // what stopped rendering. This counter is monotonic for the life of the list,
47
+ // which is the scope the keys have to be unique in.
48
+ const uid = useRef(0);
34
49
 
35
50
  useEffect(() => {
36
51
  let live = true;
37
52
  setEntries([]);
38
53
  setRepl([]);
39
- void client.consoleBuffer(targetId).then((b) => live && setEntries(b));
54
+ const row = (e: ConsoleEntry): Row => ({ uid: uid.current++, e });
55
+ void client
56
+ .consoleBuffer(targetId)
57
+ .then((b) => live && setEntries(b.map(row)));
40
58
  const off = client.onConsole(targetId, (e) =>
41
- setEntries((cur) => [...cur, e].slice(-2000)),
59
+ setEntries((cur) => [...cur, row(e)].slice(-2000)),
42
60
  );
43
61
  return () => {
44
62
  live = false;
@@ -81,8 +99,8 @@ export function ConsolePanel({
81
99
  console output streams here · type below to evaluate
82
100
  </div>
83
101
  )}
84
- {entries.map((e) => (
85
- <div className="ins-log-row" data-level={e.level} key={`c${e.seq}`}>
102
+ {entries.map(({ uid, e }) => (
103
+ <div className="ins-log-row" data-level={e.level} key={`c${uid}`}>
86
104
  <span className="ins-log-gutter">{GUTTER[e.level] ?? '·'}</span>
87
105
  <span className="ins-log-body">{e.text}</span>
88
106
  {e.source?.url && (
@@ -1,57 +0,0 @@
1
- export interface ProcRow {
2
- pid: number;
3
- ppid: number;
4
- }
5
- /** Parse `ps -Ao pid=,ppid=` output. Malformed lines are skipped, not thrown. */
6
- export declare function parsePsTable(out: string): ProcRow[];
7
- export declare function readProcTable(): ProcRow[];
8
- /**
9
- * Descendants of `root`, deepest first, excluding `root` itself. Killing in
10
- * this order stops a supervisor (tsx watch) from respawning a child we already
11
- * killed. Cycle-safe: a pid is only ever visited once, so a recycled-pid loop
12
- * in the table cannot hang the caller.
13
- */
14
- export declare function descendantsOf(root: number, rows: ProcRow[]): number[];
15
- /**
16
- * The ancestor chain of `pid`, nearest first, stopping before pid 1. Empty when
17
- * the process is already an orphan — which is exactly when an orphan watch must
18
- * not arm itself.
19
- */
20
- export declare function ancestorsOf(pid: number, rows: ProcRow[]): number[];
21
- /** Signal 0 tests for existence without delivering anything. */
22
- export declare function isAlive(pid: number): boolean;
23
- export interface KillSubtreeOptions {
24
- /** How long to let SIGTERM work before escalating. */
25
- graceMs?: number;
26
- /** Injected in tests. */
27
- procTable?: () => ProcRow[];
28
- sleep?: (ms: number) => void;
29
- }
30
- /**
31
- * Kill every descendant of `root` — never `root` itself, so a caller can use
32
- * this on its own pid without racing its own exit. SIGTERM first (tsx and minio
33
- * flush and exit cleanly given the chance), SIGKILL for whatever ignored it.
34
- *
35
- * Returns the pids that had to be SIGKILLed, which is worth logging: it is the
36
- * signal that a child is not shutting down properly.
37
- */
38
- export declare function killSubtree(root: number, opts?: KillSubtreeOptions): number[];
39
- export interface OrphanWatchOptions {
40
- /** Poll period. */
41
- intervalMs?: number;
42
- procTable?: () => ProcRow[];
43
- alive?: (pid: number) => boolean;
44
- }
45
- /**
46
- * Call `onOrphan` once the chain that launched us breaks — i.e. any ancestor
47
- * recorded at startup has died. Watching the *whole* chain (not just ppid) is
48
- * what catches wrapper processes: `npm exec` outlives the terminal that ran it,
49
- * so our own ppid stays valid forever while the session it belonged to is long
50
- * gone.
51
- *
52
- * Returns a stop function. No-ops (returning a no-op stopper) when we started
53
- * with no ancestors, so a deliberately detached dev server is never reaped by
54
- * its own watchdog on the first tick.
55
- */
56
- export declare function startOrphanWatch(onOrphan: (deadAncestor: number) => void, opts?: OrphanWatchOptions): () => void;
57
- //# sourceMappingURL=devReap.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"devReap.d.ts","sourceRoot":"","sources":["../../src/cli/devReap.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,EAAE,CAQnD;AAED,wBAAgB,aAAa,IAAI,OAAO,EAAE,CAazC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAmBrE;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAWlE;AAED,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ5C;AAQD,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,OAAO,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,kBAAuB,GAC5B,MAAM,EAAE,CAyBV;AAED,MAAM,WAAW,kBAAkB;IACjC,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,OAAO,EAAE,CAAC;IAC5B,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CAClC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,EACxC,IAAI,GAAE,kBAAuB,GAC5B,MAAM,IAAI,CAeZ"}
@@ -1,173 +0,0 @@
1
- /**
2
- * Making `ugly-app dev` take its whole process tree down with it.
3
- *
4
- * A dev stack is `ugly-app dev` -> tsx watch -> esbuild, plus `sh -c tsc
5
- * --watch` -> tsc, plus minio. None of those exit on their own: tsx watch
6
- * deliberately survives a crashed server, tsc --watch runs forever, minio
7
- * serves forever. So the stack only ever goes away because something kills it,
8
- * and two things kept stopping that from happening:
9
- *
10
- * 1. The self-reap watchdog in dev.ts fired on `process.ppid === 1`. When
11
- * `ugly-app dev` is launched through a wrapper — `npx ugly-app dev`, which
12
- * leaves an `npm exec` process in between — our parent is that wrapper,
13
- * which never exits, so our ppid never becomes 1 no matter how long ago
14
- * the terminal that started it went away. `ancestorsOf` + a liveness poll
15
- * watches the whole chain instead of one link of it. (2026-08-03: a
16
- * ugly-bot stack and its minio had been up 2 days this way.)
17
- *
18
- * 2. Shutdown sent SIGTERM to concurrently's direct children only. tsx and
19
- * tsc routinely survive SIGTERM (the machine-level reaper's log is full of
20
- * "survived TERM"), and esbuild/minio are grandchildren nothing signalled
21
- * at all. `killSubtree` walks the real process table and escalates.
22
- *
23
- * Everything here is sync and dependency-free so it can also run from a
24
- * `process.on('exit')` handler, where async work never completes.
25
- */
26
- import { execSync } from 'child_process';
27
- /** Parse `ps -Ao pid=,ppid=` output. Malformed lines are skipped, not thrown. */
28
- export function parsePsTable(out) {
29
- const rows = [];
30
- for (const line of out.split('\n')) {
31
- const m = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
32
- if (!m)
33
- continue;
34
- rows.push({ pid: Number(m[1]), ppid: Number(m[2]) });
35
- }
36
- return rows;
37
- }
38
- export function readProcTable() {
39
- try {
40
- return parsePsTable(execSync('ps -Ao pid=,ppid=', {
41
- encoding: 'utf-8',
42
- stdio: ['ignore', 'pipe', 'ignore'],
43
- }));
44
- }
45
- catch {
46
- // No `ps` (or it failed): callers degrade to "no descendants / no
47
- // ancestors", which is always safer than guessing.
48
- return [];
49
- }
50
- }
51
- /**
52
- * Descendants of `root`, deepest first, excluding `root` itself. Killing in
53
- * this order stops a supervisor (tsx watch) from respawning a child we already
54
- * killed. Cycle-safe: a pid is only ever visited once, so a recycled-pid loop
55
- * in the table cannot hang the caller.
56
- */
57
- export function descendantsOf(root, rows) {
58
- const byParent = new Map();
59
- for (const r of rows) {
60
- const kids = byParent.get(r.ppid);
61
- if (kids)
62
- kids.push(r.pid);
63
- else
64
- byParent.set(r.ppid, [r.pid]);
65
- }
66
- const seen = new Set([root]);
67
- const ordered = [];
68
- const walk = (pid) => {
69
- for (const kid of byParent.get(pid) ?? []) {
70
- if (seen.has(kid))
71
- continue;
72
- seen.add(kid);
73
- walk(kid); // depth first, so children land before their parent
74
- ordered.push(kid);
75
- }
76
- };
77
- walk(root);
78
- return ordered;
79
- }
80
- /**
81
- * The ancestor chain of `pid`, nearest first, stopping before pid 1. Empty when
82
- * the process is already an orphan — which is exactly when an orphan watch must
83
- * not arm itself.
84
- */
85
- export function ancestorsOf(pid, rows) {
86
- const parentOf = new Map(rows.map((r) => [r.pid, r.ppid]));
87
- const chain = [];
88
- const seen = new Set([pid]);
89
- let cur = parentOf.get(pid);
90
- while (cur !== undefined && cur > 1 && !seen.has(cur)) {
91
- chain.push(cur);
92
- seen.add(cur);
93
- cur = parentOf.get(cur);
94
- }
95
- return chain;
96
- }
97
- /** Signal 0 tests for existence without delivering anything. */
98
- export function isAlive(pid) {
99
- try {
100
- process.kill(pid, 0);
101
- return true;
102
- }
103
- catch (err) {
104
- // EPERM means it exists but belongs to someone else — still alive.
105
- return err.code === 'EPERM';
106
- }
107
- }
108
- /** Block the thread without a subprocess, so this is usable from `exit`. */
109
- function sleepSync(ms) {
110
- const shared = new Int32Array(new SharedArrayBuffer(4));
111
- Atomics.wait(shared, 0, 0, ms);
112
- }
113
- /**
114
- * Kill every descendant of `root` — never `root` itself, so a caller can use
115
- * this on its own pid without racing its own exit. SIGTERM first (tsx and minio
116
- * flush and exit cleanly given the chance), SIGKILL for whatever ignored it.
117
- *
118
- * Returns the pids that had to be SIGKILLed, which is worth logging: it is the
119
- * signal that a child is not shutting down properly.
120
- */
121
- export function killSubtree(root, opts = {}) {
122
- const { graceMs = 2000, procTable = readProcTable, sleep = sleepSync } = opts;
123
- const targets = descendantsOf(root, procTable());
124
- if (targets.length === 0)
125
- return [];
126
- for (const pid of targets) {
127
- try {
128
- process.kill(pid, 'SIGTERM');
129
- }
130
- catch {
131
- /* already gone between listing and signalling */
132
- }
133
- }
134
- sleep(graceMs);
135
- const survivors = targets.filter(isAlive);
136
- for (const pid of survivors) {
137
- try {
138
- process.kill(pid, 'SIGKILL');
139
- }
140
- catch {
141
- /* raced us to the exit */
142
- }
143
- }
144
- return survivors;
145
- }
146
- /**
147
- * Call `onOrphan` once the chain that launched us breaks — i.e. any ancestor
148
- * recorded at startup has died. Watching the *whole* chain (not just ppid) is
149
- * what catches wrapper processes: `npm exec` outlives the terminal that ran it,
150
- * so our own ppid stays valid forever while the session it belonged to is long
151
- * gone.
152
- *
153
- * Returns a stop function. No-ops (returning a no-op stopper) when we started
154
- * with no ancestors, so a deliberately detached dev server is never reaped by
155
- * its own watchdog on the first tick.
156
- */
157
- export function startOrphanWatch(onOrphan, opts = {}) {
158
- const { intervalMs = 3000, procTable = readProcTable, alive = isAlive } = opts;
159
- const chain = ancestorsOf(process.pid, procTable());
160
- if (chain.length === 0)
161
- return () => undefined;
162
- const timer = setInterval(() => {
163
- const dead = chain.find((pid) => !alive(pid));
164
- if (dead === undefined)
165
- return;
166
- clearInterval(timer);
167
- onOrphan(dead);
168
- }, intervalMs);
169
- // Never hold the event loop open on account of the watchdog.
170
- timer.unref();
171
- return () => clearInterval(timer);
172
- }
173
- //# sourceMappingURL=devReap.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"devReap.js","sourceRoot":"","sources":["../../src/cli/devReap.ts"],"names":[],"mappings":"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"}
@@ -1,154 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { spawn } from 'child_process';
3
- import {
4
- ancestorsOf,
5
- descendantsOf,
6
- isAlive,
7
- killSubtree,
8
- parsePsTable,
9
- readProcTable,
10
- startOrphanWatch,
11
- type ProcRow,
12
- } from './devReap.js';
13
-
14
- /** A dev stack as `ps` actually reports it, plus unrelated processes. */
15
- const STACK: ProcRow[] = [
16
- { pid: 1, ppid: 0 },
17
- { pid: 100, ppid: 1 }, // npm exec wrapper (outlives the terminal)
18
- { pid: 200, ppid: 100 }, // ugly-app dev
19
- { pid: 210, ppid: 200 }, // minio
20
- { pid: 220, ppid: 200 }, // tsx watch
21
- { pid: 221, ppid: 220 }, // esbuild
22
- { pid: 230, ppid: 200 }, // sh -c tsc --watch
23
- { pid: 231, ppid: 230 }, // tsc
24
- { pid: 900, ppid: 1 }, // somebody else's process
25
- ];
26
-
27
- describe('parsePsTable', () => {
28
- it('reads pid/ppid pairs and ignores anything malformed', () => {
29
- expect(parsePsTable(' 1 0\n 502 1\n\ngarbage\n')).toEqual([
30
- { pid: 1, ppid: 0 },
31
- { pid: 502, ppid: 1 },
32
- ]);
33
- });
34
- });
35
-
36
- describe('descendantsOf', () => {
37
- it('returns the whole tree deepest-first, excluding the root', () => {
38
- const got = descendantsOf(200, STACK);
39
-
40
- expect(new Set(got)).toEqual(new Set([210, 220, 221, 230, 231]));
41
- expect(got).not.toContain(200);
42
- // Grandchildren must be signalled before the supervisor that would respawn
43
- // them — esbuild before tsx, tsc before its `sh -c` wrapper.
44
- expect(got.indexOf(221)).toBeLessThan(got.indexOf(220));
45
- expect(got.indexOf(231)).toBeLessThan(got.indexOf(230));
46
- });
47
-
48
- it('never reaches processes outside the tree', () => {
49
- expect(descendantsOf(200, STACK)).not.toContain(900);
50
- expect(descendantsOf(200, STACK)).not.toContain(100);
51
- });
52
-
53
- it('terminates on a cyclic table rather than hanging', () => {
54
- const cyclic: ProcRow[] = [
55
- { pid: 5, ppid: 6 },
56
- { pid: 6, ppid: 5 },
57
- ];
58
- expect(descendantsOf(5, cyclic)).toEqual([6]);
59
- });
60
-
61
- it('is empty for a leaf', () => {
62
- expect(descendantsOf(221, STACK)).toEqual([]);
63
- });
64
- });
65
-
66
- describe('ancestorsOf', () => {
67
- it('walks past the wrapper the old ppid===1 check stopped at', () => {
68
- // This is the ugly-bot leak: pid 200's own parent (100) is alive forever,
69
- // so watching ppid alone can never notice the session dying. The chain is
70
- // what makes 100's own launcher observable.
71
- expect(ancestorsOf(200, STACK)).toEqual([100]);
72
- expect(ancestorsOf(221, STACK)).toEqual([220, 200, 100]);
73
- });
74
-
75
- it('excludes pid 1 so launchd is never treated as a live launcher', () => {
76
- expect(ancestorsOf(100, STACK)).toEqual([]);
77
- });
78
-
79
- it('terminates on a cyclic table', () => {
80
- expect(
81
- ancestorsOf(5, [
82
- { pid: 5, ppid: 6 },
83
- { pid: 6, ppid: 5 },
84
- ]),
85
- ).toEqual([6]);
86
- });
87
- });
88
-
89
- describe('startOrphanWatch', () => {
90
- it('does not arm when the process is already an orphan', () => {
91
- let fired = false;
92
- const stop = startOrphanWatch(() => (fired = true), {
93
- intervalMs: 1,
94
- procTable: () => [{ pid: process.pid, ppid: 1 }],
95
- alive: () => false,
96
- });
97
-
98
- stop();
99
- expect(fired).toBe(false);
100
- });
101
-
102
- it('fires when an ancestor above the immediate parent dies', async () => {
103
- const dead = new Set<number>();
104
- let reported: number | null = null;
105
- const stop = startOrphanWatch((pid) => (reported = pid), {
106
- intervalMs: 1,
107
- procTable: () => [
108
- { pid: process.pid, ppid: 100 },
109
- { pid: 100, ppid: 400 }, // wrapper, stays alive
110
- { pid: 400, ppid: 1 }, // the launching session
111
- ],
112
- alive: (pid) => !dead.has(pid),
113
- });
114
-
115
- await new Promise((r) => setTimeout(r, 20));
116
- expect(reported).toBeNull(); // chain intact — nothing to do
117
-
118
- dead.add(400); // the terminal / agent session exits
119
- await new Promise((r) => setTimeout(r, 30));
120
- stop();
121
-
122
- expect(reported).toBe(400);
123
- });
124
- });
125
-
126
- describe('killSubtree', () => {
127
- it('leaves the root alone and reports nothing when there is no tree', () => {
128
- const killed = killSubtree(200, {
129
- procTable: () => [{ pid: 200, ppid: 1 }],
130
- sleep: () => undefined,
131
- });
132
- expect(killed).toEqual([]);
133
- });
134
-
135
- it('kills a real grandchild, which is what the old shutdown missed', async () => {
136
- // `sh -c 'sleep 30'` reproduces the shape that leaked: `sleep` is a
137
- // grandchild, exactly like esbuild under tsx or tsc under its `sh -c`
138
- // wrapper. Signalling only the direct child left those running.
139
- // The trailing `; true` matters: with a single command `sh` execs it and
140
- // there is no grandchild to find.
141
- const child = spawn('sh', ['-c', 'sleep 30; true'], { stdio: 'ignore' });
142
- const shPid = child.pid as number;
143
- await new Promise((r) => setTimeout(r, 400));
144
-
145
- const [sleepPid] = descendantsOf(shPid, readProcTable());
146
- expect(sleepPid).toBeTypeOf('number');
147
-
148
- killSubtree(shPid, { graceMs: 500 });
149
-
150
- expect(isAlive(sleepPid as number)).toBe(false);
151
- expect(isAlive(shPid)).toBe(true); // the root is never signalled
152
- child.kill('SIGKILL');
153
- }, 20_000);
154
- });
@@ -1,207 +0,0 @@
1
- /**
2
- * Making `ugly-app dev` take its whole process tree down with it.
3
- *
4
- * A dev stack is `ugly-app dev` -> tsx watch -> esbuild, plus `sh -c tsc
5
- * --watch` -> tsc, plus minio. None of those exit on their own: tsx watch
6
- * deliberately survives a crashed server, tsc --watch runs forever, minio
7
- * serves forever. So the stack only ever goes away because something kills it,
8
- * and two things kept stopping that from happening:
9
- *
10
- * 1. The self-reap watchdog in dev.ts fired on `process.ppid === 1`. When
11
- * `ugly-app dev` is launched through a wrapper — `npx ugly-app dev`, which
12
- * leaves an `npm exec` process in between — our parent is that wrapper,
13
- * which never exits, so our ppid never becomes 1 no matter how long ago
14
- * the terminal that started it went away. `ancestorsOf` + a liveness poll
15
- * watches the whole chain instead of one link of it. (2026-08-03: a
16
- * ugly-bot stack and its minio had been up 2 days this way.)
17
- *
18
- * 2. Shutdown sent SIGTERM to concurrently's direct children only. tsx and
19
- * tsc routinely survive SIGTERM (the machine-level reaper's log is full of
20
- * "survived TERM"), and esbuild/minio are grandchildren nothing signalled
21
- * at all. `killSubtree` walks the real process table and escalates.
22
- *
23
- * Everything here is sync and dependency-free so it can also run from a
24
- * `process.on('exit')` handler, where async work never completes.
25
- */
26
- import { execSync } from 'child_process';
27
-
28
- export interface ProcRow {
29
- pid: number;
30
- ppid: number;
31
- }
32
-
33
- /** Parse `ps -Ao pid=,ppid=` output. Malformed lines are skipped, not thrown. */
34
- export function parsePsTable(out: string): ProcRow[] {
35
- const rows: ProcRow[] = [];
36
- for (const line of out.split('\n')) {
37
- const m = /^\s*(\d+)\s+(\d+)\s*$/.exec(line);
38
- if (!m) continue;
39
- rows.push({ pid: Number(m[1]), ppid: Number(m[2]) });
40
- }
41
- return rows;
42
- }
43
-
44
- export function readProcTable(): ProcRow[] {
45
- try {
46
- return parsePsTable(
47
- execSync('ps -Ao pid=,ppid=', {
48
- encoding: 'utf-8',
49
- stdio: ['ignore', 'pipe', 'ignore'],
50
- }),
51
- );
52
- } catch {
53
- // No `ps` (or it failed): callers degrade to "no descendants / no
54
- // ancestors", which is always safer than guessing.
55
- return [];
56
- }
57
- }
58
-
59
- /**
60
- * Descendants of `root`, deepest first, excluding `root` itself. Killing in
61
- * this order stops a supervisor (tsx watch) from respawning a child we already
62
- * killed. Cycle-safe: a pid is only ever visited once, so a recycled-pid loop
63
- * in the table cannot hang the caller.
64
- */
65
- export function descendantsOf(root: number, rows: ProcRow[]): number[] {
66
- const byParent = new Map<number, number[]>();
67
- for (const r of rows) {
68
- const kids = byParent.get(r.ppid);
69
- if (kids) kids.push(r.pid);
70
- else byParent.set(r.ppid, [r.pid]);
71
- }
72
- const seen = new Set<number>([root]);
73
- const ordered: number[] = [];
74
- const walk = (pid: number): void => {
75
- for (const kid of byParent.get(pid) ?? []) {
76
- if (seen.has(kid)) continue;
77
- seen.add(kid);
78
- walk(kid); // depth first, so children land before their parent
79
- ordered.push(kid);
80
- }
81
- };
82
- walk(root);
83
- return ordered;
84
- }
85
-
86
- /**
87
- * The ancestor chain of `pid`, nearest first, stopping before pid 1. Empty when
88
- * the process is already an orphan — which is exactly when an orphan watch must
89
- * not arm itself.
90
- */
91
- export function ancestorsOf(pid: number, rows: ProcRow[]): number[] {
92
- const parentOf = new Map(rows.map((r) => [r.pid, r.ppid]));
93
- const chain: number[] = [];
94
- const seen = new Set<number>([pid]);
95
- let cur = parentOf.get(pid);
96
- while (cur !== undefined && cur > 1 && !seen.has(cur)) {
97
- chain.push(cur);
98
- seen.add(cur);
99
- cur = parentOf.get(cur);
100
- }
101
- return chain;
102
- }
103
-
104
- /** Signal 0 tests for existence without delivering anything. */
105
- export function isAlive(pid: number): boolean {
106
- try {
107
- process.kill(pid, 0);
108
- return true;
109
- } catch (err) {
110
- // EPERM means it exists but belongs to someone else — still alive.
111
- return (err as NodeJS.ErrnoException).code === 'EPERM';
112
- }
113
- }
114
-
115
- /** Block the thread without a subprocess, so this is usable from `exit`. */
116
- function sleepSync(ms: number): void {
117
- const shared = new Int32Array(new SharedArrayBuffer(4));
118
- Atomics.wait(shared, 0, 0, ms);
119
- }
120
-
121
- export interface KillSubtreeOptions {
122
- /** How long to let SIGTERM work before escalating. */
123
- graceMs?: number;
124
- /** Injected in tests. */
125
- procTable?: () => ProcRow[];
126
- sleep?: (ms: number) => void;
127
- }
128
-
129
- /**
130
- * Kill every descendant of `root` — never `root` itself, so a caller can use
131
- * this on its own pid without racing its own exit. SIGTERM first (tsx and minio
132
- * flush and exit cleanly given the chance), SIGKILL for whatever ignored it.
133
- *
134
- * Returns the pids that had to be SIGKILLed, which is worth logging: it is the
135
- * signal that a child is not shutting down properly.
136
- */
137
- export function killSubtree(
138
- root: number,
139
- opts: KillSubtreeOptions = {},
140
- ): number[] {
141
- const { graceMs = 2000, procTable = readProcTable, sleep = sleepSync } = opts;
142
-
143
- const targets = descendantsOf(root, procTable());
144
- if (targets.length === 0) return [];
145
-
146
- for (const pid of targets) {
147
- try {
148
- process.kill(pid, 'SIGTERM');
149
- } catch {
150
- /* already gone between listing and signalling */
151
- }
152
- }
153
-
154
- sleep(graceMs);
155
-
156
- const survivors = targets.filter(isAlive);
157
- for (const pid of survivors) {
158
- try {
159
- process.kill(pid, 'SIGKILL');
160
- } catch {
161
- /* raced us to the exit */
162
- }
163
- }
164
- return survivors;
165
- }
166
-
167
- export interface OrphanWatchOptions {
168
- /** Poll period. */
169
- intervalMs?: number;
170
- procTable?: () => ProcRow[];
171
- alive?: (pid: number) => boolean;
172
- }
173
-
174
- /**
175
- * Call `onOrphan` once the chain that launched us breaks — i.e. any ancestor
176
- * recorded at startup has died. Watching the *whole* chain (not just ppid) is
177
- * what catches wrapper processes: `npm exec` outlives the terminal that ran it,
178
- * so our own ppid stays valid forever while the session it belonged to is long
179
- * gone.
180
- *
181
- * Returns a stop function. No-ops (returning a no-op stopper) when we started
182
- * with no ancestors, so a deliberately detached dev server is never reaped by
183
- * its own watchdog on the first tick.
184
- */
185
- export function startOrphanWatch(
186
- onOrphan: (deadAncestor: number) => void,
187
- opts: OrphanWatchOptions = {},
188
- ): () => void {
189
- const {
190
- intervalMs = 3000,
191
- procTable = readProcTable,
192
- alive = isAlive,
193
- } = opts;
194
-
195
- const chain = ancestorsOf(process.pid, procTable());
196
- if (chain.length === 0) return () => undefined;
197
-
198
- const timer = setInterval(() => {
199
- const dead = chain.find((pid) => !alive(pid));
200
- if (dead === undefined) return;
201
- clearInterval(timer);
202
- onOrphan(dead);
203
- }, intervalMs);
204
- // Never hold the event loop open on account of the watchdog.
205
- timer.unref();
206
- return () => clearInterval(timer);
207
- }