@gachlab/devup 0.9.3 → 0.10.1

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,29 @@ 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
+ }
31
+ export interface ProjectInfo {
32
+ project: string;
33
+ profiles: Record<string, string[]>;
34
+ }
12
35
  export interface RpcContext {
13
36
  /** State of every service (read-only snapshot). */
14
37
  states(): Map<string, ProcessState>;
@@ -23,6 +46,12 @@ export interface RpcContext {
23
46
  watchLogs(svcName: string | null, onLine: (svc: string, line: string) => void): () => void;
24
47
  /** Subscribe to service-state changes. Returns an unsubscribe function. */
25
48
  watchStatus(onUpdate: (name: string, state: ProcessState) => void): () => void;
49
+ /** Per-service CPU/mem stats + system totals. */
50
+ getStats(): Promise<StatsResult>;
51
+ /** Active proxy configuration, or null when no proxy is running. */
52
+ getProxyInfo(): ProxyInfo | null;
53
+ /** Project metadata: name and profiles defined in config. */
54
+ getInfo(): ProjectInfo;
26
55
  }
27
56
  export interface SocketServerHandle {
28
57
  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,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACpC;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;IACjC,6DAA6D;IAC7D,OAAO,IAAI,WAAW,CAAC;CACxB;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
@@ -831,6 +831,7 @@ function serializeState(name, st) {
831
831
  health: st.health,
832
832
  port: st.svc.port,
833
833
  type: st.svc.type,
834
+ phase: st.svc.phase,
834
835
  errors: st.errors,
835
836
  restarts: st.restarts,
836
837
  pid: st.pid,
@@ -847,8 +848,12 @@ async function dispatch(method, params, ctx) {
847
848
  for (const [name, st] of ctx.states()) {
848
849
  out.push(serializeState(name, st));
849
850
  }
850
- return { services: out };
851
+ return { services: out, proxy: ctx.getProxyInfo() };
851
852
  }
853
+ case "stats":
854
+ return await ctx.getStats();
855
+ case "info":
856
+ return ctx.getInfo();
852
857
  case "restart": {
853
858
  const svc = stringOrThrow(params["svc"] ?? params["service"], "svc");
854
859
  await ctx.restart(svc);
@@ -951,7 +956,7 @@ function openStream(socketPath, method, params, onFrame, onError) {
951
956
  import { spawn as spawn4 } from "child_process";
952
957
  import { writeFileSync as writeFileSync2, readFileSync as readFileSync3, existsSync as existsSync10, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3, createReadStream } from "fs";
953
958
  import { join as join8 } from "path";
954
- import { homedir as homedir3 } from "os";
959
+ import { homedir as homedir3, totalmem, freemem, cpus } from "os";
955
960
  import { setTimeout as sleep } from "timers/promises";
956
961
  import { createInterface as createInterface3 } from "readline";
957
962
 
@@ -1874,6 +1879,7 @@ async function daemonBody(opts) {
1874
1879
  const logBus = new Broadcaster();
1875
1880
  const stateBus = new Broadcaster();
1876
1881
  const lazyProxies = /* @__PURE__ */ new Map();
1882
+ const prevCpuMap = /* @__PURE__ */ new Map();
1877
1883
  let externals = [];
1878
1884
  let socket = null;
1879
1885
  let healthTimer = null;
@@ -1983,7 +1989,51 @@ async function daemonBody(opts) {
1983
1989
  watchLogs: (svcName, onLine) => logBus.subscribe(({ svc, text }) => {
1984
1990
  if (svcName === null || svc === svcName) onLine(svc, text);
1985
1991
  }),
1986
- watchStatus: (onUpdate) => stateBus.subscribe(({ name, state }) => onUpdate(name, state))
1992
+ watchStatus: (onUpdate) => stateBus.subscribe(({ name, state }) => onUpdate(name, state)),
1993
+ async getStats() {
1994
+ const pids = [];
1995
+ const pidToName = /* @__PURE__ */ new Map();
1996
+ for (const [name, st] of mgr.state) {
1997
+ if (st.pid) {
1998
+ pids.push(st.pid);
1999
+ pidToName.set(st.pid, name);
2000
+ }
2001
+ }
2002
+ const raw = pids.length ? await platform.getProcessStats(pids) : /* @__PURE__ */ new Map();
2003
+ const services2 = {};
2004
+ for (const [name] of mgr.state) {
2005
+ services2[name] = { cpu: 0, memMB: 0 };
2006
+ }
2007
+ for (const [pid, data] of raw) {
2008
+ const name = pidToName.get(pid);
2009
+ if (!name) continue;
2010
+ const prev = prevCpuMap.get(name) ?? { time: Date.now(), cpu: 0 };
2011
+ const cpu = calcCpuPercent(data.cpuSeconds, prev.cpu, prev.time);
2012
+ prevCpuMap.set(name, { time: Date.now(), cpu: data.cpuSeconds });
2013
+ services2[name] = { cpu: Math.round(cpu * 10) / 10, memMB: Math.round(data.rss / 1024 * 10) / 10 };
2014
+ }
2015
+ return {
2016
+ services: services2,
2017
+ system: {
2018
+ totalMemMB: Math.round(totalmem() / 1024 / 1024),
2019
+ freeMemMB: Math.round(freemem() / 1024 / 1024),
2020
+ cpuCores: cpus().length
2021
+ }
2022
+ };
2023
+ },
2024
+ getProxyInfo() {
2025
+ if (!proxyProvider || !proxyOpts || !cliArgs.proxy) return null;
2026
+ return {
2027
+ active: true,
2028
+ provider: proxyProvider.name,
2029
+ domain: proxyOpts.domain,
2030
+ tls: proxyOpts.tls,
2031
+ routes: proxyOpts.routes
2032
+ };
2033
+ },
2034
+ getInfo() {
2035
+ return { project: projectName, profiles: config.profiles ?? {} };
2036
+ }
1987
2037
  }, { onLog: (msg) => writeDevupLog(msg) });
1988
2038
  healthTimer = setInterval(() => {
1989
2039
  void mgr.checkAllHealth();
@@ -3102,8 +3152,10 @@ function useLogsPause(setPaused, logsPaused, logsScrollOffset) {
3102
3152
  import { useEffect as useEffect5, useRef as useRef3 } from "react";
3103
3153
  import { createInterface as createInterface5 } from "readline";
3104
3154
  import { createReadStream as createReadStream3, existsSync as existsSync15 } from "fs";
3105
- function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBus) {
3155
+ import { totalmem as totalmem2, freemem as freemem2, cpus as cpus2 } from "os";
3156
+ function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBus, platform, proxy, profiles) {
3106
3157
  const handleRef = useRef3(null);
3158
+ const prevCpuMap = useRef3(/* @__PURE__ */ new Map());
3107
3159
  useEffect5(() => {
3108
3160
  if (!manager) return;
3109
3161
  let handle = null;
@@ -3135,6 +3187,50 @@ function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBu
3135
3187
  },
3136
3188
  watchStatus: (onUpdate) => {
3137
3189
  return stateBus.subscribe(({ name, state }) => onUpdate(name, state));
3190
+ },
3191
+ async getStats() {
3192
+ const pids = [];
3193
+ const pidToName = /* @__PURE__ */ new Map();
3194
+ for (const [name, st] of manager.state) {
3195
+ if (st.pid) {
3196
+ pids.push(st.pid);
3197
+ pidToName.set(st.pid, name);
3198
+ }
3199
+ }
3200
+ const raw = pids.length ? await platform.getProcessStats(pids) : /* @__PURE__ */ new Map();
3201
+ const services = {};
3202
+ for (const [name] of manager.state) {
3203
+ services[name] = { cpu: 0, memMB: 0 };
3204
+ }
3205
+ for (const [pid, data] of raw) {
3206
+ const name = pidToName.get(pid);
3207
+ if (!name) continue;
3208
+ const prev = prevCpuMap.current.get(name) ?? { time: Date.now(), cpu: 0 };
3209
+ const cpu = calcCpuPercent(data.cpuSeconds, prev.cpu, prev.time);
3210
+ prevCpuMap.current.set(name, { time: Date.now(), cpu: data.cpuSeconds });
3211
+ services[name] = { cpu: Math.round(cpu * 10) / 10, memMB: Math.round(data.rss / 1024 * 10) / 10 };
3212
+ }
3213
+ return {
3214
+ services,
3215
+ system: {
3216
+ totalMemMB: Math.round(totalmem2() / 1024 / 1024),
3217
+ freeMemMB: Math.round(freemem2() / 1024 / 1024),
3218
+ cpuCores: cpus2().length
3219
+ }
3220
+ };
3221
+ },
3222
+ getProxyInfo() {
3223
+ if (!proxy) return null;
3224
+ return {
3225
+ active: true,
3226
+ provider: proxy.provider.name,
3227
+ domain: proxy.opts.domain,
3228
+ tls: proxy.opts.tls,
3229
+ routes: proxy.opts.routes
3230
+ };
3231
+ },
3232
+ getInfo() {
3233
+ return { project: projectName, profiles };
3138
3234
  }
3139
3235
  }, { onLog: (msg) => pushLog("devup", msg, 12) });
3140
3236
  handleRef.current = handle;
@@ -3146,7 +3242,7 @@ function useControlPlane(manager, projectName, logSink, pushLog, logBus, stateBu
3146
3242
  void handle?.close();
3147
3243
  handleRef.current = null;
3148
3244
  };
3149
- }, [manager, projectName, logSink, pushLog, logBus, stateBus]);
3245
+ }, [manager, projectName, logSink, pushLog, logBus, stateBus, platform, proxy, profiles]);
3150
3246
  return handleRef;
3151
3247
  }
3152
3248
 
@@ -3289,7 +3385,7 @@ function StatsPanel({ states, stats, sortMode, maxNameLen, height, focused, scro
3289
3385
  const statsObj = Object.fromEntries([...stats].map(([k, v]) => [k, v]));
3290
3386
  const apis = sortServiceNames(names.filter((n) => states.get(n).svc.type === "api"), sortMode, statsObj, stObj);
3291
3387
  const webs = sortServiceNames(names.filter((n) => states.get(n).svc.type === "web"), sortMode, statsObj, stObj);
3292
- const cpus = os.cpus().length;
3388
+ const cpus3 = os.cpus().length;
3293
3389
  const totalGB = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1);
3294
3390
  const usedGB = (parseFloat(totalGB) - os.freemem() / 1024 / 1024 / 1024).toFixed(1);
3295
3391
  const load = os.loadavg()[0].toFixed(2);
@@ -3342,7 +3438,7 @@ function StatsPanel({ states, stats, sortMode, maxNameLen, height, focused, scro
3342
3438
  ] }),
3343
3439
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
3344
3440
  " System: ",
3345
- cpus,
3441
+ cpus3,
3346
3442
  "c Load ",
3347
3443
  load,
3348
3444
  " RAM ",
@@ -3754,7 +3850,8 @@ function App({ config, services, cliArgs, platform, env, baseCwd, proxyProvider,
3754
3850
  onToggleProxy: () => {
3755
3851
  }
3756
3852
  });
3757
- const socketServer = useControlPlane(pm.manager, config.name, logSink, pm.pushLog, pm.logBus, pm.stateBus);
3853
+ const proxyCtx = proxyProvider && proxyOpts ? { provider: proxyProvider, opts: proxyOpts } : null;
3854
+ const socketServer = useControlPlane(pm.manager, config.name, logSink, pm.pushLog, pm.logBus, pm.stateBus, platform, proxyCtx, config.profiles ?? {});
3758
3855
  const shutdown = useCallback3(async () => {
3759
3856
  lazyProxies.current.forEach((p) => p.destroy());
3760
3857
  await socketServer.current?.close();