@actiondock/core 2.0.11 → 2.2.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.
Files changed (258) hide show
  1. package/README.md +61 -30
  2. package/dist/app/app.d.ts +73 -0
  3. package/dist/app/app.js +700 -0
  4. package/dist/app/index.d.ts +2 -0
  5. package/dist/app/index.js +2 -0
  6. package/dist/app/types.d.ts +283 -0
  7. package/dist/app/types.js +1 -0
  8. package/dist/catalog/action-index.d.ts +9 -0
  9. package/dist/catalog/action-index.js +93 -0
  10. package/dist/catalog/action-resolver.d.ts +20 -0
  11. package/dist/catalog/action-resolver.js +95 -0
  12. package/dist/catalog/index.d.ts +5 -0
  13. package/dist/catalog/index.js +5 -0
  14. package/dist/catalog/location-registry.d.ts +15 -0
  15. package/dist/catalog/location-registry.js +138 -0
  16. package/dist/catalog/package-catalog.d.ts +7 -0
  17. package/dist/catalog/package-catalog.js +76 -0
  18. package/dist/catalog/types.d.ts +68 -0
  19. package/dist/catalog/types.js +1 -0
  20. package/dist/doctor/doctor.d.ts +7 -0
  21. package/dist/doctor/doctor.js +613 -0
  22. package/dist/doctor/index.d.ts +2 -0
  23. package/dist/doctor/index.js +2 -0
  24. package/dist/doctor/types.d.ts +23 -0
  25. package/dist/doctor/types.js +1 -0
  26. package/dist/errors.d.ts +122 -0
  27. package/dist/errors.js +123 -0
  28. package/dist/execution/index.d.ts +2 -0
  29. package/dist/execution/index.js +2 -0
  30. package/dist/execution/service.d.ts +79 -0
  31. package/dist/execution/service.js +633 -0
  32. package/dist/execution/types.d.ts +131 -0
  33. package/dist/execution/types.js +1 -0
  34. package/dist/export/index.d.ts +1 -0
  35. package/dist/export/index.js +1 -0
  36. package/dist/export/templates.d.ts +71 -0
  37. package/dist/export/templates.js +621 -0
  38. package/dist/filter/index.d.ts +1 -0
  39. package/dist/filter/index.js +1 -0
  40. package/dist/filter/intent.d.ts +33 -0
  41. package/dist/filter/intent.js +123 -0
  42. package/dist/host/host.d.ts +55 -0
  43. package/dist/host/host.js +740 -0
  44. package/dist/host/index.d.ts +2 -0
  45. package/dist/host/index.js +2 -0
  46. package/dist/host/types.d.ts +81 -0
  47. package/dist/host/types.js +1 -0
  48. package/dist/index.d.ts +21 -0
  49. package/dist/index.js +20 -0
  50. package/dist/ipc/diagnostic.d.ts +65 -0
  51. package/dist/ipc/diagnostic.js +129 -0
  52. package/dist/ipc/host.d.ts +7 -0
  53. package/dist/ipc/host.js +93 -0
  54. package/dist/ipc/index.d.ts +4 -0
  55. package/dist/ipc/index.js +4 -0
  56. package/dist/ipc/target.d.ts +56 -0
  57. package/dist/ipc/target.js +297 -0
  58. package/dist/ipc/types.d.ts +42 -0
  59. package/dist/ipc/types.js +1 -0
  60. package/dist/platform/default.d.ts +32 -0
  61. package/dist/platform/default.js +40 -0
  62. package/dist/platform/index.d.ts +3 -0
  63. package/dist/platform/index.js +3 -0
  64. package/dist/platform/node-fs.d.ts +37 -0
  65. package/dist/platform/node-fs.js +81 -0
  66. package/dist/platform/types.d.ts +104 -0
  67. package/dist/platform/types.js +1 -0
  68. package/dist/profile/client.d.ts +162 -0
  69. package/dist/profile/client.js +467 -0
  70. package/dist/profile/index.d.ts +3 -0
  71. package/dist/profile/index.js +3 -0
  72. package/dist/profile/manager.d.ts +47 -0
  73. package/dist/profile/manager.js +294 -0
  74. package/dist/profile/types.d.ts +61 -0
  75. package/dist/profile/types.js +1 -0
  76. package/dist/project/closure.d.ts +25 -0
  77. package/dist/project/closure.js +68 -0
  78. package/dist/project/digest.d.ts +29 -0
  79. package/dist/project/digest.js +324 -0
  80. package/dist/project/index.d.ts +10 -0
  81. package/dist/project/index.js +10 -0
  82. package/dist/project/init.d.ts +26 -0
  83. package/dist/project/init.js +175 -0
  84. package/dist/project/loader.d.ts +99 -0
  85. package/dist/project/loader.js +484 -0
  86. package/dist/project/lockfile.d.ts +55 -0
  87. package/dist/project/lockfile.js +175 -0
  88. package/dist/project/manifest.d.ts +22 -0
  89. package/dist/project/manifest.js +220 -0
  90. package/dist/project/resolver.d.ts +116 -0
  91. package/dist/project/resolver.js +376 -0
  92. package/dist/project/transactions.d.ts +103 -0
  93. package/dist/project/transactions.js +1203 -0
  94. package/dist/project/types-generator.d.ts +33 -0
  95. package/dist/project/types-generator.js +168 -0
  96. package/dist/project/types.d.ts +107 -0
  97. package/dist/project/types.js +1 -0
  98. package/dist/registry/index.d.ts +5 -0
  99. package/dist/registry/index.js +5 -0
  100. package/dist/registry/lock.d.ts +27 -0
  101. package/dist/registry/lock.js +93 -0
  102. package/dist/registry/registry.d.ts +45 -0
  103. package/dist/registry/registry.js +611 -0
  104. package/dist/registry/resolve.d.ts +62 -0
  105. package/dist/registry/resolve.js +195 -0
  106. package/dist/registry/scan.d.ts +38 -0
  107. package/dist/registry/scan.js +135 -0
  108. package/dist/registry/types.d.ts +117 -0
  109. package/dist/registry/types.js +1 -0
  110. package/dist/runtime/action-collection.d.ts +27 -0
  111. package/dist/runtime/action-collection.js +57 -0
  112. package/dist/runtime/clock.d.ts +19 -0
  113. package/dist/runtime/clock.js +17 -0
  114. package/dist/runtime/context.d.ts +86 -0
  115. package/dist/runtime/context.js +242 -0
  116. package/dist/runtime/env.d.ts +56 -0
  117. package/dist/runtime/env.js +159 -0
  118. package/dist/runtime/events.d.ts +93 -0
  119. package/dist/runtime/events.js +604 -0
  120. package/dist/runtime/execution-manager.d.ts +48 -0
  121. package/dist/runtime/execution-manager.js +66 -0
  122. package/dist/runtime/index.d.ts +10 -0
  123. package/dist/runtime/index.js +10 -0
  124. package/dist/runtime/module-loader.d.ts +34 -0
  125. package/dist/runtime/module-loader.js +16 -0
  126. package/dist/runtime/process.d.ts +9 -0
  127. package/dist/runtime/process.js +146 -0
  128. package/dist/runtime/runner.d.ts +293 -0
  129. package/dist/runtime/runner.js +984 -0
  130. package/dist/runtime/standalone.d.ts +89 -0
  131. package/dist/runtime/standalone.js +536 -0
  132. package/dist/schema/validator.d.ts +28 -0
  133. package/dist/schema/validator.js +116 -0
  134. package/dist/server/body.d.ts +40 -0
  135. package/dist/server/body.js +93 -0
  136. package/dist/server/index.d.ts +5 -0
  137. package/dist/server/index.js +5 -0
  138. package/dist/server/routes/actions.d.ts +5 -0
  139. package/dist/server/routes/actions.js +298 -0
  140. package/dist/server/routes/common.d.ts +47 -0
  141. package/dist/server/routes/common.js +104 -0
  142. package/dist/server/routes/config.d.ts +6 -0
  143. package/dist/server/routes/config.js +173 -0
  144. package/dist/server/routes/doctor.d.ts +5 -0
  145. package/dist/server/routes/doctor.js +63 -0
  146. package/dist/server/routes/health.d.ts +5 -0
  147. package/dist/server/routes/health.js +35 -0
  148. package/dist/server/routes/index.d.ts +9 -0
  149. package/dist/server/routes/index.js +9 -0
  150. package/dist/server/routes/info.d.ts +7 -0
  151. package/dist/server/routes/info.js +158 -0
  152. package/dist/server/routes/playbooks.d.ts +5 -0
  153. package/dist/server/routes/playbooks.js +134 -0
  154. package/dist/server/routes/runs.d.ts +5 -0
  155. package/dist/server/routes/runs.js +343 -0
  156. package/dist/server/routes/state.d.ts +6 -0
  157. package/dist/server/routes/state.js +169 -0
  158. package/dist/server/security.d.ts +44 -0
  159. package/dist/server/security.js +101 -0
  160. package/dist/server/server.d.ts +15 -0
  161. package/dist/server/server.js +355 -0
  162. package/dist/server/types.d.ts +78 -0
  163. package/dist/server/types.js +1 -0
  164. package/dist/storage/data-dir-lock.d.ts +119 -0
  165. package/dist/storage/data-dir-lock.js +1016 -0
  166. package/dist/storage/driver.d.ts +9 -0
  167. package/dist/storage/driver.js +59 -0
  168. package/dist/storage/index.d.ts +64 -0
  169. package/dist/storage/index.js +98 -0
  170. package/dist/storage/lazy.d.ts +8 -0
  171. package/dist/storage/lazy.js +44 -0
  172. package/dist/storage/mask.d.ts +26 -0
  173. package/dist/storage/mask.js +48 -0
  174. package/dist/storage/params.d.ts +13 -0
  175. package/dist/storage/params.js +32 -0
  176. package/dist/storage/sqlite.d.ts +79 -0
  177. package/dist/storage/sqlite.js +689 -0
  178. package/dist/storage/types.d.ts +172 -0
  179. package/dist/storage/types.js +14 -0
  180. package/dist/target/index.d.ts +4 -0
  181. package/dist/target/index.js +4 -0
  182. package/dist/target/local.d.ts +52 -0
  183. package/dist/target/local.js +343 -0
  184. package/dist/target/remote.d.ts +76 -0
  185. package/dist/target/remote.js +839 -0
  186. package/dist/target/target.d.ts +6 -0
  187. package/dist/target/target.js +35 -0
  188. package/dist/target/types.d.ts +246 -0
  189. package/dist/target/types.js +34 -0
  190. package/dist/utils/index.d.ts +49 -0
  191. package/dist/utils/index.js +180 -0
  192. package/dist/version.d.ts +4 -0
  193. package/dist/version.js +4 -0
  194. package/package.json +13 -13
  195. package/src/build/index.ts +0 -1
  196. package/src/build/templates.ts +0 -59
  197. package/src/catalog/action-index.ts +0 -92
  198. package/src/catalog/action-resolver.ts +0 -120
  199. package/src/catalog/index.ts +0 -5
  200. package/src/catalog/location-registry.ts +0 -134
  201. package/src/catalog/package-catalog.ts +0 -87
  202. package/src/catalog/types.ts +0 -75
  203. package/src/doctor/doctor.ts +0 -419
  204. package/src/doctor/index.ts +0 -2
  205. package/src/doctor/types.ts +0 -25
  206. package/src/execution/index.ts +0 -2
  207. package/src/execution/service.ts +0 -322
  208. package/src/execution/types.ts +0 -73
  209. package/src/export/index.ts +0 -1
  210. package/src/export/templates.ts +0 -590
  211. package/src/filter/index.ts +0 -1
  212. package/src/filter/intent.ts +0 -154
  213. package/src/index.ts +0 -16
  214. package/src/profile/client.ts +0 -552
  215. package/src/profile/index.ts +0 -3
  216. package/src/profile/manager.ts +0 -341
  217. package/src/profile/types.ts +0 -71
  218. package/src/project/index.ts +0 -4
  219. package/src/project/init.ts +0 -242
  220. package/src/project/loader.ts +0 -479
  221. package/src/project/manifest.ts +0 -299
  222. package/src/project/types.ts +0 -141
  223. package/src/registry/index.ts +0 -2
  224. package/src/registry/registry.ts +0 -940
  225. package/src/registry/types.ts +0 -127
  226. package/src/runtime/clock.ts +0 -41
  227. package/src/runtime/context.ts +0 -259
  228. package/src/runtime/env.ts +0 -172
  229. package/src/runtime/events.ts +0 -142
  230. package/src/runtime/execution-manager.ts +0 -74
  231. package/src/runtime/index.ts +0 -10
  232. package/src/runtime/module-loader.ts +0 -67
  233. package/src/runtime/process.ts +0 -245
  234. package/src/runtime/runner.ts +0 -527
  235. package/src/runtime/standalone.ts +0 -429
  236. package/src/schema/validator.ts +0 -61
  237. package/src/server/body.ts +0 -112
  238. package/src/server/index.ts +0 -7
  239. package/src/server/routes/actions.ts +0 -262
  240. package/src/server/routes/common.ts +0 -126
  241. package/src/server/routes/config.ts +0 -147
  242. package/src/server/routes/doctor.ts +0 -29
  243. package/src/server/routes/health.ts +0 -40
  244. package/src/server/routes/index.ts +0 -9
  245. package/src/server/routes/info.ts +0 -221
  246. package/src/server/routes/playbooks.ts +0 -109
  247. package/src/server/routes/runs.ts +0 -301
  248. package/src/server/routes/state.ts +0 -133
  249. package/src/server/runtime-registry.ts +0 -115
  250. package/src/server/security.ts +0 -115
  251. package/src/server/server.ts +0 -251
  252. package/src/server/types.ts +0 -57
  253. package/src/storage/driver.ts +0 -126
  254. package/src/storage/index.ts +0 -81
  255. package/src/storage/mask.ts +0 -34
  256. package/src/storage/sqlite.ts +0 -714
  257. package/src/storage/types.ts +0 -139
  258. package/src/utils/index.ts +0 -62
@@ -1,142 +0,0 @@
1
- import type { ExecutionEvent } from "@actiondock/sdk";
2
-
3
- export interface EventSink {
4
- emit(event: ExecutionEvent): void;
5
- subscribe(
6
- runId: string,
7
- options?: { after?: number; signal?: AbortSignal }
8
- ): AsyncIterable<ExecutionEvent>;
9
- clear(runId: string): void;
10
- }
11
-
12
- /**
13
- * 进程内有界事件缓冲区实现。
14
- * 遵循设计文档:每个运行最多保留 1024 条或 1MB 事件,支持单调序号。
15
- */
16
- export class InMemoryEventSink implements EventSink {
17
- private eventsByRun = new Map<string, ExecutionEvent[]>();
18
- private listeners = new Map<string, Set<(event: ExecutionEvent) => void>>();
19
- private maxEventsPerRun = 1024;
20
-
21
- emit(event: ExecutionEvent): void {
22
- let list = this.eventsByRun.get(event.runId);
23
- if (!list) {
24
- list = [];
25
- this.eventsByRun.set(event.runId, list);
26
- }
27
-
28
- if (list.length >= this.maxEventsPerRun) {
29
- // 淘汰旧日志和进度事件,保留状态和结果
30
- const nonEssentialIndex = list.findIndex((e) => e.type === "log" || e.type === "progress");
31
- if (nonEssentialIndex >= 0) {
32
- list.splice(nonEssentialIndex, 1);
33
- } else {
34
- list.shift();
35
- }
36
- }
37
- list.push(event);
38
-
39
- const subs = this.listeners.get(event.runId);
40
- if (subs) {
41
- for (const listener of subs) {
42
- try {
43
- listener(event);
44
- } catch {
45
- // 忽略单个监听器内部异常
46
- }
47
- }
48
- }
49
- }
50
-
51
- async *subscribe(
52
- runId: string,
53
- options: { after?: number; signal?: AbortSignal } = {}
54
- ): AsyncIterable<ExecutionEvent> {
55
- const after = options.after ?? -1;
56
- const history = this.eventsByRun.get(runId) || [];
57
-
58
- for (const evt of history) {
59
- if (evt.sequence > after) {
60
- yield evt;
61
- }
62
- }
63
-
64
- const lastEvt = history[history.length - 1];
65
- if (lastEvt && (lastEvt.type === "finish" || lastEvt.type === "status" && lastEvt.status !== "running")) {
66
- return;
67
- }
68
-
69
- const queue: ExecutionEvent[] = [];
70
- let notify: (() => void) | null = null;
71
- let done = false;
72
-
73
- const listener = (evt: ExecutionEvent) => {
74
- if (evt.sequence > after) {
75
- queue.push(evt);
76
- if (notify) {
77
- notify();
78
- notify = null;
79
- }
80
- if (evt.type === "finish") {
81
- done = true;
82
- }
83
- }
84
- };
85
-
86
- let subs = this.listeners.get(runId);
87
- if (!subs) {
88
- subs = new Set();
89
- this.listeners.set(runId, subs);
90
- }
91
- subs.add(listener);
92
-
93
- const cleanup = () => {
94
- subs?.delete(listener);
95
- if (subs && subs.size === 0) {
96
- this.listeners.delete(runId);
97
- }
98
- };
99
-
100
- if (options.signal) {
101
- options.signal.addEventListener("abort", () => {
102
- done = true;
103
- if (notify) {
104
- notify();
105
- notify = null;
106
- }
107
- }, { once: true });
108
- }
109
-
110
- try {
111
- while (!done && !options.signal?.aborted) {
112
- if (queue.length > 0) {
113
- yield queue.shift()!;
114
- } else {
115
- await new Promise<void>((resolve) => {
116
- notify = resolve;
117
- });
118
- }
119
- }
120
- while (queue.length > 0) {
121
- yield queue.shift()!;
122
- }
123
- } finally {
124
- cleanup();
125
- }
126
- }
127
-
128
- clear(runId: string): void {
129
- this.eventsByRun.delete(runId);
130
- this.listeners.delete(runId);
131
- }
132
- }
133
-
134
- let defaultEventSink: EventSink = new InMemoryEventSink();
135
-
136
- export function getDefaultEventSink(): EventSink {
137
- return defaultEventSink;
138
- }
139
-
140
- export function setDefaultEventSink(sink: EventSink): void {
141
- defaultEventSink = sink;
142
- }
@@ -1,74 +0,0 @@
1
- import type { ExecutionHandle } from "./runner";
2
-
3
- /**
4
- * 内存中活跃执行句柄(In-flight Execution Handles)生命周期管理器。
5
- *
6
- * 职责:
7
- * 1. 集中追踪正在执行的异步 Action 任务句柄。
8
- * 2. 任务完成(无论成功或失败)后通过 Promise.finally 自动注销,防止内存泄漏。
9
- * 3. 支持依据 runId 查询、遍历或主动发送取消信号(cancel)。
10
- */
11
- export class ExecutionManager {
12
- private active = new Map<string, ExecutionHandle>();
13
-
14
- /**
15
- * 注册一个活跃的执行句柄。
16
- * 当底层 Promise 完成结算(resolve/reject)时,自动从活跃映射表中移除。
17
- *
18
- * @param handle 执行句柄对象
19
- */
20
- public register(handle: ExecutionHandle): void {
21
- this.active.set(handle.runId, handle);
22
- handle.result.finally(() => {
23
- this.active.delete(handle.runId);
24
- });
25
- }
26
-
27
- /**
28
- * 根据 runId 检索正在执行的任务句柄。
29
- *
30
- * @param runId 运行 ID
31
- * @returns 匹配的 ExecutionHandle 或 undefined
32
- */
33
- public get(runId: string): ExecutionHandle | undefined {
34
- return this.active.get(runId);
35
- }
36
-
37
- /**
38
- * 向指定 runId 的在途执行任务发送取消信号。
39
- *
40
- * @param runId 目标运行 ID
41
- * @param reason 取消原因描述
42
- * @returns 若句柄存在且成功触发中断返回 true;若任务不存在或已完成返回 false
43
- */
44
- public cancel(runId: string, reason?: string): boolean {
45
- const handle = this.active.get(runId);
46
- if (!handle) {
47
- return false;
48
- }
49
- return handle.cancel(reason);
50
- }
51
-
52
- /**
53
- * 手动从活跃集合中移除执行句柄。
54
- *
55
- * @param runId 运行 ID
56
- */
57
- public remove(runId: string): void {
58
- this.active.delete(runId);
59
- }
60
-
61
- /**
62
- * 获取当前系统中所有正在执行的活跃句柄列表。
63
- */
64
- public list(): ExecutionHandle[] {
65
- return Array.from(this.active.values());
66
- }
67
-
68
- /**
69
- * 清空所有活跃执行句柄。
70
- */
71
- public clear(): void {
72
- this.active.clear();
73
- }
74
- }
@@ -1,10 +0,0 @@
1
- export * from "./context";
2
- export * from "./runner";
3
- export * from "./execution-manager";
4
- export * from "./standalone";
5
- export * from "./env";
6
- export * from "./clock";
7
- export * from "./process";
8
- export * from "./events";
9
- export * from "./module-loader";
10
-
@@ -1,67 +0,0 @@
1
- import { pathToFileURL } from "node:url";
2
-
3
- /**
4
- * 统一源码模块加载器接口。
5
- * 解耦 Action 与各类扩展模块的具体加载机制(如 ECMAScript 原生 import、tsx 动态转译加载等)。
6
- */
7
- export interface ModuleLoader {
8
- /**
9
- * 解析模块标识符为绝对路径或完整 URL。
10
- *
11
- * @param specifier 模块规范说明符或物理路径
12
- * @param parentPath 发起解析的父级文件或目录路径
13
- */
14
- resolve?(specifier: string, parentPath?: string): string;
15
-
16
- /**
17
- * 动态加载模块并返回命名空间全量导出对象。
18
- *
19
- * @param specifier 模块规范说明符或物理路径
20
- * @param parentPath 发起加载的父级文件或目录路径
21
- */
22
- load<T = any>(specifier: string, parentPath?: string): Promise<T>;
23
-
24
- /**
25
- * 加载模块并解包其默认导出(default 或 action 属性)。
26
- *
27
- * @param specifier 模块规范说明符或物理路径
28
- * @param parentPath 发起加载的父级文件或目录路径
29
- */
30
- loadDefault?<T = any>(specifier: string, parentPath?: string): Promise<T>;
31
- }
32
-
33
- /**
34
- * 基于标准 ECMAScript 动态 import 的默认模块加载器。
35
- */
36
- export class DefaultModuleLoader implements ModuleLoader {
37
- async load<T = any>(specifier: string, _parentPath?: string): Promise<T> {
38
- const importSpecifier = specifier.startsWith("file://")
39
- ? specifier
40
- : pathToFileURL(specifier).href;
41
- return (await import(importSpecifier)) as T;
42
- }
43
-
44
- async loadDefault<T = any>(specifier: string, parentPath?: string): Promise<T> {
45
- const mod = await this.load<any>(specifier, parentPath);
46
- return (mod?.default !== undefined ? mod.default : mod?.action !== undefined ? mod.action : mod) as T;
47
- }
48
- }
49
-
50
- let globalModuleLoader: ModuleLoader | undefined;
51
-
52
- /**
53
- * 注册全局模块加载器实现。
54
- */
55
- export function setModuleLoader(loader: ModuleLoader): void {
56
- globalModuleLoader = loader;
57
- }
58
-
59
- /**
60
- * 获取当前全局模块加载器,若未显式注册则回退使用 DefaultModuleLoader。
61
- */
62
- export function getModuleLoader(): ModuleLoader {
63
- if (!globalModuleLoader) {
64
- globalModuleLoader = new DefaultModuleLoader();
65
- }
66
- return globalModuleLoader;
67
- }
@@ -1,245 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import type {
3
- DetachedProcessOptions,
4
- DetachedProcessResult,
5
- ProcessAPI,
6
- ProcessExecOptions,
7
- ProcessResult,
8
- RuntimeError,
9
- } from "@actiondock/sdk";
10
-
11
- export type ProcessExecutor = ProcessAPI;
12
-
13
- let globalProcessExecutor: ProcessExecutor | undefined;
14
-
15
- export function setProcessExecutor(executor: ProcessExecutor): void {
16
- globalProcessExecutor = executor;
17
- }
18
-
19
- export function getProcessExecutor(): ProcessExecutor {
20
- if (!globalProcessExecutor) {
21
- globalProcessExecutor = new DefaultProcessExecutor();
22
- }
23
- return globalProcessExecutor;
24
- }
25
-
26
- /**
27
- * 基于 Node.js 标准 child_process 实现的基础进程执行器。
28
- */
29
- export class DefaultProcessExecutor implements ProcessExecutor {
30
- async exec(
31
- command: string,
32
- args: string[] = [],
33
- options: ProcessExecOptions = {}
34
- ): Promise<ProcessResult> {
35
- const startTime = Date.now();
36
- const maxOutputBytes = options.maxOutputBytes ?? 10 * 1024 * 1024;
37
-
38
- return new Promise<ProcessResult>((resolve, reject) => {
39
- let stdoutBuf = "";
40
- let stderrBuf = "";
41
- let totalBytes = 0;
42
- let timedOut = false;
43
- let cancelled = false;
44
- let error: RuntimeError | undefined;
45
-
46
- const cp = spawn(command, args, {
47
- cwd: options.cwd,
48
- env: options.env ? { ...process.env, ...options.env } : process.env,
49
- stdio: ["pipe", "pipe", "pipe"],
50
- });
51
-
52
- if (options.input) {
53
- cp.stdin.write(options.input);
54
- cp.stdin.end();
55
- } else {
56
- cp.stdin.end();
57
- }
58
-
59
- let timer: ReturnType<typeof setTimeout> | undefined;
60
- if (options.timeoutMs && options.timeoutMs > 0) {
61
- timer = setTimeout(() => {
62
- timedOut = true;
63
- cp.kill("SIGTERM");
64
- setTimeout(() => {
65
- if (!cp.killed) cp.kill("SIGKILL");
66
- }, 1000);
67
- }, options.timeoutMs);
68
- }
69
-
70
- const onAbort = () => {
71
- cancelled = true;
72
- cp.kill("SIGTERM");
73
- setTimeout(() => {
74
- if (!cp.killed) cp.kill("SIGKILL");
75
- }, 1000);
76
- };
77
-
78
- if (options.signal) {
79
- if (options.signal.aborted) {
80
- onAbort();
81
- } else {
82
- options.signal.addEventListener("abort", onAbort, { once: true });
83
- }
84
- }
85
-
86
- cp.stdout?.on("data", (chunk: Buffer) => {
87
- totalBytes += chunk.length;
88
- if (totalBytes > maxOutputBytes) {
89
- error = {
90
- code: "PROCESS_OUTPUT_LIMIT",
91
- message: `Process output exceeded limit of ${maxOutputBytes} bytes`,
92
- };
93
- cp.kill("SIGKILL");
94
- return;
95
- }
96
- stdoutBuf += chunk.toString("utf-8");
97
- });
98
-
99
- cp.stderr?.on("data", (chunk: Buffer) => {
100
- totalBytes += chunk.length;
101
- if (totalBytes > maxOutputBytes) {
102
- error = {
103
- code: "PROCESS_OUTPUT_LIMIT",
104
- message: `Process output exceeded limit of ${maxOutputBytes} bytes`,
105
- };
106
- cp.kill("SIGKILL");
107
- return;
108
- }
109
- stderrBuf += chunk.toString("utf-8");
110
- });
111
-
112
- cp.on("error", (err) => {
113
- if (timer) clearTimeout(timer);
114
- const durationMs = Date.now() - startTime;
115
- const res: ProcessResult = {
116
- ok: false,
117
- exitCode: null,
118
- stdout: stdoutBuf.trim(),
119
- stderr: stderrBuf.trim() || err.message,
120
- raw: new TextEncoder().encode(stdoutBuf),
121
- timedOut,
122
- cancelled,
123
- durationMs,
124
- error: error || {
125
- code: "PROCESS_SPAWN_ERROR",
126
- message: err.message,
127
- },
128
- };
129
- if (options.throwOnError) {
130
- reject(new Error(err.message));
131
- } else {
132
- resolve(res);
133
- }
134
- });
135
-
136
- cp.on("close", (exitCode, signal) => {
137
- if (timer) clearTimeout(timer);
138
- const durationMs = Date.now() - startTime;
139
- const ok = exitCode === 0 && !timedOut && !cancelled && !error;
140
-
141
- const res: ProcessResult = {
142
- ok,
143
- exitCode,
144
- signal: signal || undefined,
145
- stdout: stdoutBuf.trim(),
146
- stderr: stderrBuf.trim(),
147
- raw: new TextEncoder().encode(stdoutBuf),
148
- timedOut,
149
- cancelled,
150
- durationMs,
151
- error,
152
- };
153
-
154
- if (!ok && options.throwOnError) {
155
- reject(new Error(stderrBuf.trim() || `Process exited with code ${exitCode}`));
156
- } else {
157
- resolve(res);
158
- }
159
- });
160
- });
161
- }
162
-
163
- async spawnDetached(options: DetachedProcessOptions): Promise<DetachedProcessResult> {
164
- const startTime = Date.now();
165
- try {
166
- const child = spawn(options.command, options.args || [], {
167
- cwd: options.cwd,
168
- env: options.env ? { ...process.env, ...options.env } : process.env,
169
- detached: true,
170
- stdio: "ignore",
171
- });
172
-
173
- child.unref();
174
-
175
- if (!options.probe) {
176
- return {
177
- ok: true,
178
- pid: child.pid,
179
- ready: true,
180
- durationMs: Date.now() - startTime,
181
- };
182
- }
183
-
184
- const probeInterval = options.probeIntervalMs ?? 200;
185
- const probeTimeout = options.probeTimeoutMs ?? 5000;
186
- const deadline = Date.now() + probeTimeout;
187
-
188
- while (Date.now() < deadline) {
189
- if (options.signal?.aborted) {
190
- return {
191
- ok: false,
192
- pid: child.pid,
193
- ready: false,
194
- durationMs: Date.now() - startTime,
195
- error: {
196
- code: "PROCESS_CANCELLED",
197
- message: "Probe was cancelled by AbortSignal",
198
- },
199
- };
200
- }
201
-
202
- try {
203
- const res: DetachedProcessResult = {
204
- ok: true,
205
- pid: child.pid,
206
- ready: true,
207
- durationMs: Date.now() - startTime,
208
- };
209
- const isReady = await options.probe(res as any);
210
- if (isReady) {
211
- return res;
212
- }
213
- } catch {
214
- // 探测失败继续轮询
215
- }
216
-
217
- const remaining = deadline - Date.now();
218
- if (remaining <= 0) break;
219
- const sleepTime = Math.min(probeInterval, remaining);
220
- await new Promise((r) => setTimeout(r, sleepTime));
221
- }
222
-
223
- return {
224
- ok: false,
225
- pid: child.pid,
226
- ready: false,
227
- durationMs: Date.now() - startTime,
228
- error: {
229
- code: "PROCESS_PROBE_TIMEOUT",
230
- message: `Process probe timed out after ${probeTimeout}ms`,
231
- },
232
- };
233
- } catch (err: any) {
234
- return {
235
- ok: false,
236
- ready: false,
237
- durationMs: Date.now() - startTime,
238
- error: {
239
- code: "PROCESS_DETACHED_FAILED",
240
- message: err.message,
241
- },
242
- };
243
- }
244
- }
245
- }