@mebular/service 0.0.0-stage → 0.1.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/dist/cli.d.ts +17 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +120 -0
- package/dist/cli.js.map +1 -0
- package/dist/heartbeat.d.ts +30 -0
- package/dist/heartbeat.d.ts.map +1 -0
- package/dist/heartbeat.js +69 -0
- package/dist/heartbeat.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/launchd.d.ts +32 -0
- package/dist/launchd.d.ts.map +1 -0
- package/dist/launchd.js +102 -0
- package/dist/launchd.js.map +1 -0
- package/dist/manager.d.ts +169 -0
- package/dist/manager.d.ts.map +1 -0
- package/dist/manager.js +505 -0
- package/dist/manager.js.map +1 -0
- package/dist/paths.d.ts +17 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +29 -0
- package/dist/paths.js.map +1 -0
- package/dist/units.d.ts +50 -0
- package/dist/units.d.ts.map +1 -0
- package/dist/units.js +173 -0
- package/dist/units.js.map +1 -0
- package/dist/version.json +6 -0
- package/package.json +28 -4
- package/src/cli.ts +139 -0
- package/src/heartbeat.ts +89 -0
- package/src/index.ts +8 -0
- package/src/launchd.ts +111 -0
- package/src/manager.ts +686 -0
- package/src/paths.ts +40 -0
- package/src/units.ts +208 -0
- package/README.md +0 -3
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { RunCommand, ServiceDescriptor } from './manager.js';
|
|
2
|
+
import type { ServiceKind, ServicePlatform } from './units.js';
|
|
3
|
+
export interface ServiceCliOptions {
|
|
4
|
+
descriptors: readonly ServiceDescriptor[];
|
|
5
|
+
argv: readonly string[];
|
|
6
|
+
home?: string;
|
|
7
|
+
platform?: ServicePlatform;
|
|
8
|
+
run?: RunCommand;
|
|
9
|
+
out?: (line: string) => void;
|
|
10
|
+
/** 入口边界解析好的构建 SHA(F-INSTALL-1:与 cwd 解耦);缺省回落 resolveBuildSha()。 */
|
|
11
|
+
sha?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const SERVICE_USAGE = "\u7528\u6CD5\uFF1A<cli> service install <name> [--no-autostart] [--label L] [--sha S] | uninstall <name> | restart <name> | status [name] | logs <name> [--tail N]";
|
|
14
|
+
/** 返回退出码;输出为单行 JSON。 */
|
|
15
|
+
export declare function runServiceCli(opts: ServiceCliOptions): number;
|
|
16
|
+
export type { ServiceKind };
|
|
17
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAkB,MAAM,cAAc,CAAC;AAElF,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAoB/D,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC1C,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,GAAG,CAAC,EAAE,UAAU,CAAC;IACjB,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA8BD,eAAO,MAAM,aAAa,uKAC6H,CAAC;AAExJ,wBAAwB;AACxB,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAgE7D;AAED,YAAY,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// 共用 `service` 子命令(fleet / mebular 两个 CLI 复用同一 UX 与语义)。
|
|
2
|
+
//
|
|
3
|
+
// install <name> [--no-autostart] [--label L] [--sha S]
|
|
4
|
+
// uninstall <name>
|
|
5
|
+
// status [name]
|
|
6
|
+
// logs <name> [--tail N]
|
|
7
|
+
import { installService, resolveBuildSha, restartService, serviceLogs, serviceStatus, uninstallService } from './manager.js';
|
|
8
|
+
import { appendFileSync } from 'node:fs';
|
|
9
|
+
/**
|
|
10
|
+
* 仪器化(F-INSTALL-1):`MEBULAR_SERVICE_TRACE=<path>` 时,install 的每条外部命令
|
|
11
|
+
* (launchctl/systemctl/…)以 JSONL 追加落盘(at/cmd/args/rc/stdout/stderr/durationMs),
|
|
12
|
+
* 供 launchd 收尾竞态等「命令级时序」诊断。
|
|
13
|
+
*/
|
|
14
|
+
function fileTracerFromEnv() {
|
|
15
|
+
const target = process.env.MEBULAR_SERVICE_TRACE;
|
|
16
|
+
if (target === undefined || target.length === 0)
|
|
17
|
+
return undefined;
|
|
18
|
+
return (entry) => {
|
|
19
|
+
try {
|
|
20
|
+
appendFileSync(target, `${JSON.stringify(entry)}\n`, 'utf-8');
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// 留痕失败绝不阻塞安装主路径
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function parseFlags(list) {
|
|
28
|
+
const positional = [];
|
|
29
|
+
const flags = {};
|
|
30
|
+
for (let i = 0; i < list.length; i++) {
|
|
31
|
+
const token = list[i];
|
|
32
|
+
if (token.startsWith('--')) {
|
|
33
|
+
const key = token.slice(2);
|
|
34
|
+
const next = list[i + 1];
|
|
35
|
+
// `--extra` 的值本身以 `--` 开头(透传给服务的 CLI 参数),必须按值消费。
|
|
36
|
+
if (key === 'extra' && next !== undefined) {
|
|
37
|
+
flags[key] = next;
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
else if (next === undefined || next.startsWith('--')) {
|
|
41
|
+
flags[key] = true;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
flags[key] = next;
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
positional.push(token);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { positional, flags };
|
|
53
|
+
}
|
|
54
|
+
const findDescriptor = (descriptors, name) => descriptors.find((d) => d.kind === name);
|
|
55
|
+
export const SERVICE_USAGE = '用法:<cli> service install <name> [--no-autostart] [--label L] [--sha S] | uninstall <name> | restart <name> | status [name] | logs <name> [--tail N]';
|
|
56
|
+
/** 返回退出码;输出为单行 JSON。 */
|
|
57
|
+
export function runServiceCli(opts) {
|
|
58
|
+
const out = opts.out ?? ((line) => process.stdout.write(`${line}\n`));
|
|
59
|
+
const [sub, ...rest] = [...opts.argv];
|
|
60
|
+
const { positional, flags } = parseFlags(rest);
|
|
61
|
+
const base = {
|
|
62
|
+
...(opts.home !== undefined ? { home: opts.home } : {}),
|
|
63
|
+
...(opts.platform !== undefined ? { platform: opts.platform } : {}),
|
|
64
|
+
...(opts.run !== undefined ? { run: opts.run } : {}),
|
|
65
|
+
};
|
|
66
|
+
const emit = (value, code = 0) => {
|
|
67
|
+
out(JSON.stringify(value, null, 2));
|
|
68
|
+
return code;
|
|
69
|
+
};
|
|
70
|
+
if (sub === 'status') {
|
|
71
|
+
const names = positional.length > 0 ? positional : opts.descriptors.map((d) => d.kind);
|
|
72
|
+
const services = names.map((name) => {
|
|
73
|
+
const descriptor = findDescriptor(opts.descriptors, name);
|
|
74
|
+
if (!descriptor)
|
|
75
|
+
throw new Error(`未知服务:${name}`);
|
|
76
|
+
return serviceStatus(descriptor, base);
|
|
77
|
+
});
|
|
78
|
+
return emit({ ok: true, action: 'status', services });
|
|
79
|
+
}
|
|
80
|
+
const name = positional[0] ?? (opts.descriptors.length === 1 ? opts.descriptors[0].kind : undefined);
|
|
81
|
+
if (name === undefined) {
|
|
82
|
+
out(SERVICE_USAGE);
|
|
83
|
+
return 2;
|
|
84
|
+
}
|
|
85
|
+
const found = findDescriptor(opts.descriptors, name);
|
|
86
|
+
if (!found) {
|
|
87
|
+
out(SERVICE_USAGE);
|
|
88
|
+
return 2;
|
|
89
|
+
}
|
|
90
|
+
// `--extra "a b c"`:把宿主透传的额外 CLI 参数追加到服务启动命令(如 `--submit 3`)。
|
|
91
|
+
const extra = typeof flags.extra === 'string' ? flags.extra.split(/\s+/).filter((s) => s.length > 0) : [];
|
|
92
|
+
const descriptor = extra.length > 0 ? { ...found, args: [...found.args, ...extra] } : found;
|
|
93
|
+
if (sub === 'install') {
|
|
94
|
+
const autostart = flags['no-autostart'] !== true;
|
|
95
|
+
const sha = typeof flags.sha === 'string' ? flags.sha : (opts.sha ?? resolveBuildSha());
|
|
96
|
+
const trace = fileTracerFromEnv();
|
|
97
|
+
const result = installService(descriptor, {
|
|
98
|
+
...base,
|
|
99
|
+
autostart,
|
|
100
|
+
sha,
|
|
101
|
+
...(typeof flags.label === 'string' ? { label: flags.label } : {}),
|
|
102
|
+
...(trace !== undefined ? { trace } : {}),
|
|
103
|
+
});
|
|
104
|
+
return emit(result);
|
|
105
|
+
}
|
|
106
|
+
if (sub === 'uninstall') {
|
|
107
|
+
return emit(uninstallService(descriptor, base));
|
|
108
|
+
}
|
|
109
|
+
if (sub === 'restart') {
|
|
110
|
+
const result = restartService(descriptor, base);
|
|
111
|
+
return emit(result, result.ok ? 0 : 1);
|
|
112
|
+
}
|
|
113
|
+
if (sub === 'logs') {
|
|
114
|
+
const tail = typeof flags.tail === 'string' ? Number(flags.tail) : undefined;
|
|
115
|
+
return emit(serviceLogs(descriptor, { ...base, ...(tail !== undefined && Number.isFinite(tail) ? { tail } : {}) }));
|
|
116
|
+
}
|
|
117
|
+
out(SERVICE_USAGE);
|
|
118
|
+
return 2;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface Heartbeat {
|
|
2
|
+
pid: number;
|
|
3
|
+
/** epoch ms */
|
|
4
|
+
ts: number;
|
|
5
|
+
role: string;
|
|
6
|
+
sha: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const HEARTBEAT_FILE = "service.heartbeat";
|
|
9
|
+
export declare const HEARTBEAT_DEFAULT_MAX_AGE_MS = 15000;
|
|
10
|
+
export declare const heartbeatPath: (dir: string) => string;
|
|
11
|
+
export declare function writeHeartbeat(dir: string, input: {
|
|
12
|
+
role: string;
|
|
13
|
+
sha: string;
|
|
14
|
+
pid?: number;
|
|
15
|
+
now?: number;
|
|
16
|
+
}): Heartbeat;
|
|
17
|
+
export declare function readHeartbeat(dir: string): Heartbeat | null;
|
|
18
|
+
export declare function heartbeatAgeMs(dir: string, now?: number): number | null;
|
|
19
|
+
export declare function isHeartbeatFresh(dir: string, maxAgeMs?: number, now?: number): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 常驻进程心跳:立即写一次,之后每 `intervalMs` 刷新;返回停止函数(只停刷新,不删文件——
|
|
22
|
+
* 进程死亡后文件停留在最后 ts,`status`/doctor 据此报告「陈旧」)。
|
|
23
|
+
*/
|
|
24
|
+
export declare function startHeartbeat(dir: string, input: {
|
|
25
|
+
role: string;
|
|
26
|
+
sha: string;
|
|
27
|
+
intervalMs?: number;
|
|
28
|
+
pid?: number;
|
|
29
|
+
}): () => void;
|
|
30
|
+
//# sourceMappingURL=heartbeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.d.ts","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe;IACf,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,cAAc,sBAAsB,CAAC;AAClD,eAAO,MAAM,4BAA4B,QAAS,CAAC;AAEnD,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,MAAwC,CAAC;AAErF,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D,SAAS,CAkBX;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAU3D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,IAAI,CAGnF;AAED,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAqC,EAC/C,GAAG,GAAE,MAAmB,GACvB,OAAO,CAGT;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtE,MAAM,IAAI,CAaZ"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// 进程心跳(D4):常驻组件周期性写 `<configDir>/service.heartbeat`,供 `service status` 与 doctor 判定
|
|
2
|
+
// 「是否在跑且新鲜」。原子写、0600(权限语义同现约定;Windows 走 ACL,由 doctor 说明差异)。
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export const HEARTBEAT_FILE = 'service.heartbeat';
|
|
6
|
+
export const HEARTBEAT_DEFAULT_MAX_AGE_MS = 15_000;
|
|
7
|
+
export const heartbeatPath = (dir) => path.join(dir, HEARTBEAT_FILE);
|
|
8
|
+
export function writeHeartbeat(dir, input) {
|
|
9
|
+
const record = {
|
|
10
|
+
pid: input.pid ?? process.pid,
|
|
11
|
+
ts: input.now ?? Date.now(),
|
|
12
|
+
role: input.role,
|
|
13
|
+
sha: input.sha,
|
|
14
|
+
};
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
16
|
+
const target = heartbeatPath(dir);
|
|
17
|
+
const tmp = `${target}.tmp-${process.pid}`;
|
|
18
|
+
fs.writeFileSync(tmp, JSON.stringify(record), { mode: 0o600 });
|
|
19
|
+
try {
|
|
20
|
+
fs.chmodSync(tmp, 0o600);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
// Windows 无 POSIX mode;由 ACL 兜底。
|
|
24
|
+
}
|
|
25
|
+
fs.renameSync(tmp, target);
|
|
26
|
+
return record;
|
|
27
|
+
}
|
|
28
|
+
export function readHeartbeat(dir) {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(fs.readFileSync(heartbeatPath(dir), 'utf-8'));
|
|
31
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
32
|
+
return null;
|
|
33
|
+
const rec = parsed;
|
|
34
|
+
if (typeof rec.pid !== 'number' || typeof rec.ts !== 'number')
|
|
35
|
+
return null;
|
|
36
|
+
return { pid: rec.pid, ts: rec.ts, role: String(rec.role ?? ''), sha: String(rec.sha ?? '') };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function heartbeatAgeMs(dir, now = Date.now()) {
|
|
43
|
+
const hb = readHeartbeat(dir);
|
|
44
|
+
return hb === null ? null : Math.max(0, now - hb.ts);
|
|
45
|
+
}
|
|
46
|
+
export function isHeartbeatFresh(dir, maxAgeMs = HEARTBEAT_DEFAULT_MAX_AGE_MS, now = Date.now()) {
|
|
47
|
+
const age = heartbeatAgeMs(dir, now);
|
|
48
|
+
return age !== null && age <= maxAgeMs;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 常驻进程心跳:立即写一次,之后每 `intervalMs` 刷新;返回停止函数(只停刷新,不删文件——
|
|
52
|
+
* 进程死亡后文件停留在最后 ts,`status`/doctor 据此报告「陈旧」)。
|
|
53
|
+
*/
|
|
54
|
+
export function startHeartbeat(dir, input) {
|
|
55
|
+
const intervalMs = input.intervalMs ?? 5_000;
|
|
56
|
+
const beat = () => {
|
|
57
|
+
try {
|
|
58
|
+
writeHeartbeat(dir, { role: input.role, sha: input.sha, ...(input.pid !== undefined ? { pid: input.pid } : {}) });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// 心跳失败不应让服务崩溃。
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
beat();
|
|
65
|
+
const timer = setInterval(beat, intervalMs);
|
|
66
|
+
timer.unref?.();
|
|
67
|
+
return () => clearInterval(timer);
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=heartbeat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heartbeat.js","sourceRoot":"","sources":["../src/heartbeat.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @mebular/service —— 常驻组件服务化(平台单元生成 + 生命周期 + 心跳)。零 core 依赖。
|
|
2
|
+
export * from './units.js';
|
|
3
|
+
export * from './paths.js';
|
|
4
|
+
export * from './launchd.js';
|
|
5
|
+
export * from './heartbeat.js';
|
|
6
|
+
export * from './manager.js';
|
|
7
|
+
export * from './cli.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,UAAU,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** `launchctl print gui/<uid>/<label>` 里我们关心的事实。 */
|
|
2
|
+
export interface LaunchdJob {
|
|
3
|
+
label: string | null;
|
|
4
|
+
/** 作业来源 plist 路径(`path = …`) */
|
|
5
|
+
path: string | null;
|
|
6
|
+
/** `program = …` */
|
|
7
|
+
program: string | null;
|
|
8
|
+
/** `arguments = { … }`(去掉可执行文件名之外的逐项) */
|
|
9
|
+
arguments: string[];
|
|
10
|
+
/** `environment = { K => V }`(`=>` 与 `=` 两种写法都接受) */
|
|
11
|
+
environment: Record<string, string>;
|
|
12
|
+
/** `working directory = …` */
|
|
13
|
+
workingDirectory: string | null;
|
|
14
|
+
}
|
|
15
|
+
/** 归属比较用的路径规范化(绝对化 + 去尾部分隔符)。纯字符串运算,不做 realpath(免 IO)。 */
|
|
16
|
+
export declare function normalizeServicePath(value: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* 解析 `launchctl print gui/<uid>/<label>` 输出。**容错**:缺块/空输出返回全 null,
|
|
19
|
+
* 调用方据此判定「不归属」(保守 → 绝不当成 running)。
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseLaunchdPrint(output: string): LaunchdJob;
|
|
22
|
+
/**
|
|
23
|
+
* 加载中的作业是否属于本 home。**任一**事实匹配即视为本 home:
|
|
24
|
+
* - 环境 `MEBULAR_HOME` / `MEBULAR_SERVICE_HOME` == home;
|
|
25
|
+
* - `working directory` == home;
|
|
26
|
+
* - plist 路径位于 `<home>/` 之下。
|
|
27
|
+
* 无法判定(解析不到任何事实)→ **false**(保守:宁可说不是本 home,也不误报 running)。
|
|
28
|
+
*/
|
|
29
|
+
export declare function jobBelongsToHome(job: LaunchdJob | null, home: string): boolean;
|
|
30
|
+
/** 人类可读的「占用者」描述(用于警告文案;无法识别时给 null)。 */
|
|
31
|
+
export declare function describeJobOwner(job: LaunchdJob | null): string | null;
|
|
32
|
+
//# sourceMappingURL=launchd.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchd.d.ts","sourceRoot":"","sources":["../src/launchd.ts"],"names":[],"mappings":"AAWA,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gCAAgC;IAChC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,oBAAoB;IACpB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yCAAyC;IACzC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,8BAA8B;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAID,0DAA0D;AAC1D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG1D;AA0BD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAgB5D;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAa9E;AAED,wCAAwC;AACxC,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAOtE"}
|
package/dist/launchd.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// macOS launchd 作业的**纯解析与归属判定**(无 IO、无平台副作用),供 `service status/install`
|
|
2
|
+
// 校验「加载中的作业是否属于本 home」——这是 F-SVC-1 的核心不变量:
|
|
3
|
+
// **跨 home 的作业绝不算 running,也绝不 bootout 他人**。
|
|
4
|
+
//
|
|
5
|
+
// 背景(真机事故):旧方案 label 无 home 维度(`com.mebular.<kind>`),任意 home 的 install 都会
|
|
6
|
+
// 顶掉同 label 的既有作业;`launchctl print gui/<uid>/<label>` 只要 label 存在就返回 0 → 别的 home
|
|
7
|
+
// 的作业被本 home 误报 running(假阳性)。故 running 判定必须回到「作业内容」:
|
|
8
|
+
// ProgramArguments / MEBULAR_HOME / WorkingDirectory / plist 路径与本 home 是否一致。
|
|
9
|
+
import { resolve, sep } from 'node:path';
|
|
10
|
+
const strip = (value) => value.trim().replace(/^["']|["']$/g, '').trim();
|
|
11
|
+
/** 归属比较用的路径规范化(绝对化 + 去尾部分隔符)。纯字符串运算,不做 realpath(免 IO)。 */
|
|
12
|
+
export function normalizeServicePath(value) {
|
|
13
|
+
const absolute = resolve(value);
|
|
14
|
+
return absolute.length > 1 ? absolute.replace(/[/\\]+$/, '') : absolute;
|
|
15
|
+
}
|
|
16
|
+
function readScalar(lines, key) {
|
|
17
|
+
const re = new RegExp(`^\\s*${key}\\s*=\\s*(.+?)\\s*$`);
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
const match = re.exec(line);
|
|
20
|
+
if (match)
|
|
21
|
+
return strip(match[1]);
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
/** 读 `key = { … }` 或 `key = ( … )` 块内容(去缩进、去行尾逗号)。 */
|
|
26
|
+
function readBlock(lines, key) {
|
|
27
|
+
const open = new RegExp(`^\\s*${key}\\s*=\\s*[({]\\s*$`);
|
|
28
|
+
const start = lines.findIndex((line) => open.test(line));
|
|
29
|
+
if (start < 0)
|
|
30
|
+
return [];
|
|
31
|
+
const out = [];
|
|
32
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i];
|
|
34
|
+
if (/^\s*[)}]\s*$/.test(line))
|
|
35
|
+
break;
|
|
36
|
+
const trimmed = line.replace(/^\s+/, '').replace(/,\s*$/, '');
|
|
37
|
+
if (trimmed.length > 0)
|
|
38
|
+
out.push(trimmed);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 解析 `launchctl print gui/<uid>/<label>` 输出。**容错**:缺块/空输出返回全 null,
|
|
44
|
+
* 调用方据此判定「不归属」(保守 → 绝不当成 running)。
|
|
45
|
+
*/
|
|
46
|
+
export function parseLaunchdPrint(output) {
|
|
47
|
+
const lines = String(output ?? '').split('\n');
|
|
48
|
+
const argumentsList = readBlock(lines, 'arguments').map(strip).filter((s) => s.length > 0);
|
|
49
|
+
const environment = {};
|
|
50
|
+
for (const entry of readBlock(lines, 'environment')) {
|
|
51
|
+
const match = /^(.+?)\s*(?:=>|=)\s*(.*)$/.exec(entry);
|
|
52
|
+
if (match && match[1] !== undefined)
|
|
53
|
+
environment[strip(match[1])] = strip(match[2] ?? '');
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
label: readScalar(lines, 'label'),
|
|
57
|
+
path: readScalar(lines, 'path'),
|
|
58
|
+
program: readScalar(lines, 'program'),
|
|
59
|
+
arguments: argumentsList,
|
|
60
|
+
environment,
|
|
61
|
+
workingDirectory: readScalar(lines, 'working directory'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 加载中的作业是否属于本 home。**任一**事实匹配即视为本 home:
|
|
66
|
+
* - 环境 `MEBULAR_HOME` / `MEBULAR_SERVICE_HOME` == home;
|
|
67
|
+
* - `working directory` == home;
|
|
68
|
+
* - plist 路径位于 `<home>/` 之下。
|
|
69
|
+
* 无法判定(解析不到任何事实)→ **false**(保守:宁可说不是本 home,也不误报 running)。
|
|
70
|
+
*/
|
|
71
|
+
export function jobBelongsToHome(job, home) {
|
|
72
|
+
if (!job)
|
|
73
|
+
return false;
|
|
74
|
+
const target = normalizeServicePath(home);
|
|
75
|
+
for (const key of ['MEBULAR_HOME', 'MEBULAR_SERVICE_HOME']) {
|
|
76
|
+
const value = job.environment[key];
|
|
77
|
+
if (typeof value === 'string' && value.length > 0 && normalizeServicePath(value) === target)
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
if (job.workingDirectory && normalizeServicePath(job.workingDirectory) === target)
|
|
81
|
+
return true;
|
|
82
|
+
if (job.path) {
|
|
83
|
+
const p = normalizeServicePath(job.path);
|
|
84
|
+
if (p === target || p.startsWith(target + sep))
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
/** 人类可读的「占用者」描述(用于警告文案;无法识别时给 null)。 */
|
|
90
|
+
export function describeJobOwner(job) {
|
|
91
|
+
if (!job)
|
|
92
|
+
return null;
|
|
93
|
+
const fromEnv = job.environment.MEBULAR_HOME ?? job.environment.MEBULAR_SERVICE_HOME;
|
|
94
|
+
if (typeof fromEnv === 'string' && fromEnv.length > 0)
|
|
95
|
+
return fromEnv;
|
|
96
|
+
if (job.workingDirectory)
|
|
97
|
+
return job.workingDirectory;
|
|
98
|
+
if (job.path)
|
|
99
|
+
return job.path;
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=launchd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchd.js","sourceRoot":"","sources":["../src/launchd.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { type ServiceKind, type ServicePlatform } from './units.js';
|
|
2
|
+
export interface RunResult {
|
|
3
|
+
code: number;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
}
|
|
7
|
+
export type RunCommand = (cmd: string, args: readonly string[]) => RunResult;
|
|
8
|
+
export declare const defaultRun: RunCommand;
|
|
9
|
+
/** install 仪器化留痕:每条外部命令的 argv/rc/stdout/stderr/耗时(供 launchd 竞态诊断)。 */
|
|
10
|
+
export interface RunTraceEntry {
|
|
11
|
+
at: string;
|
|
12
|
+
cmd: string;
|
|
13
|
+
args: string[];
|
|
14
|
+
code: number;
|
|
15
|
+
stdout: string;
|
|
16
|
+
stderr: string;
|
|
17
|
+
durationMs: number;
|
|
18
|
+
}
|
|
19
|
+
/** 单个常驻组件的宿主描述(由 fleet / mebular CLI 提供)。 */
|
|
20
|
+
export interface ServiceDescriptor {
|
|
21
|
+
kind: ServiceKind;
|
|
22
|
+
/** 解释器参数(不含 execPath),通常 `[cliJs, 'node'|'worker'|'serve', ...]` */
|
|
23
|
+
args: readonly string[];
|
|
24
|
+
/** 心跳/配置目录(`service.heartbeat` 落在这里) */
|
|
25
|
+
heartbeatDir: string;
|
|
26
|
+
execPath?: string;
|
|
27
|
+
workingDir?: string;
|
|
28
|
+
env?: Readonly<Record<string, string>>;
|
|
29
|
+
}
|
|
30
|
+
export interface ServiceOptions {
|
|
31
|
+
platform?: ServicePlatform;
|
|
32
|
+
home?: string;
|
|
33
|
+
run?: RunCommand;
|
|
34
|
+
now?: number;
|
|
35
|
+
sha?: string;
|
|
36
|
+
/** 仪器化:非空则对每条外部命令回调一条留痕(CLI 经 MEBULAR_SERVICE_TRACE 落 JSONL)。 */
|
|
37
|
+
trace?: (entry: RunTraceEntry) => void;
|
|
38
|
+
}
|
|
39
|
+
export interface ServiceManifest {
|
|
40
|
+
kind: ServiceKind;
|
|
41
|
+
label: string;
|
|
42
|
+
sha: string;
|
|
43
|
+
autostart: boolean;
|
|
44
|
+
args: string[];
|
|
45
|
+
execPath: string;
|
|
46
|
+
unitPath: string;
|
|
47
|
+
installedAt: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 构建 SHA 兜底 helper:env → 显式 cwd 的 git HEAD → `unknown`。
|
|
51
|
+
* **本模块刻意不读 `import.meta` 与 `process.cwd()`**(F-INSTALL-1):前者 ts-jest 以 CJS 加载 src 会编译失败;
|
|
52
|
+
* 后者是「sha 随 cwd 漂移」的病灶。真正与 cwd 无关的解析(模块邻接 `version.json` → 模块所在仓库 git)
|
|
53
|
+
* 在 ESM 入口边界完成并注入:`mebular service` 经 `packages/mcp/src/build-sha.mjs`、`fleet` 经 `cli.ts buildSha()`,
|
|
54
|
+
* 再经 `runServiceCli({ sha })` / `installService({ sha })` 传入。
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveBuildSha(env?: NodeJS.ProcessEnv, cwd?: string): string;
|
|
57
|
+
export interface InstallResult {
|
|
58
|
+
ok: boolean;
|
|
59
|
+
action: 'install';
|
|
60
|
+
kind: ServiceKind;
|
|
61
|
+
label: string;
|
|
62
|
+
sha: string;
|
|
63
|
+
unitPath: string;
|
|
64
|
+
manifestPath: string;
|
|
65
|
+
autostart: boolean;
|
|
66
|
+
started: boolean;
|
|
67
|
+
/** started=false 时的真实原因(bootstrap 重试耗尽 / 终态校验超时等);绝不谎报。 */
|
|
68
|
+
startError?: string;
|
|
69
|
+
/** 迁移:本次安装顺带卸下的旧(无 home 维度)作业 label。 */
|
|
70
|
+
migratedFrom?: string;
|
|
71
|
+
/** 同 label 被**他人**作业占用 → 已拒绝(绝不顶替/bootout)。 */
|
|
72
|
+
foreign?: boolean;
|
|
73
|
+
warning?: string;
|
|
74
|
+
error?: string;
|
|
75
|
+
}
|
|
76
|
+
/** install 专属选项:时序旋钮全部有界且可注入(测试用小值;CLI 用默认)。 */
|
|
77
|
+
export interface InstallOptions extends ServiceOptions {
|
|
78
|
+
autostart?: boolean;
|
|
79
|
+
label?: string;
|
|
80
|
+
/** bootout 后等待「作业确实卸载」的预算(默认 3000ms;轮询 print 直至查无此作业)。 */
|
|
81
|
+
unloadTimeoutMs?: number;
|
|
82
|
+
/** bootstrap 失败(如 launchd 收尾竞态 error 5)时的尝试次数(默认 4,含首次)。 */
|
|
83
|
+
bootstrapAttempts?: number;
|
|
84
|
+
/** bootstrap 相邻两次尝试间的退避(默认 750ms)。 */
|
|
85
|
+
bootstrapBackoffMs?: number;
|
|
86
|
+
/** 装载后「确认真的起来了」的校验预算(默认 8000ms;轮询新鲜心跳)。 */
|
|
87
|
+
verifyTimeoutMs?: number;
|
|
88
|
+
/** 校验轮询间隔(默认 250ms)。 */
|
|
89
|
+
verifyIntervalMs?: number;
|
|
90
|
+
}
|
|
91
|
+
export declare function installService(descriptor: ServiceDescriptor, options?: InstallOptions): InstallResult;
|
|
92
|
+
export interface UninstallResult {
|
|
93
|
+
ok: boolean;
|
|
94
|
+
action: 'uninstall';
|
|
95
|
+
kind: ServiceKind;
|
|
96
|
+
removed: boolean;
|
|
97
|
+
note?: string;
|
|
98
|
+
}
|
|
99
|
+
export declare function uninstallService(descriptor: ServiceDescriptor, options?: ServiceOptions): UninstallResult;
|
|
100
|
+
/**
|
|
101
|
+
* 某配置目录是否已被常驻服务接管(读 manifest,`args` 含该目录)。dir-scoped:
|
|
102
|
+
* 在 temp 设备目录上恒为 `[]`(除非确有针对它的服务),使 doctor 可复现。
|
|
103
|
+
*/
|
|
104
|
+
export declare function registeredServicesForDir(dir: string, options?: {
|
|
105
|
+
home?: string;
|
|
106
|
+
}): ServiceKind[];
|
|
107
|
+
export interface ServiceStatusEntry {
|
|
108
|
+
kind: ServiceKind;
|
|
109
|
+
label: string;
|
|
110
|
+
registered: boolean;
|
|
111
|
+
running: boolean;
|
|
112
|
+
/** F-SVC-1:存在同 label(或旧全局 label)的**他人**作业 → 跨 home 占用(running 必为 false)。 */
|
|
113
|
+
foreign: boolean;
|
|
114
|
+
/** 跨 home 占用者的 home/路径提示(无则 null)。 */
|
|
115
|
+
foreignOwner: string | null;
|
|
116
|
+
unitPath: string;
|
|
117
|
+
sha: string | null;
|
|
118
|
+
autostart: boolean | null;
|
|
119
|
+
heartbeat: {
|
|
120
|
+
present: boolean;
|
|
121
|
+
fresh: boolean;
|
|
122
|
+
ageMs: number | null;
|
|
123
|
+
pid: number | null;
|
|
124
|
+
sha: string | null;
|
|
125
|
+
role: string | null;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export declare function serviceStatus(descriptor: ServiceDescriptor, options?: ServiceOptions): ServiceStatusEntry;
|
|
129
|
+
/** 一键重启:平台命令(**纯函数**,不执行;未注册为服务时给手动指引)。 */
|
|
130
|
+
export interface RestartPlan {
|
|
131
|
+
kind: ServiceKind;
|
|
132
|
+
label: string;
|
|
133
|
+
registered: boolean;
|
|
134
|
+
/** 需按序执行的命令(每项 = [cmd, ...args]);未注册时为空数组 */
|
|
135
|
+
commands: string[][];
|
|
136
|
+
/** 未注册/不可托管时的手动重启指引 */
|
|
137
|
+
manual?: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 构造重启命令(不执行):
|
|
141
|
+
* - darwin:`launchctl kickstart -k gui/<uid>/<label>`(-k 先杀再拉起,KeepAlive 兜底)
|
|
142
|
+
* - linux :`systemctl --user restart <unit>`(Restart=on-failure 兜底)
|
|
143
|
+
* - win32 :`schtasks /End` + `schtasks /Run`(计划任务无 restart,退化为停+起)
|
|
144
|
+
* 未注册(单元文件与 manifest 都不存在)→ 只给手动指引,绝不盲发 kickstart。
|
|
145
|
+
*/
|
|
146
|
+
export declare function restartPlanFor(descriptor: ServiceDescriptor, options?: ServiceOptions): RestartPlan;
|
|
147
|
+
export interface RestartResult {
|
|
148
|
+
ok: boolean;
|
|
149
|
+
action: 'restart';
|
|
150
|
+
kind: ServiceKind;
|
|
151
|
+
label: string;
|
|
152
|
+
registered: boolean;
|
|
153
|
+
commands: string[][];
|
|
154
|
+
executed: boolean;
|
|
155
|
+
manual?: string;
|
|
156
|
+
}
|
|
157
|
+
/** 执行重启(命令可注入,便于 hermetic 测试)。未注册 → executed:false + 手动指引。 */
|
|
158
|
+
export declare function restartService(descriptor: ServiceDescriptor, options?: ServiceOptions): RestartResult;
|
|
159
|
+
export interface LogsResult {
|
|
160
|
+
kind: ServiceKind;
|
|
161
|
+
stdoutLog: string;
|
|
162
|
+
stderrLog: string;
|
|
163
|
+
stdout: string[];
|
|
164
|
+
stderr: string[];
|
|
165
|
+
}
|
|
166
|
+
export declare function serviceLogs(descriptor: ServiceDescriptor, options?: ServiceOptions & {
|
|
167
|
+
tail?: number;
|
|
168
|
+
}): LogsResult;
|
|
169
|
+
//# sourceMappingURL=manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../src/manager.ts"],"names":[],"mappings":"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"}
|