@svenroth-ai/shipwright 0.25.0 → 0.27.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/client/dist/assets/{EmbeddedTerminal-5QvKe3_a.js → EmbeddedTerminal-CqCQUDtp.js} +1 -1
- package/client/dist/assets/{abnfDiagram-VRR7QNED-CXi9HegQ.js → abnfDiagram-VRR7QNED-DX2objTP.js} +1 -1
- package/client/dist/assets/{arc-DLh4GiqR.js → arc-DQmjbsFU.js} +1 -1
- package/client/dist/assets/{architectureDiagram-ZJ3FMSHR-DUypQ8dQ.js → architectureDiagram-ZJ3FMSHR-DAvCsPCW.js} +1 -1
- package/client/dist/assets/{blockDiagram-677ZJIJ3-D2cVMKJG.js → blockDiagram-677ZJIJ3-DC-dlyYI.js} +1 -1
- package/client/dist/assets/{c4Diagram-LMCZKHZV-DtruBGJw.js → c4Diagram-LMCZKHZV-DSlmXfhE.js} +1 -1
- package/client/dist/assets/channel-BfmIcrp7.js +1 -0
- package/client/dist/assets/{chunk-2Q5K7J3B-CAfQkFw2.js → chunk-2Q5K7J3B-C111EHL6.js} +1 -1
- package/client/dist/assets/{chunk-32BRIVSS-CPWXoLnQ.js → chunk-32BRIVSS-DrCYs2XG.js} +1 -1
- package/client/dist/assets/{chunk-5VM5RSS4-xVc3oppQ.js → chunk-5VM5RSS4-DIejoaXS.js} +1 -1
- package/client/dist/assets/{chunk-EX3LRPZG-CUpl6fRR.js → chunk-EX3LRPZG-CGWNeH2g.js} +1 -1
- package/client/dist/assets/{chunk-JWPE2WC7-NejUp56x.js → chunk-JWPE2WC7-CCH0SRFY.js} +1 -1
- package/client/dist/assets/{chunk-MOJQB5TN-C9-2lLg0.js → chunk-MOJQB5TN-B3OpZGRv.js} +1 -1
- package/client/dist/assets/{chunk-RYQCIY6F-BAPtwABy.js → chunk-RYQCIY6F-BKhdp_8v.js} +1 -1
- package/client/dist/assets/{chunk-V7JOEXUC-Dwzihw26.js → chunk-V7JOEXUC-B6Bu8x1y.js} +1 -1
- package/client/dist/assets/{chunk-VR4S4FIN-9-iArFJM.js → chunk-VR4S4FIN-OgFi2_iB.js} +1 -1
- package/client/dist/assets/{chunk-XXDRQBXY-DiWHOrqe.js → chunk-XXDRQBXY-xyjuuhxl.js} +1 -1
- package/client/dist/assets/classDiagram-OUVF2IWQ-HfUQZ6Uc.js +1 -0
- package/client/dist/assets/classDiagram-v2-EOCWNBFH-HfUQZ6Uc.js +1 -0
- package/client/dist/assets/{cose-bilkent-JH36ORCC-Cp04EOcH.js → cose-bilkent-JH36ORCC-BQIH3SCK.js} +1 -1
- package/client/dist/assets/{cynefin-VYW2F7L2-D--GN1v8.js → cynefin-VYW2F7L2-oyLAz8te.js} +1 -1
- package/client/dist/assets/{cynefinDiagram-TSTJHNR4-BGH0o_qp.js → cynefinDiagram-TSTJHNR4-DoLCsH0l.js} +1 -1
- package/client/dist/assets/{dagre-VKFMJZFB-DSKkIKlj.js → dagre-VKFMJZFB-D7djx1_Y.js} +1 -1
- package/client/dist/assets/{diagram-FQU43EPY-Cs_C4lb-.js → diagram-FQU43EPY-DFuYE0Vf.js} +1 -1
- package/client/dist/assets/{diagram-G47NLZAW-oDtAA8O_.js → diagram-G47NLZAW-CffCZVXk.js} +1 -1
- package/client/dist/assets/{diagram-NH7WQ7WH-BiFMjqSH.js → diagram-NH7WQ7WH-BhrHtUlt.js} +1 -1
- package/client/dist/assets/{diagram-OA4YK3LP-Ccs3thCj.js → diagram-OA4YK3LP-CwQPbOn0.js} +1 -1
- package/client/dist/assets/{diagram-WEI45ONY-CdZNqgO3.js → diagram-WEI45ONY-abtYnYqH.js} +1 -1
- package/client/dist/assets/{ebnfDiagram-CCIWWBDH-CapTA0VL.js → ebnfDiagram-CCIWWBDH-DfV7gT4m.js} +1 -1
- package/client/dist/assets/{erDiagram-Q63AITRT-BcRNEP8s.js → erDiagram-Q63AITRT-vtbCprgH.js} +1 -1
- package/client/dist/assets/{flowDiagram-23GEKE2U-CJ_zF7e_.js → flowDiagram-23GEKE2U-CVCgcyDF.js} +1 -1
- package/client/dist/assets/{ganttDiagram-NO4QXBWP-CnJdcg9A.js → ganttDiagram-NO4QXBWP-BclyOgMb.js} +1 -1
- package/client/dist/assets/{gitGraphDiagram-IHSO6WYX-YTUQ_ELk.js → gitGraphDiagram-IHSO6WYX-BXT4rIKj.js} +1 -1
- package/client/dist/assets/index-CKU9dY18.css +1 -0
- package/client/dist/assets/index-DOMl6atu.js +847 -0
- package/client/dist/assets/{infoDiagram-FWYZ7A6U-CLOP_ZWD.js → infoDiagram-FWYZ7A6U-Dr4mN24J.js} +1 -1
- package/client/dist/assets/{ishikawaDiagram-FXEZZL3T-aFTIzr7o.js → ishikawaDiagram-FXEZZL3T-BBUFWq5C.js} +1 -1
- package/client/dist/assets/{journeyDiagram-5HDEW3XC-CbPJB4xd.js → journeyDiagram-5HDEW3XC-CmclipcQ.js} +1 -1
- package/client/dist/assets/{kanban-definition-HUTT4EX6-BoyhytIS.js → kanban-definition-HUTT4EX6-eIzUeZ03.js} +1 -1
- package/client/dist/assets/{linear-1VXA-uBg.js → linear-DGiT7uKK.js} +1 -1
- package/client/dist/assets/{mermaid.core-D_lO2yK9.js → mermaid.core-BcLNGbxW.js} +4 -4
- package/client/dist/assets/{mindmap-definition-LN4V7U3C-iFK7LXMx.js → mindmap-definition-LN4V7U3C-C4ANnNiP.js} +1 -1
- package/client/dist/assets/{pegDiagram-2B236MQR-DNmcmfA-.js → pegDiagram-2B236MQR-DnMeh3Yh.js} +1 -1
- package/client/dist/assets/{pieDiagram-ENE6RG2P-ekx83O5b.js → pieDiagram-ENE6RG2P-CKV_rkhF.js} +1 -1
- package/client/dist/assets/{quadrantDiagram-ABIIQ3AL-DayyD1l8.js → quadrantDiagram-ABIIQ3AL-BuUsGwHh.js} +1 -1
- package/client/dist/assets/{railroadDiagram-RFXS5EU6-Bdgz7S6G.js → railroadDiagram-RFXS5EU6-BwwdKnFL.js} +1 -1
- package/client/dist/assets/{requirementDiagram-TGXJPOKE-szP3Muld.js → requirementDiagram-TGXJPOKE-CpQZouJV.js} +1 -1
- package/client/dist/assets/{sankeyDiagram-HTMAVEWB-Ct3rzgUj.js → sankeyDiagram-HTMAVEWB-4fnDUNVa.js} +1 -1
- package/client/dist/assets/{sequenceDiagram-DBY2YBRQ-_69EfmOU.js → sequenceDiagram-DBY2YBRQ-BOcaWlM0.js} +1 -1
- package/client/dist/assets/{sizeCapture-X5ZJPWSS-BSOU06J2.js → sizeCapture-X5ZJPWSS-_pv-9R0c.js} +1 -1
- package/client/dist/assets/{stateDiagram-2N3HPSRC-HmOmFwQm.js → stateDiagram-2N3HPSRC-CFwCOonY.js} +1 -1
- package/client/dist/assets/stateDiagram-v2-6OUMAXLB-srkiu3Ib.js +1 -0
- package/client/dist/assets/{swimlanes-5IMT3BWC-CVg_4oT7.js → swimlanes-5IMT3BWC-DFa2tzxu.js} +2 -2
- package/client/dist/assets/swimlanesDiagram-G3AALYLV-Dq-0K9Zm.js +8 -0
- package/client/dist/assets/{timeline-definition-FHXFAJF6-CBBaDKXm.js → timeline-definition-FHXFAJF6-rhGYElgn.js} +1 -1
- package/client/dist/assets/{vennDiagram-L72KCM5P-CBXiWPMc.js → vennDiagram-L72KCM5P-BoGjRxyA.js} +1 -1
- package/client/dist/assets/{wardleyDiagram-EHGQE667-uhBCZoW0.js → wardleyDiagram-EHGQE667-D-IGv5bH.js} +1 -1
- package/client/dist/assets/{xychartDiagram-FW5EYKEG-xxE_wY5O.js → xychartDiagram-FW5EYKEG-D9PFTCfm.js} +1 -1
- package/client/dist/index.html +27 -27
- package/lib/claude-cli.mjs +173 -173
- package/lib/preflight.mjs +252 -252
- package/lib/probe-path.mjs +84 -84
- package/lib/probes.mjs +10 -4
- package/lib/server.mjs +506 -369
- package/lib/util.mjs +157 -157
- package/lib/win32-spawn.mjs +253 -253
- package/package.json +68 -68
- package/scripts/deploy-procs.mjs +208 -208
- package/scripts/deploy-swap.mjs +276 -276
- package/scripts/kill-targets.js +140 -140
- package/scripts/repair-claude-json.mjs +295 -295
- package/server/dist/config/default-actions.json +90 -90
- package/server/dist/core/mission-context/association.d.ts +46 -0
- package/server/dist/core/mission-context/association.d.ts.map +1 -1
- package/server/dist/core/mission-context/association.js +69 -0
- package/server/dist/core/mission-context/association.js.map +1 -1
- package/server/dist/core/mission-context/resolver-iterate.d.ts +6 -0
- package/server/dist/core/mission-context/resolver-iterate.d.ts.map +1 -0
- package/server/dist/core/mission-context/resolver-iterate.js +202 -0
- package/server/dist/core/mission-context/resolver-iterate.js.map +1 -0
- package/server/dist/core/mission-context/resolver-parts.d.ts +20 -0
- package/server/dist/core/mission-context/resolver-parts.d.ts.map +1 -1
- package/server/dist/core/mission-context/resolver-parts.js +92 -0
- package/server/dist/core/mission-context/resolver-parts.js.map +1 -1
- package/server/dist/core/mission-context/resolver.d.ts +1 -1
- package/server/dist/core/mission-context/resolver.d.ts.map +1 -1
- package/server/dist/core/mission-context/resolver.js +17 -199
- package/server/dist/core/mission-context/resolver.js.map +1 -1
- package/server/dist/core/mission-context/run-id-recovery.d.ts +21 -5
- package/server/dist/core/mission-context/run-id-recovery.d.ts.map +1 -1
- package/server/dist/core/mission-context/run-id-recovery.js +32 -5
- package/server/dist/core/mission-context/run-id-recovery.js.map +1 -1
- package/server/dist/core/mission-context/scenario.d.ts +18 -11
- package/server/dist/core/mission-context/scenario.d.ts.map +1 -1
- package/server/dist/core/mission-context/scenario.js +40 -3
- package/server/dist/core/mission-context/scenario.js.map +1 -1
- package/server/dist/core/path-guard.d.ts +1 -1
- package/server/dist/core/path-guard.d.ts.map +1 -1
- package/server/dist/core/path-guard.js +9 -2
- package/server/dist/core/path-guard.js.map +1 -1
- package/server/dist/core/sdk-sessions-merge.d.ts +19 -0
- package/server/dist/core/sdk-sessions-merge.d.ts.map +1 -1
- package/server/dist/core/sdk-sessions-merge.js +19 -5
- package/server/dist/core/sdk-sessions-merge.js.map +1 -1
- package/server/dist/core/sdk-sessions-refresh.d.ts +50 -0
- package/server/dist/core/sdk-sessions-refresh.d.ts.map +1 -0
- package/server/dist/core/sdk-sessions-refresh.js +69 -0
- package/server/dist/core/sdk-sessions-refresh.js.map +1 -0
- package/server/dist/core/sdk-sessions-store.d.ts +2 -0
- package/server/dist/core/sdk-sessions-store.d.ts.map +1 -1
- package/server/dist/core/sdk-sessions-store.js +7 -9
- package/server/dist/core/sdk-sessions-store.js.map +1 -1
- package/server/dist/core/shipslog-docs-reader.d.ts +4 -0
- package/server/dist/core/shipslog-docs-reader.d.ts.map +1 -0
- package/server/dist/core/shipslog-docs-reader.js +181 -0
- package/server/dist/core/shipslog-docs-reader.js.map +1 -0
- package/server/dist/core/shipslog-docs-types.d.ts +15 -0
- package/server/dist/core/shipslog-docs-types.d.ts.map +1 -0
- package/server/dist/core/shipslog-docs-types.js +14 -0
- package/server/dist/core/shipslog-docs-types.js.map +1 -0
- package/server/dist/core/shipwright-plugin-version-reader.d.ts +15 -0
- package/server/dist/core/shipwright-plugin-version-reader.d.ts.map +1 -0
- package/server/dist/core/shipwright-plugin-version-reader.js +49 -0
- package/server/dist/core/shipwright-plugin-version-reader.js.map +1 -0
- package/server/dist/external/design-review/serve.js +27 -27
- package/server/dist/external/inbox/_derive.d.ts +2 -25
- package/server/dist/external/inbox/_derive.d.ts.map +1 -1
- package/server/dist/external/inbox/_derive.js.map +1 -1
- package/server/dist/external/inbox/_lead.d.ts +23 -0
- package/server/dist/external/inbox/_lead.d.ts.map +1 -0
- package/server/dist/external/inbox/_lead.js +83 -0
- package/server/dist/external/inbox/_lead.js.map +1 -0
- package/server/dist/external/inbox/_types.d.ts +32 -0
- package/server/dist/external/inbox/_types.d.ts.map +1 -0
- package/server/dist/external/inbox/_types.js +10 -0
- package/server/dist/external/inbox/_types.js.map +1 -0
- package/server/dist/external/inbox/routes.d.ts.map +1 -1
- package/server/dist/external/inbox/routes.js +29 -1
- package/server/dist/external/inbox/routes.js.map +1 -1
- package/server/dist/external/launch/claim-holder-gate.d.ts +42 -0
- package/server/dist/external/launch/claim-holder-gate.d.ts.map +1 -0
- package/server/dist/external/launch/claim-holder-gate.js +87 -0
- package/server/dist/external/launch/claim-holder-gate.js.map +1 -0
- package/server/dist/external/launch/routes.d.ts.map +1 -1
- package/server/dist/external/launch/routes.js +23 -19
- package/server/dist/external/launch/routes.js.map +1 -1
- package/server/dist/external/mission-context/association-write.d.ts +22 -0
- package/server/dist/external/mission-context/association-write.d.ts.map +1 -0
- package/server/dist/external/mission-context/association-write.js +90 -0
- package/server/dist/external/mission-context/association-write.js.map +1 -0
- package/server/dist/external/mission-context/routes.d.ts.map +1 -1
- package/server/dist/external/mission-context/routes.js +18 -31
- package/server/dist/external/mission-context/routes.js.map +1 -1
- package/server/dist/external/org/lead-question-threads.d.ts +46 -0
- package/server/dist/external/org/lead-question-threads.d.ts.map +1 -0
- package/server/dist/external/org/lead-question-threads.js +132 -0
- package/server/dist/external/org/lead-question-threads.js.map +1 -0
- package/server/dist/external/org/mount.d.ts +3 -0
- package/server/dist/external/org/mount.d.ts.map +1 -1
- package/server/dist/external/org/mount.js +2 -1
- package/server/dist/external/org/mount.js.map +1 -1
- package/server/dist/external/org/usage.d.ts.map +1 -1
- package/server/dist/external/org/usage.js +20 -2
- package/server/dist/external/org/usage.js.map +1 -1
- package/server/dist/external/routes.d.ts.map +1 -1
- package/server/dist/external/routes.js +10 -13
- package/server/dist/external/routes.js.map +1 -1
- package/server/dist/external/shipslog-docs/routes.d.ts +10 -0
- package/server/dist/external/shipslog-docs/routes.d.ts.map +1 -0
- package/server/dist/external/shipslog-docs/routes.js +41 -0
- package/server/dist/external/shipslog-docs/routes.js.map +1 -0
- package/server/dist/routes/diagnostics.d.ts +12 -0
- package/server/dist/routes/diagnostics.d.ts.map +1 -1
- package/server/dist/routes/diagnostics.js +4 -1
- package/server/dist/routes/diagnostics.js.map +1 -1
- package/server/dist/routes/org-threads-composite.d.ts +24 -0
- package/server/dist/routes/org-threads-composite.d.ts.map +1 -0
- package/server/dist/routes/org-threads-composite.js +51 -0
- package/server/dist/routes/org-threads-composite.js.map +1 -0
- package/server/dist/routes/org.d.ts +5 -0
- package/server/dist/routes/org.d.ts.map +1 -1
- package/server/dist/routes/org.js +13 -0
- package/server/dist/routes/org.js.map +1 -1
- package/server/dist/terminal/pty-manager.d.ts +23 -1
- package/server/dist/terminal/pty-manager.d.ts.map +1 -1
- package/server/dist/terminal/pty-manager.js +22 -3
- package/server/dist/terminal/pty-manager.js.map +1 -1
- package/server/dist/terminal/pty-write-chunker.d.ts +109 -0
- package/server/dist/terminal/pty-write-chunker.d.ts.map +1 -0
- package/server/dist/terminal/pty-write-chunker.js +234 -0
- package/server/dist/terminal/pty-write-chunker.js.map +1 -0
- package/server/dist/types/org.d.ts +19 -0
- package/server/dist/types/org.d.ts.map +1 -1
- package/server/package.json +1 -1
- package/server/profiles/PROFILE_SCHEMA_VERSION +1 -1
- package/server/profiles/README.md +27 -27
- package/server/profiles/python-plugin-monorepo.json +35 -35
- package/server/profiles/supabase-nextjs.json +253 -253
- package/server/profiles/vite-hono.json +92 -92
- package/client/dist/assets/channel-C_k-9jsa.js +0 -1
- package/client/dist/assets/classDiagram-OUVF2IWQ-CsEPq9Ac.js +0 -1
- package/client/dist/assets/classDiagram-v2-EOCWNBFH-CsEPq9Ac.js +0 -1
- package/client/dist/assets/index-6nZ1bt0a.css +0 -1
- package/client/dist/assets/index-irQhGNpn.js +0 -828
- package/client/dist/assets/stateDiagram-v2-6OUMAXLB-BW8H33dv.js +0 -1
- package/client/dist/assets/swimlanesDiagram-G3AALYLV-BaRlxcPD.js +0 -8
package/scripts/deploy-procs.mjs
CHANGED
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* deploy-procs.mjs — OS process discovery + termination for the production deploy.
|
|
3
|
-
*
|
|
4
|
-
* The impure twin of kill-targets.js (which is pure parsing, unit-tested, shared
|
|
5
|
-
* with dev-restart.js). Split out of deploy-swap.mjs to keep both files inside the
|
|
6
|
-
* 300-line convention; the swapper owns the deploy CHOREOGRAPHY, this file owns
|
|
7
|
-
* "which processes, and how do we end them".
|
|
8
|
-
*
|
|
9
|
-
* The one rule that must never be broken here: KILL ONE PROCESS, NEVER ITS TREE.
|
|
10
|
-
* See killPid().
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { execSync, spawnSync } from 'node:child_process';
|
|
14
|
-
import { createRequire } from 'node:module';
|
|
15
|
-
|
|
16
|
-
const require = createRequire(import.meta.url);
|
|
17
|
-
// Audited kill-scope helpers: state-filtered, exact-port, IPv6-aware.
|
|
18
|
-
const { parseWindowsListenerPids, buildLsofCommand } = require('./kill-targets.js');
|
|
19
|
-
|
|
20
|
-
const isWin = process.platform === 'win32';
|
|
21
|
-
const CAPTURE = { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] };
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The kill scope is EXACTLY the Hono port — never Vite. Deliberately NOT
|
|
25
|
-
* kill-targets.computeKillTargets(), which returns [PORT, VITE_PORT] for
|
|
26
|
-
* `dev:fresh` (that restarts both halves): a production deploy which swept the
|
|
27
|
-
* Vite port too would kill the operator's dev server as a side effect.
|
|
28
|
-
*
|
|
29
|
-
* @param {number} port
|
|
30
|
-
* @returns {number[]}
|
|
31
|
-
*/
|
|
32
|
-
export function killPortsFor(port) {
|
|
33
|
-
return [Number(port)];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Can we enumerate listeners at all? Windows always has netstat; POSIX needs lsof.
|
|
38
|
-
* Without it findListenerPids() returns [] for "nothing is listening" AND for "I
|
|
39
|
-
* cannot see listeners" — which would report a perfectly healthy server as a failed
|
|
40
|
-
* deploy. The pre-fix .sh had this fallback ("no lsof: settle for the process still
|
|
41
|
-
* being alive"); callers must keep it rather than lie about the outcome.
|
|
42
|
-
*/
|
|
43
|
-
export function canDiscoverListeners() {
|
|
44
|
-
if (isWin) return true;
|
|
45
|
-
try {
|
|
46
|
-
execSync('command -v lsof', { stdio: 'ignore' });
|
|
47
|
-
return true;
|
|
48
|
-
} catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** PIDs LISTENING on the port (state-filtered + exact-port on both platforms). */
|
|
54
|
-
export function findListenerPids(port) {
|
|
55
|
-
const ports = killPortsFor(port);
|
|
56
|
-
try {
|
|
57
|
-
if (isWin) {
|
|
58
|
-
// Plain `netstat -ano` (NOT `-p TCP`, which drops IPv6 listeners).
|
|
59
|
-
return parseWindowsListenerPids(execSync('netstat -ano', CAPTURE), ports);
|
|
60
|
-
}
|
|
61
|
-
const cmd = buildLsofCommand(ports);
|
|
62
|
-
if (!cmd) return [];
|
|
63
|
-
return execSync(cmd, CAPTURE).split(/\s+/).filter(Boolean);
|
|
64
|
-
} catch {
|
|
65
|
-
// netstat/lsof missing, or lsof exits 1 when nothing matches — nothing to kill.
|
|
66
|
-
return [];
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* PIDs of a `tsx watch` dev server on a webui server entry.
|
|
72
|
-
*
|
|
73
|
-
* Why this exists at all: killing only the port listener is not enough when the
|
|
74
|
-
* server runs under `tsx watch` — the watch PARENT would immediately respawn the
|
|
75
|
-
* child and re-take the port. The parent has to go too.
|
|
76
|
-
*
|
|
77
|
-
* KNOWN over-broad scope (inherited verbatim from the pre-fix inline sweep, kept
|
|
78
|
-
* for behavior parity): the match is `tsx` + `src/index.ts`, which also hits a
|
|
79
|
-
* *different* project's dev server, because a `tsx watch` command line carries the
|
|
80
|
-
* entry path relatively (`src/index.ts`) and not the repo it belongs to. Narrowing
|
|
81
|
-
* it needs per-PID cwd resolution — deliberately out of scope for the self-kill fix
|
|
82
|
-
* (see the iterate's decision drop).
|
|
83
|
-
*/
|
|
84
|
-
export function findTsxServerPids() {
|
|
85
|
-
try {
|
|
86
|
-
if (isWin) {
|
|
87
|
-
// spawnSync with shell:false — the argument reaches powershell.exe verbatim.
|
|
88
|
-
// An execSync string would cross JS -> cmd.exe -> powershell, three escaping
|
|
89
|
-
// layers deep, where one added quote silently turns the sweep into a no-op.
|
|
90
|
-
const script =
|
|
91
|
-
"Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | " +
|
|
92
|
-
"Where-Object { $_.CommandLine -match 'tsx' -and $_.CommandLine -match 'src[\\\\/]index\\.ts' } | " +
|
|
93
|
-
'Select-Object -ExpandProperty ProcessId';
|
|
94
|
-
const r = spawnSync('powershell', ['-NoProfile', '-Command', script], {
|
|
95
|
-
encoding: 'utf-8',
|
|
96
|
-
shell: false,
|
|
97
|
-
});
|
|
98
|
-
return String(r.stdout ?? '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
99
|
-
}
|
|
100
|
-
return execSync("pgrep -f 'tsx.*src/index\\.ts'", CAPTURE).split(/\s+/).filter(Boolean);
|
|
101
|
-
} catch {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Kill ONE process — never its tree.
|
|
108
|
-
*
|
|
109
|
-
* LOAD-BEARING: `taskkill /F /T` (what dev-restart.js uses) kills the target's
|
|
110
|
-
* DESCENDANTS too — and when the deploy runs from an embedded terminal, the swapper
|
|
111
|
-
* *is* a descendant of the Hono server it is about to kill. A /T sweep would
|
|
112
|
-
* therefore kill the swapper itself and re-create the very outage the swapper
|
|
113
|
-
* exists to prevent. Same on POSIX: signal the PID, never the process group.
|
|
114
|
-
*/
|
|
115
|
-
export function killPid(pid) {
|
|
116
|
-
try {
|
|
117
|
-
if (isWin) {
|
|
118
|
-
execSync(`taskkill /F /PID ${Number(pid)}`, { stdio: 'ignore' });
|
|
119
|
-
} else {
|
|
120
|
-
process.kill(Number(pid), 'SIGTERM');
|
|
121
|
-
}
|
|
122
|
-
return true;
|
|
123
|
-
} catch {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** SIGKILL escalation for anything still alive after the grace period (POSIX only). */
|
|
129
|
-
export function killPidHard(pid) {
|
|
130
|
-
try {
|
|
131
|
-
if (!isWin) process.kill(Number(pid), 'SIGKILL');
|
|
132
|
-
} catch { /* already gone */ }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Stop whatever serves the port and WAIT FOR EVIDENCE that it is free.
|
|
139
|
-
*
|
|
140
|
-
* The pre-fix scripts slept a flat 700 ms and started regardless. If the kill did
|
|
141
|
-
* not land (a PID the user may not touch, a wedged process), the new server walks
|
|
142
|
-
* straight into EADDRINUSE and dies — leaving the machine with NO server. A stale
|
|
143
|
-
* old server is a bad outcome; no server at all is a far worse one. So when the
|
|
144
|
-
* port refuses to free, the caller must NOT start: `freed: false` means "leave the
|
|
145
|
-
* old server alone and report it".
|
|
146
|
-
*
|
|
147
|
-
* POSIX gets a real grace period first (SIGTERM), because the server flushes
|
|
148
|
-
* terminal snapshots on shutdown (ADR-092/096) and an immediate axe truncates that;
|
|
149
|
-
* SIGKILL follows only if it is still there. Windows has no graceful signal —
|
|
150
|
-
* taskkill /F was immediate before this change too.
|
|
151
|
-
*
|
|
152
|
-
* @returns {Promise<{freed: boolean, killed: string[], survivor: string|null}>}
|
|
153
|
-
*/
|
|
154
|
-
export async function stopOldServer(port, { timeoutMs = 5000 } = {}) {
|
|
155
|
-
const killed = [...new Set([...findListenerPids(port), ...findTsxServerPids()])];
|
|
156
|
-
for (const pid of killed) killPid(pid);
|
|
157
|
-
|
|
158
|
-
// Cannot observe listeners (no lsof) — fall back to the pre-fix behavior.
|
|
159
|
-
if (!canDiscoverListeners()) {
|
|
160
|
-
await sleep(700);
|
|
161
|
-
return { freed: true, killed, survivor: null };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const deadline = Date.now() + timeoutMs;
|
|
165
|
-
let escalated = false;
|
|
166
|
-
while (Date.now() < deadline) {
|
|
167
|
-
await sleep(250);
|
|
168
|
-
const still = findListenerPids(port);
|
|
169
|
-
if (still.length === 0) return { freed: true, killed, survivor: null };
|
|
170
|
-
if (!escalated && Date.now() > deadline - timeoutMs / 2) {
|
|
171
|
-
for (const pid of still) killPidHard(pid);
|
|
172
|
-
escalated = true;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
return { freed: false, killed, survivor: findListenerPids(port)[0] ?? null };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Ready = a listener on the port owned by OUR child. A stale listener (an old
|
|
180
|
-
* server that survived the kill) can therefore never fake success. Fails fast when
|
|
181
|
-
* the child exits (EADDRINUSE, missing dist, crash on boot), and degrades to a
|
|
182
|
-
* liveness check where listeners are not observable.
|
|
183
|
-
*
|
|
184
|
-
* @returns {Promise<{ok: boolean, readiness: 'listener'|'process-alive', exited: boolean, exitCode: number|null}>}
|
|
185
|
-
*/
|
|
186
|
-
export async function waitForServerUp(port, child, { timeoutMs = 12000 } = {}) {
|
|
187
|
-
let exited = false;
|
|
188
|
-
let exitCode = null;
|
|
189
|
-
child.on('exit', (code) => {
|
|
190
|
-
exited = true;
|
|
191
|
-
exitCode = code;
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
if (!canDiscoverListeners()) {
|
|
195
|
-
await sleep(2500);
|
|
196
|
-
return { ok: !exited, readiness: 'process-alive', exited, exitCode };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const deadline = Date.now() + timeoutMs;
|
|
200
|
-
while (Date.now() < deadline) {
|
|
201
|
-
await sleep(500);
|
|
202
|
-
if (exited) return { ok: false, readiness: 'listener', exited, exitCode };
|
|
203
|
-
if (findListenerPids(port).includes(String(child.pid))) {
|
|
204
|
-
return { ok: true, readiness: 'listener', exited: false, exitCode: null };
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return { ok: false, readiness: 'listener', exited, exitCode };
|
|
208
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* deploy-procs.mjs — OS process discovery + termination for the production deploy.
|
|
3
|
+
*
|
|
4
|
+
* The impure twin of kill-targets.js (which is pure parsing, unit-tested, shared
|
|
5
|
+
* with dev-restart.js). Split out of deploy-swap.mjs to keep both files inside the
|
|
6
|
+
* 300-line convention; the swapper owns the deploy CHOREOGRAPHY, this file owns
|
|
7
|
+
* "which processes, and how do we end them".
|
|
8
|
+
*
|
|
9
|
+
* The one rule that must never be broken here: KILL ONE PROCESS, NEVER ITS TREE.
|
|
10
|
+
* See killPid().
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
// Audited kill-scope helpers: state-filtered, exact-port, IPv6-aware.
|
|
18
|
+
const { parseWindowsListenerPids, buildLsofCommand } = require('./kill-targets.js');
|
|
19
|
+
|
|
20
|
+
const isWin = process.platform === 'win32';
|
|
21
|
+
const CAPTURE = { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] };
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The kill scope is EXACTLY the Hono port — never Vite. Deliberately NOT
|
|
25
|
+
* kill-targets.computeKillTargets(), which returns [PORT, VITE_PORT] for
|
|
26
|
+
* `dev:fresh` (that restarts both halves): a production deploy which swept the
|
|
27
|
+
* Vite port too would kill the operator's dev server as a side effect.
|
|
28
|
+
*
|
|
29
|
+
* @param {number} port
|
|
30
|
+
* @returns {number[]}
|
|
31
|
+
*/
|
|
32
|
+
export function killPortsFor(port) {
|
|
33
|
+
return [Number(port)];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Can we enumerate listeners at all? Windows always has netstat; POSIX needs lsof.
|
|
38
|
+
* Without it findListenerPids() returns [] for "nothing is listening" AND for "I
|
|
39
|
+
* cannot see listeners" — which would report a perfectly healthy server as a failed
|
|
40
|
+
* deploy. The pre-fix .sh had this fallback ("no lsof: settle for the process still
|
|
41
|
+
* being alive"); callers must keep it rather than lie about the outcome.
|
|
42
|
+
*/
|
|
43
|
+
export function canDiscoverListeners() {
|
|
44
|
+
if (isWin) return true;
|
|
45
|
+
try {
|
|
46
|
+
execSync('command -v lsof', { stdio: 'ignore' });
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** PIDs LISTENING on the port (state-filtered + exact-port on both platforms). */
|
|
54
|
+
export function findListenerPids(port) {
|
|
55
|
+
const ports = killPortsFor(port);
|
|
56
|
+
try {
|
|
57
|
+
if (isWin) {
|
|
58
|
+
// Plain `netstat -ano` (NOT `-p TCP`, which drops IPv6 listeners).
|
|
59
|
+
return parseWindowsListenerPids(execSync('netstat -ano', CAPTURE), ports);
|
|
60
|
+
}
|
|
61
|
+
const cmd = buildLsofCommand(ports);
|
|
62
|
+
if (!cmd) return [];
|
|
63
|
+
return execSync(cmd, CAPTURE).split(/\s+/).filter(Boolean);
|
|
64
|
+
} catch {
|
|
65
|
+
// netstat/lsof missing, or lsof exits 1 when nothing matches — nothing to kill.
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* PIDs of a `tsx watch` dev server on a webui server entry.
|
|
72
|
+
*
|
|
73
|
+
* Why this exists at all: killing only the port listener is not enough when the
|
|
74
|
+
* server runs under `tsx watch` — the watch PARENT would immediately respawn the
|
|
75
|
+
* child and re-take the port. The parent has to go too.
|
|
76
|
+
*
|
|
77
|
+
* KNOWN over-broad scope (inherited verbatim from the pre-fix inline sweep, kept
|
|
78
|
+
* for behavior parity): the match is `tsx` + `src/index.ts`, which also hits a
|
|
79
|
+
* *different* project's dev server, because a `tsx watch` command line carries the
|
|
80
|
+
* entry path relatively (`src/index.ts`) and not the repo it belongs to. Narrowing
|
|
81
|
+
* it needs per-PID cwd resolution — deliberately out of scope for the self-kill fix
|
|
82
|
+
* (see the iterate's decision drop).
|
|
83
|
+
*/
|
|
84
|
+
export function findTsxServerPids() {
|
|
85
|
+
try {
|
|
86
|
+
if (isWin) {
|
|
87
|
+
// spawnSync with shell:false — the argument reaches powershell.exe verbatim.
|
|
88
|
+
// An execSync string would cross JS -> cmd.exe -> powershell, three escaping
|
|
89
|
+
// layers deep, where one added quote silently turns the sweep into a no-op.
|
|
90
|
+
const script =
|
|
91
|
+
"Get-CimInstance Win32_Process -Filter \"Name='node.exe'\" | " +
|
|
92
|
+
"Where-Object { $_.CommandLine -match 'tsx' -and $_.CommandLine -match 'src[\\\\/]index\\.ts' } | " +
|
|
93
|
+
'Select-Object -ExpandProperty ProcessId';
|
|
94
|
+
const r = spawnSync('powershell', ['-NoProfile', '-Command', script], {
|
|
95
|
+
encoding: 'utf-8',
|
|
96
|
+
shell: false,
|
|
97
|
+
});
|
|
98
|
+
return String(r.stdout ?? '').split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
return execSync("pgrep -f 'tsx.*src/index\\.ts'", CAPTURE).split(/\s+/).filter(Boolean);
|
|
101
|
+
} catch {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Kill ONE process — never its tree.
|
|
108
|
+
*
|
|
109
|
+
* LOAD-BEARING: `taskkill /F /T` (what dev-restart.js uses) kills the target's
|
|
110
|
+
* DESCENDANTS too — and when the deploy runs from an embedded terminal, the swapper
|
|
111
|
+
* *is* a descendant of the Hono server it is about to kill. A /T sweep would
|
|
112
|
+
* therefore kill the swapper itself and re-create the very outage the swapper
|
|
113
|
+
* exists to prevent. Same on POSIX: signal the PID, never the process group.
|
|
114
|
+
*/
|
|
115
|
+
export function killPid(pid) {
|
|
116
|
+
try {
|
|
117
|
+
if (isWin) {
|
|
118
|
+
execSync(`taskkill /F /PID ${Number(pid)}`, { stdio: 'ignore' });
|
|
119
|
+
} else {
|
|
120
|
+
process.kill(Number(pid), 'SIGTERM');
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** SIGKILL escalation for anything still alive after the grace period (POSIX only). */
|
|
129
|
+
export function killPidHard(pid) {
|
|
130
|
+
try {
|
|
131
|
+
if (!isWin) process.kill(Number(pid), 'SIGKILL');
|
|
132
|
+
} catch { /* already gone */ }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Stop whatever serves the port and WAIT FOR EVIDENCE that it is free.
|
|
139
|
+
*
|
|
140
|
+
* The pre-fix scripts slept a flat 700 ms and started regardless. If the kill did
|
|
141
|
+
* not land (a PID the user may not touch, a wedged process), the new server walks
|
|
142
|
+
* straight into EADDRINUSE and dies — leaving the machine with NO server. A stale
|
|
143
|
+
* old server is a bad outcome; no server at all is a far worse one. So when the
|
|
144
|
+
* port refuses to free, the caller must NOT start: `freed: false` means "leave the
|
|
145
|
+
* old server alone and report it".
|
|
146
|
+
*
|
|
147
|
+
* POSIX gets a real grace period first (SIGTERM), because the server flushes
|
|
148
|
+
* terminal snapshots on shutdown (ADR-092/096) and an immediate axe truncates that;
|
|
149
|
+
* SIGKILL follows only if it is still there. Windows has no graceful signal —
|
|
150
|
+
* taskkill /F was immediate before this change too.
|
|
151
|
+
*
|
|
152
|
+
* @returns {Promise<{freed: boolean, killed: string[], survivor: string|null}>}
|
|
153
|
+
*/
|
|
154
|
+
export async function stopOldServer(port, { timeoutMs = 5000 } = {}) {
|
|
155
|
+
const killed = [...new Set([...findListenerPids(port), ...findTsxServerPids()])];
|
|
156
|
+
for (const pid of killed) killPid(pid);
|
|
157
|
+
|
|
158
|
+
// Cannot observe listeners (no lsof) — fall back to the pre-fix behavior.
|
|
159
|
+
if (!canDiscoverListeners()) {
|
|
160
|
+
await sleep(700);
|
|
161
|
+
return { freed: true, killed, survivor: null };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const deadline = Date.now() + timeoutMs;
|
|
165
|
+
let escalated = false;
|
|
166
|
+
while (Date.now() < deadline) {
|
|
167
|
+
await sleep(250);
|
|
168
|
+
const still = findListenerPids(port);
|
|
169
|
+
if (still.length === 0) return { freed: true, killed, survivor: null };
|
|
170
|
+
if (!escalated && Date.now() > deadline - timeoutMs / 2) {
|
|
171
|
+
for (const pid of still) killPidHard(pid);
|
|
172
|
+
escalated = true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { freed: false, killed, survivor: findListenerPids(port)[0] ?? null };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Ready = a listener on the port owned by OUR child. A stale listener (an old
|
|
180
|
+
* server that survived the kill) can therefore never fake success. Fails fast when
|
|
181
|
+
* the child exits (EADDRINUSE, missing dist, crash on boot), and degrades to a
|
|
182
|
+
* liveness check where listeners are not observable.
|
|
183
|
+
*
|
|
184
|
+
* @returns {Promise<{ok: boolean, readiness: 'listener'|'process-alive', exited: boolean, exitCode: number|null}>}
|
|
185
|
+
*/
|
|
186
|
+
export async function waitForServerUp(port, child, { timeoutMs = 12000 } = {}) {
|
|
187
|
+
let exited = false;
|
|
188
|
+
let exitCode = null;
|
|
189
|
+
child.on('exit', (code) => {
|
|
190
|
+
exited = true;
|
|
191
|
+
exitCode = code;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
if (!canDiscoverListeners()) {
|
|
195
|
+
await sleep(2500);
|
|
196
|
+
return { ok: !exited, readiness: 'process-alive', exited, exitCode };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const deadline = Date.now() + timeoutMs;
|
|
200
|
+
while (Date.now() < deadline) {
|
|
201
|
+
await sleep(500);
|
|
202
|
+
if (exited) return { ok: false, readiness: 'listener', exited, exitCode };
|
|
203
|
+
if (findListenerPids(port).includes(String(child.pid))) {
|
|
204
|
+
return { ok: true, readiness: 'listener', exited: false, exitCode: null };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { ok: false, readiness: 'listener', exited, exitCode };
|
|
208
|
+
}
|