@gachlab/devup 0.9.3 → 0.10.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.
@@ -9,6 +9,25 @@ import type { ProcessState } from '../process/types.js';
9
9
  * to the socket file already has the same uid as the devup process — no
10
10
  * additional auth needed. Strictly local; TCP exposure is intentionally
11
11
  * out of scope. */
12
+ export interface ServiceStatEntry {
13
+ cpu: number;
14
+ memMB: number;
15
+ }
16
+ export interface StatsResult {
17
+ services: Record<string, ServiceStatEntry>;
18
+ system: {
19
+ totalMemMB: number;
20
+ freeMemMB: number;
21
+ cpuCores: number;
22
+ };
23
+ }
24
+ export interface ProxyInfo {
25
+ active: boolean;
26
+ provider: string;
27
+ domain: string;
28
+ tls: boolean;
29
+ routes: Record<string, string>;
30
+ }
12
31
  export interface RpcContext {
13
32
  /** State of every service (read-only snapshot). */
14
33
  states(): Map<string, ProcessState>;
@@ -23,6 +42,10 @@ export interface RpcContext {
23
42
  watchLogs(svcName: string | null, onLine: (svc: string, line: string) => void): () => void;
24
43
  /** Subscribe to service-state changes. Returns an unsubscribe function. */
25
44
  watchStatus(onUpdate: (name: string, state: ProcessState) => void): () => void;
45
+ /** Per-service CPU/mem stats + system totals. */
46
+ getStats(): Promise<StatsResult>;
47
+ /** Active proxy configuration, or null when no proxy is running. */
48
+ getProxyInfo(): ProxyInfo | null;
26
49
  }
27
50
  export interface SocketServerHandle {
28
51
  server: Server;
@@ -1 +1 @@
1
- {"version":3,"file":"socket-server.d.ts","sourceRoot":"","sources":["../../src/control-plane/socket-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAMlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;oBAQoB;AAEpB,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACpC,iCAAiC;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,8BAA8B;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D;2CACuC;IACvC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC3F,2EAA2E;IAC3E,WAAW,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAChF;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,UAAU,EACf,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC1D,OAAO,CAAC,kBAAkB,CAAC,CA+C7B"}
1
+ {"version":3,"file":"socket-server.d.ts","sourceRoot":"","sources":["../../src/control-plane/socket-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAe,MAAM,UAAU,CAAC;AAMlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAExD;;;;;;;;oBAQoB;AAEpB,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC3C,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACpC,iCAAiC;IACjC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,8BAA8B;IAC9B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yFAAyF;IACzF,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC5D;2CACuC;IACvC,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC3F,2EAA2E;IAC3E,WAAW,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC/E,iDAAiD;IACjD,QAAQ,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IACjC,oEAAoE;IACpE,YAAY,IAAI,SAAS,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,UAAU,EACf,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GAC1D,OAAO,CAAC,kBAAkB,CAAC,CA+C7B"}
package/dist/index.js CHANGED
@@ -847,8 +847,10 @@ async function dispatch(method, params, ctx) {
847
847
  for (const [name, st] of ctx.states()) {
848
848
  out.push(serializeState(name, st));
849
849
  }
850
- return { services: out };
850
+ return { services: out, proxy: ctx.getProxyInfo() };
851
851
  }
852
+ case "stats":
853
+ return await ctx.getStats();
852
854
  case "restart": {
853
855
  const svc = stringOrThrow(params["svc"] ?? params["service"], "svc");
854
856
  await ctx.restart(svc);
@@ -951,7 +953,7 @@ function openStream(socketPath, method, params, onFrame, onError) {
951
953
  import { spawn as spawn4 } from "child_process";
952
954
  import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, existsSync as existsSync10, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3, createReadStream } from "fs";
953
955
  import { join as join8 } from "path";
954
- import { homedir as homedir3 } from "os";
956
+ import { homedir as homedir3, totalmem, freemem, cpus } from "os";
955
957
  import { setTimeout as sleep } from "timers/promises";
956
958
  import { createInterface as createInterface3 } from "readline";
957
959
 
@@ -1874,6 +1876,7 @@ async function daemonBody(opts) {
1874
1876
  const logBus = new Broadcaster();
1875
1877
  const stateBus = new Broadcaster();
1876
1878
  const lazyProxies = /* @__PURE__ */ new Map();
1879
+ const prevCpuMap = /* @__PURE__ */ new Map();
1877
1880
  let externals = [];
1878
1881
  let socket = null;
1879
1882
  let healthTimer = null;
@@ -1983,7 +1986,48 @@ async function daemonBody(opts) {
1983
1986
  watchLogs: (svcName, onLine) => logBus.subscribe(({ svc, text }) => {
1984
1987
  if (svcName === null || svc === svcName) onLine(svc, text);
1985
1988
  }),
1986
- watchStatus: (onUpdate) => stateBus.subscribe(({ name, state }) => onUpdate(name, state))
1989
+ watchStatus: (onUpdate) => stateBus.subscribe(({ name, state }) => onUpdate(name, state)),
1990
+ async getStats() {
1991
+ const pids = [];
1992
+ const pidToName = /* @__PURE__ */ new Map();
1993
+ for (const [name, st] of mgr.state) {
1994
+ if (st.pid) {
1995
+ pids.push(st.pid);
1996
+ pidToName.set(st.pid, name);
1997
+ }
1998
+ }
1999
+ const raw = pids.length ? await platform.getProcessStats(pids) : /* @__PURE__ */ new Map();
2000
+ const services2 = {};
2001
+ for (const [name] of mgr.state) {
2002
+ services2[name] = { cpu: 0, memMB: 0 };
2003
+ }
2004
+ for (const [pid, data] of raw) {
2005
+ const name = pidToName.get(pid);
2006
+ if (!name) continue;
2007
+ const prev = prevCpuMap.get(name) ?? { time: Date.now(), cpu: 0 };
2008
+ const cpu = calcCpuPercent(data.cpuSeconds, prev.cpu, prev.time);
2009
+ prevCpuMap.set(name, { time: Date.now(), cpu: data.cpuSeconds });
2010
+ services2[name] = { cpu: Math.round(cpu * 10) / 10, memMB: Math.round(data.rss / 1024 * 10) / 10 };
2011
+ }
2012
+ return {
2013
+ services: services2,
2014
+ system: {
2015
+ totalMemMB: Math.round(totalmem() / 1024 / 1024),
2016
+ freeMemMB: Math.round(freemem() / 1024 / 1024),
2017
+ cpuCores: cpus().length
2018
+ }
2019
+ };
2020
+ },
2021
+ getProxyInfo() {
2022
+ if (!proxyProvider || !proxyOpts || !cliArgs.proxy) return null;
2023
+ return {
2024
+ active: true,
2025
+ provider: proxyProvider.name,
2026
+ domain: proxyOpts.domain,
2027
+ tls: proxyOpts.tls,
2028
+ routes: proxyOpts.routes
2029
+ };
2030
+ }
1987
2031
  }, { onLog: (msg) => writeDevupLog(msg) });
1988
2032
  healthTimer = setInterval(() => {
1989
2033
  void mgr.checkAllHealth();
@@ -3102,8 +3146,10 @@ function useLogsPause(setPaused, logsPaused, logsScrollOffset) {
3102
3146
  import { useEffect as useEffect5, useRef as useRef3 } from "react";
3103
3147
  import { createInterface as createInterface5 } from "readline";
3104
3148
  import { createReadStream as createReadStream3, existsSync as existsSync15 } from "fs";
3105
- function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBus) {
3149
+ import { totalmem as totalmem2, freemem as freemem2, cpus as cpus2 } from "os";
3150
+ function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBus, platform, proxy) {
3106
3151
  const handleRef = useRef3(null);
3152
+ const prevCpuMap = useRef3(/* @__PURE__ */ new Map());
3107
3153
  useEffect5(() => {
3108
3154
  if (!manager) return;
3109
3155
  let handle = null;
@@ -3135,6 +3181,47 @@ function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBu
3135
3181
  },
3136
3182
  watchStatus: (onUpdate) => {
3137
3183
  return stateBus.subscribe(({ name, state }) => onUpdate(name, state));
3184
+ },
3185
+ async getStats() {
3186
+ const pids = [];
3187
+ const pidToName = /* @__PURE__ */ new Map();
3188
+ for (const [name, st] of manager.state) {
3189
+ if (st.pid) {
3190
+ pids.push(st.pid);
3191
+ pidToName.set(st.pid, name);
3192
+ }
3193
+ }
3194
+ const raw = pids.length ? await platform.getProcessStats(pids) : /* @__PURE__ */ new Map();
3195
+ const services = {};
3196
+ for (const [name] of manager.state) {
3197
+ services[name] = { cpu: 0, memMB: 0 };
3198
+ }
3199
+ for (const [pid, data] of raw) {
3200
+ const name = pidToName.get(pid);
3201
+ if (!name) continue;
3202
+ const prev = prevCpuMap.current.get(name) ?? { time: Date.now(), cpu: 0 };
3203
+ const cpu = calcCpuPercent(data.cpuSeconds, prev.cpu, prev.time);
3204
+ prevCpuMap.current.set(name, { time: Date.now(), cpu: data.cpuSeconds });
3205
+ services[name] = { cpu: Math.round(cpu * 10) / 10, memMB: Math.round(data.rss / 1024 * 10) / 10 };
3206
+ }
3207
+ return {
3208
+ services,
3209
+ system: {
3210
+ totalMemMB: Math.round(totalmem2() / 1024 / 1024),
3211
+ freeMemMB: Math.round(freemem2() / 1024 / 1024),
3212
+ cpuCores: cpus2().length
3213
+ }
3214
+ };
3215
+ },
3216
+ getProxyInfo() {
3217
+ if (!proxy) return null;
3218
+ return {
3219
+ active: true,
3220
+ provider: proxy.provider.name,
3221
+ domain: proxy.opts.domain,
3222
+ tls: proxy.opts.tls,
3223
+ routes: proxy.opts.routes
3224
+ };
3138
3225
  }
3139
3226
  }, { onLog: (msg) => pushLog("devup", msg, 12) });
3140
3227
  handleRef.current = handle;
@@ -3146,7 +3233,7 @@ function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBu
3146
3233
  void handle?.close();
3147
3234
  handleRef.current = null;
3148
3235
  };
3149
- }, [manager, projectName, logSink, pushLog, logBus, stateBus]);
3236
+ }, [manager, projectName, logSink, pushLog, logBus, stateBus, platform, proxy]);
3150
3237
  return handleRef;
3151
3238
  }
3152
3239
 
@@ -3289,7 +3376,7 @@ function StatsPanel({ states, stats, sortMode, maxNameLen, height, focused, scro
3289
3376
  const statsObj = Object.fromEntries([...stats].map(([k, v]) => [k, v]));
3290
3377
  const apis = sortServiceNames(names.filter((n) => states.get(n).svc.type === "api"), sortMode, statsObj, stObj);
3291
3378
  const webs = sortServiceNames(names.filter((n) => states.get(n).svc.type === "web"), sortMode, statsObj, stObj);
3292
- const cpus = os.cpus().length;
3379
+ const cpus3 = os.cpus().length;
3293
3380
  const totalGB = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1);
3294
3381
  const usedGB = (parseFloat(totalGB) - os.freemem() / 1024 / 1024 / 1024).toFixed(1);
3295
3382
  const load = os.loadavg()[0].toFixed(2);
@@ -3342,7 +3429,7 @@ function StatsPanel({ states, stats, sortMode, maxNameLen, height, focused, scro
3342
3429
  ] }),
3343
3430
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3344
3431
  " System: ",
3345
- cpus,
3432
+ cpus3,
3346
3433
  "c Load ",
3347
3434
  load,
3348
3435
  " RAM ",
@@ -3754,7 +3841,8 @@ function App({ config, services, cliArgs, platform, env, baseCwd, proxyProvider,
3754
3841
  onToggleProxy: () => {
3755
3842
  }
3756
3843
  });
3757
- const socketServer = useControlPlane(pm.manager, config.name, logSink, pm.pushLog, pm.logBus, pm.stateBus);
3844
+ const proxyCtx = proxyProvider && proxyOpts ? { provider: proxyProvider, opts: proxyOpts } : null;
3845
+ const socketServer = useControlPlane(pm.manager, config.name, logSink, pm.pushLog, pm.logBus, pm.stateBus, platform, proxyCtx);
3758
3846
  const shutdown = useCallback3(async () => {
3759
3847
  lazyProxies.current.forEach((p) => p.destroy());
3760
3848
  await socketServer.current?.close();