@juejin-opensource/jusage 0.1.1-beta.8 → 0.1.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.
- package/bin/jusage.js +4 -1
- package/dist/daemon.d.ts +9 -0
- package/dist/daemon.js +111 -7
- package/dist/dashboard/assets/ChevronDown-CBnDSnT1.js +1 -0
- package/dist/dashboard/assets/dashboard-Dj9OZI8h.js +55 -0
- package/dist/dashboard/assets/download-Ce2CqEkq.js +1 -0
- package/dist/dashboard/assets/env-zw6gWqFK.js +1 -0
- package/dist/dashboard/assets/index-CRso69Gk.css +1 -0
- package/dist/dashboard/assets/index-Cx78OSHq.js +1 -0
- package/dist/dashboard/assets/index-DKJwDJt7.js +143 -0
- package/dist/dashboard/assets/index-Y8p4IgcB.js +1 -0
- package/dist/dashboard/assets/pricing-BG8I1tLI.js +1 -0
- package/dist/dashboard/assets/{rank-DedRCNMw.js → rank-rho9cnvt.js} +2 -2
- package/dist/dashboard/assets/{settings-jDmOMdkE.js → settings-B6jdyBqT.js} +1 -1
- package/dist/dashboard/e1.png +0 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.js +18 -3
- package/dist/service-macos.js +32 -3
- package/dist/service-windows.js +2 -1
- package/dist/service.js +29 -6
- package/package.json +3 -3
- package/dist/dashboard/assets/dashboard-CUazuH2T.js +0 -55
- package/dist/dashboard/assets/download-BzFSswoC.js +0 -1
- package/dist/dashboard/assets/env-BKC5UKtr.js +0 -1
- package/dist/dashboard/assets/index-1sV0Fo6m.js +0 -143
- package/dist/dashboard/assets/index-C1eSlCqF.css +0 -1
- package/dist/dashboard/assets/index-DcW6PDH-.js +0 -1
package/bin/jusage.js
CHANGED
package/dist/daemon.d.ts
CHANGED
|
@@ -4,7 +4,16 @@ export declare function daemonLogPath(dataDir?: string): string;
|
|
|
4
4
|
export declare function ensureDaemonLogDir(dataDir?: string): Promise<string>;
|
|
5
5
|
/** Soft then hard kill. Returns true if the process is gone. */
|
|
6
6
|
export declare function stopPid(pid: number, timeoutMs?: number): Promise<boolean>;
|
|
7
|
+
/** True when local-api `/health` returns `{ ok: true }`. */
|
|
8
|
+
export declare function probeLocalHealth(port: number): Promise<boolean>;
|
|
7
9
|
export declare function waitForPid(dataDir?: string, timeoutMs?: number): Promise<number | null>;
|
|
10
|
+
export interface ServiceReady {
|
|
11
|
+
pid: number | null;
|
|
12
|
+
health: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Wait until the pid file is live, or `/health` is up as a fallback. */
|
|
15
|
+
export declare function waitForServiceReady(dataDir: string, port: number, timeoutMs?: number): Promise<ServiceReady>;
|
|
16
|
+
export declare function readDaemonLogTail(dataDir?: string, maxChars?: number): Promise<string>;
|
|
8
17
|
export declare function resolveServiceCommand(cliBinPath: string): {
|
|
9
18
|
nodePath: string;
|
|
10
19
|
args: string[];
|
package/dist/daemon.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
2
4
|
import { join } from 'node:path';
|
|
3
|
-
import { DEFAULT_DATA_DIR, logsDir, clearPid, getRunningOwner, getRunningPid, isPidAlive, pidFilePath, readPid, readRuntimeOwner, writePid, writeRuntimeOwner, } from '@juejin-opensource/jusage-core';
|
|
5
|
+
import { DEFAULT_DATA_DIR, argsMatchRuntimeKind, logsDir, clearPid, getRunningOwner, getRunningPid, isPidAlive, pidFilePath, readPid, readProcessArgs, readRuntimeOwner, writePid, writeRuntimeOwner, } from '@juejin-opensource/jusage-core';
|
|
4
6
|
export { clearPid, getRunningOwner, getRunningPid, isPidAlive, pidFilePath, readPid, readRuntimeOwner, writePid, writeRuntimeOwner, };
|
|
5
7
|
export function daemonLogPath(dataDir = DEFAULT_DATA_DIR) {
|
|
6
8
|
return join(logsDir(dataDir), 'daemon.log');
|
|
@@ -43,22 +45,124 @@ export async function stopPid(pid, timeoutMs = 5000) {
|
|
|
43
45
|
await new Promise((r) => setTimeout(r, 200));
|
|
44
46
|
return !isPidAlive(pid);
|
|
45
47
|
}
|
|
48
|
+
async function readLivePid(dataDir) {
|
|
49
|
+
// Do not go through getRunningOwner: a locale-mismatched startedAt would
|
|
50
|
+
// delete the pid file the daemon just wrote, and this wait would never see it.
|
|
51
|
+
const owner = await readRuntimeOwner(dataDir);
|
|
52
|
+
if (owner != null && isPidAlive(owner.pid))
|
|
53
|
+
return owner.pid;
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/** True when local-api `/health` returns `{ ok: true }`. */
|
|
57
|
+
export async function probeLocalHealth(port) {
|
|
58
|
+
try {
|
|
59
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`, {
|
|
60
|
+
signal: AbortSignal.timeout(400),
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok)
|
|
63
|
+
return false;
|
|
64
|
+
const body = (await res.json());
|
|
65
|
+
return body.ok === true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function readListeningPid(port) {
|
|
72
|
+
try {
|
|
73
|
+
if (process.platform === 'win32') {
|
|
74
|
+
const result = spawnSync('powershell.exe', [
|
|
75
|
+
'-NoProfile',
|
|
76
|
+
'-NonInteractive',
|
|
77
|
+
'-ExecutionPolicy',
|
|
78
|
+
'Bypass',
|
|
79
|
+
'-Command',
|
|
80
|
+
`(Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty OwningProcess)`,
|
|
81
|
+
], { encoding: 'utf8', windowsHide: true, timeout: 3_000 });
|
|
82
|
+
const pid = Number((result.stdout ?? '').trim());
|
|
83
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
84
|
+
}
|
|
85
|
+
const result = spawnSync('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t'], { encoding: 'utf8', timeout: 2_000 });
|
|
86
|
+
const pid = Number((result.stdout ?? '').trim().split('\n')[0]);
|
|
87
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function recoverPidFromHealth(dataDir, port) {
|
|
94
|
+
const listenPid = readListeningPid(port);
|
|
95
|
+
if (listenPid == null || !isPidAlive(listenPid))
|
|
96
|
+
return null;
|
|
97
|
+
const args = readProcessArgs(listenPid) ?? '';
|
|
98
|
+
const kind = argsMatchRuntimeKind(args, 'desktop')
|
|
99
|
+
? 'desktop'
|
|
100
|
+
: argsMatchRuntimeKind(args, 'cli')
|
|
101
|
+
? 'cli'
|
|
102
|
+
: null;
|
|
103
|
+
if (!kind)
|
|
104
|
+
return null;
|
|
105
|
+
await writePid(listenPid, dataDir, kind);
|
|
106
|
+
return listenPid;
|
|
107
|
+
}
|
|
46
108
|
export async function waitForPid(dataDir = DEFAULT_DATA_DIR, timeoutMs = 15_000) {
|
|
47
109
|
const deadline = Date.now() + timeoutMs;
|
|
48
110
|
while (Date.now() < deadline) {
|
|
49
|
-
const pid = await
|
|
111
|
+
const pid = await readLivePid(dataDir);
|
|
50
112
|
if (pid != null)
|
|
51
113
|
return pid;
|
|
52
114
|
await new Promise((r) => setTimeout(r, 200));
|
|
53
115
|
}
|
|
54
116
|
return getRunningPid(dataDir);
|
|
55
117
|
}
|
|
118
|
+
/** Wait until the pid file is live, or `/health` is up as a fallback. */
|
|
119
|
+
export async function waitForServiceReady(dataDir, port, timeoutMs = 15_000) {
|
|
120
|
+
const deadline = Date.now() + timeoutMs;
|
|
121
|
+
while (Date.now() < deadline) {
|
|
122
|
+
const pid = await readLivePid(dataDir);
|
|
123
|
+
if (pid != null)
|
|
124
|
+
return { pid, health: await probeLocalHealth(port) };
|
|
125
|
+
if (await probeLocalHealth(port)) {
|
|
126
|
+
return { pid: await recoverPidFromHealth(dataDir, port), health: true };
|
|
127
|
+
}
|
|
128
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
129
|
+
}
|
|
130
|
+
const pid = (await readLivePid(dataDir)) ?? (await getRunningPid(dataDir));
|
|
131
|
+
if (pid != null)
|
|
132
|
+
return { pid, health: await probeLocalHealth(port) };
|
|
133
|
+
if (await probeLocalHealth(port)) {
|
|
134
|
+
return { pid: await recoverPidFromHealth(dataDir, port), health: true };
|
|
135
|
+
}
|
|
136
|
+
return { pid: null, health: false };
|
|
137
|
+
}
|
|
138
|
+
export async function readDaemonLogTail(dataDir = DEFAULT_DATA_DIR, maxChars = 2000) {
|
|
139
|
+
try {
|
|
140
|
+
const raw = await readFile(daemonLogPath(dataDir), 'utf8');
|
|
141
|
+
const trimmed = raw.trimEnd();
|
|
142
|
+
if (!trimmed)
|
|
143
|
+
return '';
|
|
144
|
+
return trimmed.length <= maxChars ? trimmed : trimmed.slice(-maxChars);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return '';
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function resolveExistingPath(path) {
|
|
151
|
+
try {
|
|
152
|
+
return realpathSync(path);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return path;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
56
158
|
export function resolveServiceCommand(cliBinPath) {
|
|
57
|
-
|
|
58
|
-
const
|
|
159
|
+
// fnm/nvm shims live in session-specific dirs; persist the real binary.
|
|
160
|
+
const nodePath = resolveExistingPath(process.execPath);
|
|
161
|
+
const binPath = resolveExistingPath(cliBinPath);
|
|
162
|
+
const args = [binPath, 'start'];
|
|
59
163
|
const commandLine = process.platform === 'win32'
|
|
60
|
-
? `"${nodePath}" "${
|
|
61
|
-
: `${shellQuote(nodePath)} ${shellQuote(
|
|
164
|
+
? `"${nodePath}" "${binPath}" start`
|
|
165
|
+
: `${shellQuote(nodePath)} ${shellQuote(binPath)} start`;
|
|
62
166
|
return { nodePath, args, commandLine };
|
|
63
167
|
}
|
|
64
168
|
function shellQuote(value) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as p,R as l,j as t,c7 as v,r as s,c8 as g,a9 as c,c2 as f,c3 as x,a7 as h,c1 as w,c9 as u,ca as m,a$ as $}from"./index-DKJwDJt7.js";const j=p({slots:{base:"popover",dialog:"popover__dialog",heading:"popover__heading",trigger:"popover__trigger"}}),n=s.createContext({}),d=({children:o,...a})=>{const r=l.useMemo(()=>j(),[]);return t.jsx(n,{value:{slots:r},children:t.jsx(v,{"data-slot":"popover-root",...a,children:o})})},b=({children:o,className:a,...r})=>{const{slots:e}=s.use(n);return t.jsx(n,{value:{slots:e},children:t.jsx(f,{value:{variant:"default"},children:t.jsx(x,{...r,className:h(a,e==null?void 0:e.base()),children:o})})})},C=({children:o,className:a,...r})=>{const e=t.jsx("svg",{"data-slot":"popover-overlay-arrow",fill:"none",height:"12",viewBox:"0 0 12 12",width:"12",xmlns:"http://www.w3.org/2000/svg",children:t.jsx("path",{d:"M0 0C5.48483 8 6.5 8 12 0Z"})}),i=l.isValidElement(o)?l.cloneElement(o,{"data-slot":"popover-overlay-arrow"}):e;return t.jsx(w,{"data-slot":"popover-overlay-arrow-group",...r,className:a,children:i})},P=({children:o,className:a,...r})=>{const{slots:e}=s.use(n);return t.jsx(u,{"data-slot":"popover-dialog",...r,className:c(e==null?void 0:e.dialog,a),children:o})},R=({children:o,className:a,...r})=>{const{slots:e}=s.use(n);return t.jsx(m,{children:t.jsx($.div,{className:c(e==null?void 0:e.trigger,a),"data-slot":"popover-trigger",role:"button",...r,children:o})})},E=({children:o,className:a,...r})=>{const{slots:e}=s.use(n);return t.jsx(g,{slot:"title",...r,className:c(e==null?void 0:e.heading,a),children:o})},_=Object.assign(d,{Root:d,Trigger:R,Dialog:P,Arrow:C,Content:b,Heading:E}),A=o=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",width:16,height:16,fill:"none",viewBox:"0 0 16 16"},o),s.createElement("path",{fill:"currentColor",fillRule:"evenodd",d:"M13.488 3.43a.75.75 0 0 1 .081 1.058l-6 7a.75.75 0 0 1-1.1.042l-3.5-3.5A.75.75 0 0 1 4.03 6.97l2.928 2.927 5.473-6.385a.75.75 0 0 1 1.057-.081",clipRule:"evenodd"})),M=o=>s.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",width:16,height:16,fill:"none",viewBox:"0 0 16 16"},o),s.createElement("path",{fill:"currentColor",fillRule:"evenodd",d:"M2.97 5.47a.75.75 0 0 1 1.06 0L8 9.44l3.97-3.97a.75.75 0 1 1 1.06 1.06l-4.5 4.5a.75.75 0 0 1-1.06 0l-4.5-4.5a.75.75 0 0 1 0-1.06",clipRule:"evenodd"}));export{M as C,_ as P,A as a};
|