ompchamber 0.5.0 → 0.6.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/README.md +46 -1
- package/dist/client/index.html +1 -1
- package/dist/client/static/css/{index.5291f58223.css → index.51f68210a5.css} +1 -1
- package/dist/client/static/js/async/{3y.ededeebde8.js → 3y.4c2596f64c.js} +3 -3
- package/dist/client/static/js/async/73.308dff4da8.js +1 -0
- package/dist/client/static/js/async/{ty.3d93cf5f5f.js → 7g.a7814350c3.js} +1 -1
- package/dist/client/static/js/async/a.bce4542392.js +1 -0
- package/dist/client/static/js/async/g.8515cbc6f7.js +2 -0
- package/dist/client/static/js/async/hn.b645041e00.js +1 -0
- package/dist/client/static/js/async/{jg.69a4beade8.js → jg.d86ebd4013.js} +1 -1
- package/dist/client/static/js/async/l.12dd34e1be.js +1 -0
- package/dist/client/static/js/async/ls.9d9a01026c.js +104 -0
- package/dist/client/static/js/async/mw.ca1512563c.js +10 -0
- package/dist/client/static/js/async/nk.fc7e58325e.js +1 -0
- package/dist/client/static/js/async/{q.7fb4d2f374.js → q.542c184fec.js} +3 -3
- package/dist/client/static/js/async/t.cda4891526.js +1 -0
- package/dist/client/static/js/async/tr.e878658140.js +309 -0
- package/dist/client/static/js/async/uf.24ca3d49c3.js +2 -0
- package/dist/client/static/js/{index.0ac93ee878.js → index.47d3b04d5f.js} +7 -7
- package/dist/client/static/js/{uy.5f791144ce.js → uy.2e8929ed0c.js} +2 -2
- package/package.json +13 -5
- package/src/cli/lib/args.js +6 -6
- package/src/cli/lib/commands/logs.js +76 -34
- package/src/cli/lib/commands/restart.js +30 -9
- package/src/cli/lib/commands/serve.js +12 -4
- package/src/cli/lib/commands/status.js +40 -18
- package/src/cli/lib/commands/stop.js +12 -7
- package/src/cli/lib/commands/update.js +2 -5
- package/src/cli/lib/paths.js +8 -28
- package/src/cli/lib/runtime.js +118 -108
- package/src/client/components/common/code-editor/index.tsx +5 -1
- package/src/client/components/common/code-surface/index.tsx +49 -1
- package/src/client/components/common/code-surface/measure.ts +99 -0
- package/src/client/components/layout/RightActivityBar.tsx +42 -69
- package/src/client/components/layout/desktop-layout/WorkspacePanels.tsx +5 -6
- package/src/client/components/layout/desktop-layout/index.tsx +11 -9
- package/src/client/components/layout/desktop-layout/resizer/index.tsx +22 -29
- package/src/client/components/layout/desktop-layout/resizer/utils.test.ts +33 -0
- package/src/client/components/layout/desktop-layout/resizer/utils.ts +34 -0
- package/src/client/components/mobile/RightSidebar.tsx +42 -119
- package/src/client/components/mobile/mobile-main-view/Header.tsx +17 -1
- package/src/client/components/workspace/editor/index.tsx +4 -1
- package/src/client/hooks/editor/use-file-editor.test.ts +158 -0
- package/src/client/hooks/editor/use-file-editor.ts +35 -19
- package/src/server/index.ts +51 -1
- package/src/server/lib/lifecycle/identity.ts +108 -0
- package/src/server/lib/lifecycle/instance.ts +102 -0
- package/src/server/lib/lifecycle/occupant.ts +101 -0
- package/src/server/lib/lifecycle/paths.ts +50 -0
- package/src/server/lib/lifecycle/port-guard.ts +170 -0
- package/src/server/lib/lifecycle/probe.ts +121 -0
- package/src/server/plugins/static.test.ts +63 -0
- package/src/server/plugins/static.ts +28 -4
- package/src/server/routes/health/probe.ts +8 -0
- package/src/shared/lib/markdown/mermaid.ts +8 -0
- package/src/shared/lib/workspace/panel-widths.test.ts +5 -5
- package/src/shared/lib/workspace/panel-widths.ts +23 -10
- package/src/shared/lib/workspace/right-panels.ts +10 -4
- package/dist/client/static/js/async/73.50280ce58d.js +0 -1
- package/dist/client/static/js/async/a.e05da304c8.js +0 -10
- package/dist/client/static/js/async/ar.1e14da5588.js +0 -1
- package/dist/client/static/js/async/g.7a4bbd4798.js +0 -2
- package/dist/client/static/js/async/hn.7568bdb58b.js +0 -1
- package/dist/client/static/js/async/l.102a97fce6.js +0 -104
- package/dist/client/static/js/async/l6.c56640c205.js +0 -1
- package/dist/client/static/js/async/nk.6de011832e.js +0 -1
- package/dist/client/static/js/async/t.cb3fcf8615.js +0 -1
- package/dist/client/static/js/async/uf.46ad6209fa.js +0 -2
- package/dist/client/static/js/async/vn.e98e2f3b61.js +0 -309
- /package/dist/client/static/js/async/{3y.ededeebde8.js.LICENSE.txt → 3y.4c2596f64c.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.7a4bbd4798.js.LICENSE.txt → g.8515cbc6f7.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{a.e05da304c8.js.LICENSE.txt → mw.ca1512563c.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.7fb4d2f374.js.LICENSE.txt → q.542c184fec.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{uf.46ad6209fa.js.LICENSE.txt → uf.24ca3d49c3.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.0ac93ee878.js.LICENSE.txt → index.47d3b04d5f.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{uy.5f791144ce.js.LICENSE.txt → uy.2e8929ed0c.js.LICENSE.txt} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ompchamber",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Developer web console and diagnostic chamber for AI Oh-My-Pi — Elysia + Preact on Bun.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
"tsconfig.json"
|
|
33
33
|
],
|
|
34
34
|
"scripts": {
|
|
35
|
-
"dev:server": "bun run --hot src/server/index.ts",
|
|
35
|
+
"dev:server": "NODE_ENV=development bun run --hot src/server/index.ts --ompchamber-server",
|
|
36
36
|
"dev:client": "rsbuild build --watch",
|
|
37
|
-
"dev": "
|
|
38
|
-
"dev:lan": "HOST=0.0.0.0
|
|
37
|
+
"dev": "bun run dev:server & bun run dev:client; wait",
|
|
38
|
+
"dev:lan": "HOST=0.0.0.0 bun run dev:server & bun run dev:client; wait",
|
|
39
39
|
"build": "rsbuild build",
|
|
40
|
-
"start": "NODE_ENV=production bun run src/server/index.ts",
|
|
40
|
+
"start": "NODE_ENV=production bun run src/server/index.ts --ompchamber-server",
|
|
41
41
|
"typecheck": "tsc",
|
|
42
42
|
"clean": "rm -rf dist",
|
|
43
43
|
"lint": "tsc --noEmit",
|
|
@@ -65,10 +65,18 @@
|
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@rsbuild/core": "^2.2.7",
|
|
67
67
|
"@rsbuild/plugin-preact": "^2.1.0",
|
|
68
|
+
"@semantic-release/changelog": "^7.0.0",
|
|
69
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
70
|
+
"@semantic-release/git": "^11.0.1",
|
|
71
|
+
"@semantic-release/github": "^12.0.10",
|
|
72
|
+
"@semantic-release/npm": "^13.2.0",
|
|
73
|
+
"@semantic-release/release-notes-generator": "^14.1.1",
|
|
68
74
|
"@tailwindcss/postcss": "^4.1.14",
|
|
69
75
|
"@types/bun": "^1.4.2",
|
|
70
76
|
"@types/node": "^22.14.0",
|
|
77
|
+
"conventional-changelog-conventionalcommits": "^9.3.1",
|
|
71
78
|
"happy-dom": "^20.14.5",
|
|
79
|
+
"semantic-release": "^25.0.9",
|
|
72
80
|
"tailwindcss": "^4.1.14",
|
|
73
81
|
"typescript": "~5.8.2"
|
|
74
82
|
}
|
package/src/cli/lib/args.js
CHANGED
|
@@ -188,20 +188,20 @@ USAGE:
|
|
|
188
188
|
COMMANDS:
|
|
189
189
|
serve Start the OMPChamber web server (default when no command is given)
|
|
190
190
|
update Update OMPChamber to the latest GitHub release
|
|
191
|
-
stop Stop
|
|
192
|
-
restart Stop and then start
|
|
193
|
-
status Show
|
|
194
|
-
logs Print or follow
|
|
191
|
+
stop Stop running OMPChamber instances (every one, or --port <port>)
|
|
192
|
+
restart Stop and then start again (every instance, or --port <port>)
|
|
193
|
+
status Show running instances (every one, or --port <port>)
|
|
194
|
+
logs Print or follow server logs (every instance, or --port <port>)
|
|
195
195
|
|
|
196
196
|
OPTIONS:
|
|
197
|
-
-p, --port <port> Web server port (default: 3000)
|
|
197
|
+
-p, --port <port> Web server port (default: 3000); scopes status/stop/restart/logs
|
|
198
198
|
--host <address> Bind address (default: 127.0.0.1)
|
|
199
199
|
--hostname <address> Alias for --host
|
|
200
200
|
--lan Bind to 0.0.0.0 for LAN access
|
|
201
201
|
--prod Run the production build instead of the dev server
|
|
202
202
|
--foreground Run the server in the foreground (no daemon)
|
|
203
203
|
--no-daemon Alias for --foreground
|
|
204
|
-
--all
|
|
204
|
+
--all Explicit form of the default for stop/restart (every instance)
|
|
205
205
|
-c, --check Report whether a newer release exists without installing it
|
|
206
206
|
--force Reinstall even when already up to date
|
|
207
207
|
--no-restart Do not restart a running instance after updating
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
-
// `ompchamber logs` — print or follow the server
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// `ompchamber logs` — print or follow the server logs.
|
|
2
|
+
//
|
|
3
|
+
// Scoping matches `status`/`stop`/`restart`: without `--port` the logs of every
|
|
4
|
+
// live instance are shown, each behind a `==> path <==` header, and `--port
|
|
5
|
+
// <port>` restricts the output to that instance. With nothing running, the log
|
|
6
|
+
// of the requested (or default) port is shown so a stopped server's output is
|
|
7
|
+
// still readable.
|
|
8
|
+
|
|
9
|
+
import { findLiveInstance, listLiveInstances } from '@/cli/lib/runtime.js';
|
|
4
10
|
import { getLogFilePath } from '@/cli/lib/paths.js';
|
|
5
|
-
import { log, printJson, fail, isJson, isQuiet } from '@/cli/lib/output.js';
|
|
11
|
+
import { log, warn, printJson, fail, isJson, isQuiet } from '@/cli/lib/output.js';
|
|
6
12
|
const DEFAULT_LINES = 50;
|
|
7
13
|
const FOLLOW_INTERVAL_MS = 300;
|
|
8
14
|
|
|
@@ -39,36 +45,47 @@ function writeLines(lines) {
|
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
function
|
|
43
|
-
|
|
48
|
+
async function readTail(target, lineCount) {
|
|
49
|
+
const content = await Bun.file(target.logFile).text();
|
|
50
|
+
return { port: target.port, logFile: target.logFile, lines: splitLines(content).slice(-lineCount) };
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
/** Per-file read position, so following several logs keeps them independent. */
|
|
54
|
+
async function followFiles(targets, multiple) {
|
|
55
|
+
const state = [];
|
|
56
|
+
for (const target of targets) {
|
|
57
|
+
let position = 0;
|
|
58
|
+
try {
|
|
59
|
+
position = (await Bun.file(target.logFile).stat()).size;
|
|
60
|
+
} catch {
|
|
61
|
+
position = 0;
|
|
62
|
+
}
|
|
63
|
+
state.push({ target, position });
|
|
64
|
+
}
|
|
49
65
|
|
|
50
|
-
let position = Buffer.byteLength(initial, 'utf8');
|
|
51
66
|
let running = true;
|
|
52
67
|
const stop = () => { running = false; };
|
|
53
|
-
|
|
54
68
|
process.once('SIGINT', stop);
|
|
55
69
|
process.once('SIGTERM', stop);
|
|
70
|
+
|
|
56
71
|
try {
|
|
57
72
|
while (running) {
|
|
58
|
-
await sleep(FOLLOW_INTERVAL_MS);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
await Bun.sleep(FOLLOW_INTERVAL_MS);
|
|
74
|
+
for (const entry of state) {
|
|
75
|
+
let size;
|
|
76
|
+
try {
|
|
77
|
+
size = (await Bun.file(entry.target.logFile).stat()).size;
|
|
78
|
+
} catch {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (size < entry.position) entry.position = 0;
|
|
82
|
+
if (size === entry.position) continue;
|
|
83
|
+
|
|
84
|
+
if (multiple) process.stdout.write(`==> ${entry.target.logFile} <==\n`);
|
|
85
|
+
const chunk = await Bun.file(entry.target.logFile).slice(entry.position, size).text();
|
|
86
|
+
entry.position = size;
|
|
87
|
+
process.stdout.write(chunk);
|
|
65
88
|
}
|
|
66
|
-
if (size < position) position = 0;
|
|
67
|
-
if (size === position) continue;
|
|
68
|
-
|
|
69
|
-
const chunk = await Bun.file(filePath).slice(position, size).text();
|
|
70
|
-
position = size;
|
|
71
|
-
process.stdout.write(chunk);
|
|
72
89
|
}
|
|
73
90
|
} finally {
|
|
74
91
|
process.removeListener('SIGINT', stop);
|
|
@@ -80,11 +97,28 @@ export async function run(options) {
|
|
|
80
97
|
const json = isJson();
|
|
81
98
|
const quiet = isQuiet();
|
|
82
99
|
const requested = explicitPort(options);
|
|
83
|
-
const
|
|
84
|
-
const logFile = live?.logFile ?? getLogFilePath(requested ?? resolvePort(options));
|
|
100
|
+
const fallbackPort = requested ?? resolvePort(options);
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
const instances = requested !== null
|
|
103
|
+
? [await findLiveInstance(requested)].filter(Boolean)
|
|
104
|
+
: await listLiveInstances();
|
|
105
|
+
|
|
106
|
+
const candidates = instances.length > 0
|
|
107
|
+
? instances.map((entry) => ({ port: entry.port, logFile: entry.logFile ?? getLogFilePath(entry.port) }))
|
|
108
|
+
: [{ port: fallbackPort, logFile: getLogFilePath(fallbackPort) }];
|
|
109
|
+
|
|
110
|
+
const targets = [];
|
|
111
|
+
const skipped = [];
|
|
112
|
+
for (const candidate of candidates) {
|
|
113
|
+
if (await Bun.file(candidate.logFile).exists()) targets.push(candidate);
|
|
114
|
+
else skipped.push(candidate);
|
|
115
|
+
}
|
|
116
|
+
if (targets.length === 0) {
|
|
117
|
+
// A server started outside the CLI (`bun run dev`) logs to its own terminal,
|
|
118
|
+
// so its recorded port has no file here even though it is very much alive.
|
|
119
|
+
fail(skipped.length > 0 && instances.length > 0
|
|
120
|
+
? `No log file at ${skipped[0].logFile}.\nPort ${skipped[0].port} was not started by the CLI — its output goes to the terminal that started it.`
|
|
121
|
+
: `No log file found at ${candidates[0].logFile}.\nStart the server first with \`ompchamber serve\`.`);
|
|
88
122
|
}
|
|
89
123
|
|
|
90
124
|
const requestedLines = Number(options?.lines);
|
|
@@ -93,16 +127,24 @@ export async function run(options) {
|
|
|
93
127
|
: DEFAULT_LINES;
|
|
94
128
|
|
|
95
129
|
if (json) {
|
|
96
|
-
|
|
130
|
+
const tails = [];
|
|
131
|
+
for (const target of targets) tails.push(await readTail(target, lineCount));
|
|
132
|
+
printJson({ instances: tails, skipped: skipped.map((entry) => ({ port: entry.port, logFile: entry.logFile })) });
|
|
97
133
|
return;
|
|
98
134
|
}
|
|
99
135
|
|
|
100
|
-
|
|
136
|
+
const multiple = targets.length > 1;
|
|
137
|
+
if (!quiet) {
|
|
138
|
+
log(multiple ? `Log files: ${targets.length} instances` : `Log file: ${targets[0].logFile}`);
|
|
139
|
+
if (skipped.length > 0) {
|
|
140
|
+
warn(`Skipped ${skipped.length} instance(s) without a CLI log file: port ${skipped.map((entry) => entry.port).join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
101
143
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
144
|
+
for (const target of targets) {
|
|
145
|
+
if (multiple) process.stdout.write(`==> ${target.logFile} <==\n`);
|
|
146
|
+
writeLines(splitLines(await Bun.file(target.logFile).text()).slice(-lineCount));
|
|
105
147
|
}
|
|
106
148
|
|
|
107
|
-
|
|
149
|
+
if (options?.follow) await followFiles(targets, multiple);
|
|
108
150
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
// `ompchamber restart` — stop
|
|
1
|
+
// `ompchamber restart` — stop live instances, then serve again.
|
|
2
|
+
//
|
|
3
|
+
// Scoping matches `status` and `stop`: without `--port` every live instance is
|
|
4
|
+
// restarted on its own recorded port/host/mode, and `--port <port>` restricts
|
|
5
|
+
// the restart to that one. With nothing running, the command is simply `serve`.
|
|
2
6
|
|
|
3
|
-
import { findLiveInstance, stopInstance } from '@/cli/lib/runtime.js';
|
|
7
|
+
import { findLiveInstance, listLiveInstances, stopInstance } from '@/cli/lib/runtime.js';
|
|
4
8
|
import { STOP_TIMEOUT_MS } from '@/cli/lib/process-lifecycle.js';
|
|
5
9
|
import { log, isJson, isQuiet } from '@/cli/lib/output.js';
|
|
6
10
|
import { run as runServe } from '@/cli/lib/commands/serve.js';
|
|
@@ -15,14 +19,31 @@ function explicitPort(options) {
|
|
|
15
19
|
export async function run(options, ctx) {
|
|
16
20
|
const json = isJson();
|
|
17
21
|
const quiet = isQuiet();
|
|
18
|
-
const
|
|
22
|
+
const requested = explicitPort(options);
|
|
19
23
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
const targets = requested !== null
|
|
25
|
+
? [await findLiveInstance(requested)].filter(Boolean)
|
|
26
|
+
: await listLiveInstances();
|
|
27
|
+
|
|
28
|
+
if (targets.length === 0) return runServe(options, ctx);
|
|
29
|
+
|
|
30
|
+
for (const live of targets) {
|
|
31
|
+
if (!json && !quiet) log(`Stopping OMPChamber on port ${live.port} (pid ${live.pid})...`);
|
|
24
32
|
await stopInstance(live, { timeoutMs: STOP_TIMEOUT_MS });
|
|
25
|
-
}
|
|
26
33
|
|
|
27
|
-
|
|
34
|
+
// Reuse the recorded port/host/mode so an instance started on a non-default
|
|
35
|
+
// port, or bound to the LAN, comes back the way it was found.
|
|
36
|
+
await runServe(
|
|
37
|
+
{
|
|
38
|
+
...options,
|
|
39
|
+
port: live.port,
|
|
40
|
+
host: live.host,
|
|
41
|
+
lan: live.host === '0.0.0.0',
|
|
42
|
+
prod: live.mode === 'prod',
|
|
43
|
+
foreground: false,
|
|
44
|
+
all: false,
|
|
45
|
+
},
|
|
46
|
+
ctx,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
28
49
|
}
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
buildServeInvocation,
|
|
7
7
|
waitForHealth,
|
|
8
8
|
findLiveInstance,
|
|
9
|
-
probeHost,
|
|
10
9
|
} from '@/cli/lib/runtime.js';
|
|
10
|
+
import { probeHost } from '@/server/lib/lifecycle/probe';
|
|
11
11
|
import { log, ok, warn, fail, printJson, isJson, isQuiet } from '@/cli/lib/output.js';
|
|
12
12
|
import { wireChildProcessLifecycle } from '@/cli/lib/process-lifecycle.js';
|
|
13
13
|
import { ompStartupError } from '@/server/lib/omp/core/startup';
|
|
@@ -64,14 +64,22 @@ export async function run(options, ctx) {
|
|
|
64
64
|
|
|
65
65
|
const existing = await findLiveInstance(port);
|
|
66
66
|
if (existing) {
|
|
67
|
-
|
|
67
|
+
// Refused here as well as in the server (lib/lifecycle/port-guard) so the
|
|
68
|
+
// CLI reports it immediately instead of spawning a child that exits during
|
|
69
|
+
// the health wait. Nothing is stopped: freeing a port is `ompchamber stop`.
|
|
70
|
+
fail(
|
|
71
|
+
`Port ${port} is already served by OMPChamber (pid ${existing.pid}, ${existing.mode}).`
|
|
72
|
+
+ '\n Nothing was stopped — a starting instance never stops another one.'
|
|
73
|
+
+ `\n Stop it first: ompchamber stop --port ${port}`
|
|
74
|
+
+ `\n Or start on another port: ompchamber serve --port <port>`,
|
|
75
|
+
);
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
if (options?.foreground) {
|
|
71
79
|
return runForeground({ pkgRoot, mode, port, host, quiet });
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
const { entry } = spawnDetachedServer({ pkgRoot, mode, port, host });
|
|
82
|
+
const { entry } = spawnDetachedServer({ pkgRoot, mode, port, host, launchMode: 'daemon' });
|
|
75
83
|
|
|
76
84
|
if (!quiet && !json) {
|
|
77
85
|
log(`Starting OMPChamber (${mode}) on ${entry.url} (pid ${entry.pid})...`);
|
|
@@ -96,7 +104,7 @@ export async function run(options, ctx) {
|
|
|
96
104
|
}
|
|
97
105
|
|
|
98
106
|
function runForeground({ pkgRoot, mode, port, host, quiet }) {
|
|
99
|
-
const { file, args, env } = buildServeInvocation({ pkgRoot, mode, port, host });
|
|
107
|
+
const { file, args, env } = buildServeInvocation({ pkgRoot, mode, port, host, launchMode: 'foreground' });
|
|
100
108
|
const url = `http://${probeHost(host)}:${port}`;
|
|
101
109
|
if (!quiet) {
|
|
102
110
|
log(`Running OMPChamber (${mode}) in the foreground on ${url} (Ctrl+C to stop)...`);
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
// `ompchamber status` — report the live
|
|
1
|
+
// `ompchamber status` — report the live instances, their health and log paths.
|
|
2
|
+
//
|
|
3
|
+
// Without `--port` every live instance is reported: a CLI server on one port, a
|
|
4
|
+
// `bun run dev` server on another and a production server on a third are three
|
|
5
|
+
// separate processes, and a status command that showed only the first would
|
|
6
|
+
// hide the two you forgot about. `--port <port>` narrows the report to that
|
|
7
|
+
// instance.
|
|
2
8
|
|
|
3
|
-
import { findLiveInstance,
|
|
9
|
+
import { findLiveInstance, listLiveInstances, waitForHealth } from '@/cli/lib/runtime.js';
|
|
10
|
+
import { probeHost } from '@/server/lib/lifecycle/probe';
|
|
4
11
|
import { log, ok, printJson, isJson } from '@/cli/lib/output.js';
|
|
5
12
|
|
|
6
13
|
function explicitPort(options) {
|
|
@@ -30,34 +37,49 @@ function formatHealth(health) {
|
|
|
30
37
|
return `ok (version ${version}${mock})`;
|
|
31
38
|
}
|
|
32
39
|
|
|
40
|
+
/** One block per instance; the field names match the single-instance output. */
|
|
41
|
+
function printInstance(entry) {
|
|
42
|
+
log(` instance: ${entry.port}`);
|
|
43
|
+
log(` pid: ${entry.pid}`);
|
|
44
|
+
log(` port: ${entry.port}`);
|
|
45
|
+
log(` url: ${entry.url ?? `http://${probeHost(entry.host)}:${entry.port}`}`);
|
|
46
|
+
log(` mode: ${entry.mode ?? 'unknown'}`);
|
|
47
|
+
log(` launch: ${entry.launchMode ?? 'unknown'}${entry.source ? ` (${entry.source})` : ''}`);
|
|
48
|
+
log(` uptime: ${formatUptime(entry.startedAt)}`);
|
|
49
|
+
log(` health: ${formatHealth(entry.health)}`);
|
|
50
|
+
log(` log: ${entry.logFile}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
33
53
|
export async function run(options) {
|
|
34
54
|
const json = isJson();
|
|
35
|
-
const
|
|
55
|
+
const requested = explicitPort(options);
|
|
56
|
+
|
|
57
|
+
const instances = requested !== null
|
|
58
|
+
? [await findLiveInstance(requested)].filter(Boolean)
|
|
59
|
+
: await listLiveInstances();
|
|
36
60
|
|
|
37
|
-
if (
|
|
61
|
+
if (instances.length === 0) {
|
|
38
62
|
if (json) {
|
|
39
|
-
printJson({ running: false });
|
|
63
|
+
printJson(requested !== null ? { running: false, port: requested } : { running: false, instances: [] });
|
|
40
64
|
return;
|
|
41
65
|
}
|
|
42
|
-
log('OMPChamber is not running.');
|
|
66
|
+
log(requested !== null ? `No OMPChamber instance on port ${requested}.` : 'OMPChamber is not running.');
|
|
43
67
|
return;
|
|
44
68
|
}
|
|
45
69
|
|
|
46
|
-
const
|
|
47
|
-
const
|
|
70
|
+
const reported = [];
|
|
71
|
+
for (const entry of instances) {
|
|
72
|
+
reported.push({ ...entry, health: await waitForHealth(entry.port, entry.host, 4000) });
|
|
73
|
+
}
|
|
48
74
|
|
|
49
75
|
if (json) {
|
|
50
|
-
printJson(
|
|
76
|
+
printJson(requested !== null ? { ...reported[0], running: true } : { running: true, instances: reported });
|
|
51
77
|
return;
|
|
52
78
|
}
|
|
53
79
|
|
|
54
|
-
ok('OMPChamber is running');
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
log(` mode: ${live.mode ?? 'unknown'}`);
|
|
60
|
-
log(` uptime: ${formatUptime(live.startedAt)}`);
|
|
61
|
-
log(` health: ${formatHealth(health)}`);
|
|
62
|
-
log(` log: ${live.logFile}`);
|
|
80
|
+
ok(reported.length === 1 ? 'OMPChamber is running' : `OMPChamber is running — ${reported.length} instances`);
|
|
81
|
+
for (const [index, entry] of reported.entries()) {
|
|
82
|
+
if (index > 0) log('');
|
|
83
|
+
printInstance(entry);
|
|
84
|
+
}
|
|
63
85
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// `ompchamber stop` — stop one or every live OMPChamber instance.
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { listLiveInstances, findLiveInstance, stopInstance } from '@/cli/lib/runtime.js';
|
|
4
4
|
import { STOP_TIMEOUT_MS } from '@/cli/lib/process-lifecycle.js';
|
|
5
5
|
import { ok, warn, printJson, isJson } from '@/cli/lib/output.js';
|
|
6
6
|
|
|
@@ -27,13 +27,18 @@ function explicitPort(options) {
|
|
|
27
27
|
|
|
28
28
|
export async function run(options) {
|
|
29
29
|
const json = isJson();
|
|
30
|
+
const requested = explicitPort(options);
|
|
30
31
|
|
|
32
|
+
// No `--port` means every live instance: a CLI server, a dev run and a
|
|
33
|
+
// production server are three processes, and `stop` without a port is the
|
|
34
|
+
// command that clears all of them. `--all` is the explicit spelling of the
|
|
35
|
+
// same default, kept because it reads well in scripts.
|
|
31
36
|
let targets;
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
} else {
|
|
35
|
-
const live = await findLiveInstance(explicitPort(options));
|
|
37
|
+
if (requested !== null) {
|
|
38
|
+
const live = await findLiveInstance(requested);
|
|
36
39
|
targets = live ? [live] : [];
|
|
40
|
+
} else {
|
|
41
|
+
targets = await listLiveInstances();
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
if (targets.length === 0) {
|
|
@@ -41,9 +46,9 @@ export async function run(options) {
|
|
|
41
46
|
printJson({ stopped: [] });
|
|
42
47
|
return;
|
|
43
48
|
}
|
|
44
|
-
warn(
|
|
49
|
+
warn(requested === null
|
|
45
50
|
? 'No running OMPChamber instances found.'
|
|
46
|
-
: `No running OMPChamber instance on port ${
|
|
51
|
+
: `No running OMPChamber instance on port ${requested}.`);
|
|
47
52
|
return;
|
|
48
53
|
}
|
|
49
54
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// build.
|
|
7
7
|
|
|
8
8
|
import { color, configure, error, log, ok, warn, printJson, isJson, isQuiet } from '@/cli/lib/output.js';
|
|
9
|
-
import {
|
|
9
|
+
import { listLiveInstances, findLiveInstance, stopInstance } from '@/cli/lib/runtime.js';
|
|
10
10
|
import { STOP_TIMEOUT_MS } from '@/cli/lib/process-lifecycle.js';
|
|
11
11
|
import { run as runServe } from '@/cli/lib/commands/serve.js';
|
|
12
12
|
import { resolveInstallContext, resolveOmpChamberVersion, updateOmpChamber } from '@/server/lib/updates/install';
|
|
@@ -17,10 +17,7 @@ import { resolveInstallContext, resolveOmpChamberVersion, updateOmpChamber } fro
|
|
|
17
17
|
* non-default port comes back on that same port.
|
|
18
18
|
*/
|
|
19
19
|
async function liveInstances(options) {
|
|
20
|
-
if (options?.all)
|
|
21
|
-
const entries = await listRegistries();
|
|
22
|
-
return entries.filter((entry) => entry && isProcessAlive(Number(entry.pid)));
|
|
23
|
-
}
|
|
20
|
+
if (options?.all) return await listLiveInstances();
|
|
24
21
|
const live = await findLiveInstance(null);
|
|
25
22
|
return live ? [live] : [];
|
|
26
23
|
}
|
package/src/cli/lib/paths.js
CHANGED
|
@@ -1,29 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { getDataDir, getRunDir } from '@/server/lib/lifecycle/paths';
|
|
3
|
+
import { joinPath } from '@/cli/lib/path-utils.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* CLI-side paths.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* The data directory and the per-port run directory are owned by
|
|
9
|
+
* `src/server/lib/lifecycle/paths` — the server writes its instance record
|
|
10
|
+
* there, and the CLI must read the same file. Only the log location is
|
|
11
|
+
* CLI-specific: the CLI redirects the daemon's stdout into it, so the server
|
|
12
|
+
* never needs to know where the logs live.
|
|
9
13
|
*/
|
|
10
|
-
export function getDataDir() {
|
|
11
|
-
const override = Bun.env.OMPCHAMBER_DATA_DIR;
|
|
12
|
-
if (typeof override === 'string' && override.trim().length > 0) {
|
|
13
|
-
return resolvePath(override.trim());
|
|
14
|
-
}
|
|
15
|
-
return joinPath(homeDir(), '.ompchamber');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Directory holding per-instance registry files (`<port>.json`).
|
|
20
|
-
* Created on demand with owner-only permissions.
|
|
21
|
-
*/
|
|
22
|
-
export function getRunDir() {
|
|
23
|
-
const dir = joinPath(getDataDir(), 'run');
|
|
24
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
25
|
-
return dir;
|
|
26
|
-
}
|
|
27
14
|
|
|
28
15
|
/**
|
|
29
16
|
* Directory holding per-instance log files.
|
|
@@ -34,13 +21,6 @@ export function getLogsDir() {
|
|
|
34
21
|
return dir;
|
|
35
22
|
}
|
|
36
23
|
|
|
37
|
-
/**
|
|
38
|
-
* Registry file describing the running instance bound to `port`.
|
|
39
|
-
*/
|
|
40
|
-
export function getRegistryPath(port) {
|
|
41
|
-
return joinPath(getRunDir(), `${port}.json`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
24
|
/**
|
|
45
25
|
* Log file for the running instance bound to `port`.
|
|
46
26
|
*/
|