@actiondock/core 2.0.12 → 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 (192) 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 +1 -1
  9. package/dist/catalog/action-index.js +93 -0
  10. package/dist/catalog/action-resolver.d.ts +2 -2
  11. package/dist/catalog/action-resolver.js +95 -0
  12. package/dist/catalog/index.d.ts +5 -5
  13. package/dist/catalog/index.js +5 -0
  14. package/dist/catalog/location-registry.d.ts +5 -5
  15. package/dist/catalog/location-registry.js +138 -0
  16. package/dist/catalog/package-catalog.d.ts +2 -2
  17. package/dist/catalog/package-catalog.js +76 -0
  18. package/dist/catalog/types.d.ts +2 -2
  19. package/dist/catalog/types.js +1 -0
  20. package/dist/doctor/doctor.d.ts +2 -1
  21. package/dist/doctor/doctor.js +613 -0
  22. package/dist/doctor/index.d.ts +2 -2
  23. package/dist/doctor/index.js +2 -0
  24. package/dist/doctor/types.js +1 -0
  25. package/dist/errors.d.ts +122 -0
  26. package/dist/errors.js +123 -0
  27. package/dist/execution/index.d.ts +2 -2
  28. package/dist/execution/index.js +2 -0
  29. package/dist/execution/service.d.ts +46 -41
  30. package/dist/execution/service.js +633 -0
  31. package/dist/execution/types.d.ts +59 -2
  32. package/dist/execution/types.js +1 -0
  33. package/dist/export/index.d.ts +1 -1
  34. package/dist/export/index.js +1 -0
  35. package/dist/export/templates.d.ts +45 -7
  36. package/dist/export/templates.js +621 -0
  37. package/dist/filter/index.d.ts +1 -1
  38. package/dist/filter/index.js +1 -0
  39. package/dist/filter/intent.js +123 -0
  40. package/dist/host/host.d.ts +55 -0
  41. package/dist/host/host.js +740 -0
  42. package/dist/host/index.d.ts +2 -0
  43. package/dist/host/index.js +2 -0
  44. package/dist/host/types.d.ts +81 -0
  45. package/dist/host/types.js +1 -0
  46. package/dist/index.d.ts +21 -16
  47. package/dist/index.js +20 -8458
  48. package/dist/ipc/diagnostic.d.ts +65 -0
  49. package/dist/ipc/diagnostic.js +129 -0
  50. package/dist/ipc/host.d.ts +7 -0
  51. package/dist/ipc/host.js +93 -0
  52. package/dist/ipc/index.d.ts +4 -0
  53. package/dist/ipc/index.js +4 -0
  54. package/dist/ipc/target.d.ts +56 -0
  55. package/dist/ipc/target.js +297 -0
  56. package/dist/ipc/types.d.ts +42 -0
  57. package/dist/ipc/types.js +1 -0
  58. package/dist/platform/default.d.ts +32 -0
  59. package/dist/platform/default.js +40 -0
  60. package/dist/platform/index.d.ts +3 -0
  61. package/dist/platform/index.js +3 -0
  62. package/dist/platform/node-fs.d.ts +37 -0
  63. package/dist/platform/node-fs.js +81 -0
  64. package/dist/platform/types.d.ts +104 -0
  65. package/dist/platform/types.js +1 -0
  66. package/dist/profile/client.d.ts +58 -12
  67. package/dist/profile/client.js +467 -0
  68. package/dist/profile/index.d.ts +3 -3
  69. package/dist/profile/index.js +3 -0
  70. package/dist/profile/manager.d.ts +2 -2
  71. package/dist/profile/manager.js +294 -0
  72. package/dist/profile/types.d.ts +1 -1
  73. package/dist/profile/types.js +1 -0
  74. package/dist/project/closure.js +68 -0
  75. package/dist/project/digest.d.ts +29 -0
  76. package/dist/project/digest.js +324 -0
  77. package/dist/project/index.d.ts +10 -5
  78. package/dist/project/index.js +10 -0
  79. package/dist/project/init.d.ts +3 -4
  80. package/dist/project/init.js +175 -0
  81. package/dist/project/loader.d.ts +21 -27
  82. package/dist/project/loader.js +484 -0
  83. package/dist/project/lockfile.d.ts +55 -0
  84. package/dist/project/lockfile.js +175 -0
  85. package/dist/project/manifest.d.ts +6 -28
  86. package/dist/project/manifest.js +220 -0
  87. package/dist/project/resolver.d.ts +116 -0
  88. package/dist/project/resolver.js +376 -0
  89. package/dist/project/transactions.d.ts +103 -0
  90. package/dist/project/transactions.js +1203 -0
  91. package/dist/project/types-generator.d.ts +33 -0
  92. package/dist/project/types-generator.js +168 -0
  93. package/dist/project/types.d.ts +58 -82
  94. package/dist/project/types.js +1 -0
  95. package/dist/registry/index.d.ts +5 -2
  96. package/dist/registry/index.js +5 -0
  97. package/dist/registry/lock.d.ts +27 -0
  98. package/dist/registry/lock.js +93 -0
  99. package/dist/registry/registry.d.ts +32 -11
  100. package/dist/registry/registry.js +611 -0
  101. package/dist/registry/resolve.d.ts +62 -0
  102. package/dist/registry/resolve.js +195 -0
  103. package/dist/registry/scan.d.ts +38 -0
  104. package/dist/registry/scan.js +135 -0
  105. package/dist/registry/types.d.ts +1 -1
  106. package/dist/registry/types.js +1 -0
  107. package/dist/runtime/action-collection.d.ts +27 -0
  108. package/dist/runtime/action-collection.js +57 -0
  109. package/dist/runtime/clock.d.ts +0 -2
  110. package/dist/runtime/clock.js +17 -0
  111. package/dist/runtime/context.d.ts +16 -5
  112. package/dist/runtime/context.js +242 -0
  113. package/dist/runtime/env.d.ts +3 -1
  114. package/dist/runtime/env.js +159 -0
  115. package/dist/runtime/events.d.ts +46 -14
  116. package/dist/runtime/events.js +604 -0
  117. package/dist/runtime/execution-manager.d.ts +1 -1
  118. package/dist/runtime/execution-manager.js +66 -0
  119. package/dist/runtime/index.d.ts +10 -9
  120. package/dist/runtime/index.js +10 -0
  121. package/dist/runtime/module-loader.d.ts +0 -8
  122. package/dist/runtime/module-loader.js +16 -0
  123. package/dist/runtime/process.d.ts +2 -4
  124. package/dist/runtime/process.js +146 -0
  125. package/dist/runtime/runner.d.ts +117 -10
  126. package/dist/runtime/runner.js +984 -0
  127. package/dist/runtime/standalone.d.ts +68 -19
  128. package/dist/runtime/standalone.js +536 -0
  129. package/dist/schema/validator.js +116 -0
  130. package/dist/server/body.js +93 -0
  131. package/dist/server/index.d.ts +5 -6
  132. package/dist/server/index.js +5 -0
  133. package/dist/server/routes/actions.d.ts +2 -2
  134. package/dist/server/routes/actions.js +298 -0
  135. package/dist/server/routes/common.d.ts +28 -17
  136. package/dist/server/routes/common.js +104 -0
  137. package/dist/server/routes/config.d.ts +2 -1
  138. package/dist/server/routes/config.js +173 -0
  139. package/dist/server/routes/doctor.d.ts +2 -2
  140. package/dist/server/routes/doctor.js +63 -0
  141. package/dist/server/routes/health.d.ts +2 -2
  142. package/dist/server/routes/health.js +35 -0
  143. package/dist/server/routes/index.d.ts +9 -9
  144. package/dist/server/routes/index.js +9 -0
  145. package/dist/server/routes/info.d.ts +4 -2
  146. package/dist/server/routes/info.js +158 -0
  147. package/dist/server/routes/playbooks.d.ts +2 -2
  148. package/dist/server/routes/playbooks.js +134 -0
  149. package/dist/server/routes/runs.d.ts +2 -2
  150. package/dist/server/routes/runs.js +343 -0
  151. package/dist/server/routes/state.d.ts +2 -1
  152. package/dist/server/routes/state.js +169 -0
  153. package/dist/server/security.d.ts +11 -4
  154. package/dist/server/security.js +101 -0
  155. package/dist/server/server.d.ts +6 -6
  156. package/dist/server/server.js +355 -0
  157. package/dist/server/types.d.ts +27 -8
  158. package/dist/server/types.js +1 -0
  159. package/dist/storage/data-dir-lock.d.ts +119 -0
  160. package/dist/storage/data-dir-lock.js +1016 -0
  161. package/dist/storage/driver.d.ts +4 -10
  162. package/dist/storage/driver.js +59 -0
  163. package/dist/storage/index.d.ts +30 -10
  164. package/dist/storage/index.js +98 -0
  165. package/dist/storage/lazy.d.ts +8 -0
  166. package/dist/storage/lazy.js +44 -0
  167. package/dist/storage/mask.d.ts +12 -5
  168. package/dist/storage/mask.js +48 -0
  169. package/dist/storage/params.d.ts +13 -0
  170. package/dist/storage/params.js +32 -0
  171. package/dist/storage/sqlite.d.ts +19 -6
  172. package/dist/storage/sqlite.js +689 -0
  173. package/dist/storage/types.d.ts +57 -6
  174. package/dist/storage/types.js +14 -0
  175. package/dist/target/index.d.ts +4 -0
  176. package/dist/target/index.js +4 -0
  177. package/dist/target/local.d.ts +52 -0
  178. package/dist/target/local.js +343 -0
  179. package/dist/target/remote.d.ts +76 -0
  180. package/dist/target/remote.js +839 -0
  181. package/dist/target/target.d.ts +6 -0
  182. package/dist/target/target.js +35 -0
  183. package/dist/target/types.d.ts +246 -0
  184. package/dist/target/types.js +34 -0
  185. package/dist/utils/index.d.ts +22 -2
  186. package/dist/utils/index.js +180 -0
  187. package/dist/version.d.ts +1 -1
  188. package/dist/version.js +4 -0
  189. package/package.json +6 -7
  190. package/dist/build/index.d.ts +0 -1
  191. package/dist/build/templates.d.ts +0 -19
  192. package/dist/server/runtime-registry.d.ts +0 -78
@@ -0,0 +1,484 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
4
+ import { basename, dirname, join, resolve } from "node:path";
5
+ import { DefaultModuleLoader } from "../runtime/module-loader.js";
6
+ import { ACTION_LOAD_FAILED, isMissingModuleError } from "../errors.js";
7
+ import { loadManifest, ACTION_ID_REGEX, PLAYBOOK_ID_REGEX } from "./manifest.js";
8
+ /**
9
+ * 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
10
+ *
11
+ * @param cwd 起始搜索目录(默认为 process.cwd())
12
+ * @returns 项目根目录绝对路径,若未找到则返回 null
13
+ */
14
+ export function findProjectRoot(cwd) {
15
+ let current;
16
+ try {
17
+ current = resolve(cwd || process.cwd());
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ while (true) {
23
+ try {
24
+ const configPath = join(current, "actiondock.json");
25
+ if (existsSync(configPath)) {
26
+ return current;
27
+ }
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ const parent = dirname(current);
33
+ if (parent === current) {
34
+ break;
35
+ }
36
+ current = parent;
37
+ }
38
+ return null;
39
+ }
40
+ import { PACKAGE_ID_REGEX, assertPathWithinRoot, assertWithinProjectRoot, } from "../utils/index.js";
41
+ /**
42
+ * 加载并校验指定目录下的 `actiondock.json` 配置文件。
43
+ *
44
+ * @param projectRoot 项目根目录绝对路径
45
+ * @returns 解析后的 ProjectConfig 对象
46
+ * @throws {Error} 若文件不存在或 JSON 格式错误、缺失必要字段
47
+ */
48
+ export function loadProjectConfig(projectRoot) {
49
+ const configPath = join(projectRoot, "actiondock.json");
50
+ if (!existsSync(configPath)) {
51
+ throw new Error(`actiondock.json not found in ${projectRoot}`);
52
+ }
53
+ const content = readFileSync(configPath, "utf-8");
54
+ try {
55
+ const parsed = JSON.parse(content);
56
+ const isScoped = /^@[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+$/.test(parsed.id);
57
+ if (!parsed.id || typeof parsed.id !== "string" || (!PACKAGE_ID_REGEX.test(parsed.id) && !isScoped)) {
58
+ throw new Error(`actiondock.json invalid or missing 'id': '${parsed?.id}' (must match ${PACKAGE_ID_REGEX})`);
59
+ }
60
+ if (!parsed.name || typeof parsed.name !== "string") {
61
+ parsed.name = parsed.id;
62
+ }
63
+ if (!parsed.version || typeof parsed.version !== "string") {
64
+ parsed.version = "0.1.0";
65
+ }
66
+ if (parsed.actionsDir) {
67
+ assertWithinProjectRoot(projectRoot, parsed.actionsDir, "actionsDir");
68
+ }
69
+ if (parsed.playbooksDir) {
70
+ assertWithinProjectRoot(projectRoot, parsed.playbooksDir, "playbooksDir");
71
+ }
72
+ return parsed;
73
+ }
74
+ catch (err) {
75
+ throw new Error(`Failed to parse actiondock.json: ${err.message}`);
76
+ }
77
+ }
78
+ export const ALLOWED_INSTALLERS = new Set(["npm", "bun"]);
79
+ /**
80
+ * 探测宿主系统中可用的包管理工具。
81
+ * 优先级:
82
+ * - 环境变量 ACTIONDOCK_INSTALLER 显式指定(严格白名单校验:npm, bun);
83
+ * - 依据项目根目录下现存的锁文件进行精确匹配(bun.lock/bun.lockb -> bun, package-lock.json/npm-shrinkwrap.json -> npm);
84
+ * - 候选回退优先级探测(npm > bun)。
85
+ */
86
+ export function getInstallCommand(projectRoot) {
87
+ const preferred = process.env.ACTIONDOCK_INSTALLER?.trim();
88
+ if (preferred) {
89
+ if (ALLOWED_INSTALLERS.has(preferred)) {
90
+ return [preferred, "install"];
91
+ }
92
+ process.stderr.write(`[actiondock] Warning: Ignored unsupported or invalid ACTIONDOCK_INSTALLER '${preferred}'. Allowed values: npm, bun.\n`);
93
+ }
94
+ if (projectRoot) {
95
+ const lockfileMap = [
96
+ ["bun.lockb", "bun"],
97
+ ["bun.lock", "bun"],
98
+ ["package-lock.json", "npm"],
99
+ ["npm-shrinkwrap.json", "npm"],
100
+ ];
101
+ for (const [lockFile, pm] of lockfileMap) {
102
+ if (existsSync(join(projectRoot, lockFile))) {
103
+ return [pm, "install"];
104
+ }
105
+ }
106
+ }
107
+ const candidates = [
108
+ ["npm", "install"],
109
+ ["bun", "install"],
110
+ ];
111
+ for (const [pm, action] of candidates) {
112
+ try {
113
+ const check = spawnSync(pm, ["--version"], {
114
+ stdio: "pipe",
115
+ // Windows 兼容:npm 是 .cmd 脚本,无 shell 直接 spawn 必然 ENOENT,
116
+ // 会导致探测误判 npm 缺失而错误回退选择 bun
117
+ shell: process.platform === "win32",
118
+ });
119
+ if (check.status === 0) {
120
+ return [pm, action];
121
+ }
122
+ }
123
+ catch {
124
+ // 继续探测下一个候选包管理器
125
+ }
126
+ }
127
+ return ["npm", "install"];
128
+ }
129
+ /**
130
+ * 缓存依赖指纹的文件相对路径(位于 node_modules/.cache/actiondock 下)。
131
+ * 基于项目绝对路径生成唯一哈希标识,避免软链接共享 node_modules 时指纹冲突。
132
+ */
133
+ function getDependencyFingerprintPath(projectRoot) {
134
+ const projectKey = createHash("sha256").update(resolve(projectRoot)).digest("hex").slice(0, 16);
135
+ return join(projectRoot, "node_modules", ".cache", "actiondock", `${projectKey}-deps.hash`);
136
+ }
137
+ /**
138
+ * 读取项目当前缓存的依赖指纹哈希。
139
+ */
140
+ export function readStoredDependencyFingerprint(projectRoot) {
141
+ const fpPath = getDependencyFingerprintPath(projectRoot);
142
+ try {
143
+ if (existsSync(fpPath)) {
144
+ return readFileSync(fpPath, "utf-8").trim();
145
+ }
146
+ }
147
+ catch {
148
+ // 忽略读取异常
149
+ }
150
+ return null;
151
+ }
152
+ /**
153
+ * 将最新的依赖指纹哈希写入缓存文件。
154
+ */
155
+ export function saveDependencyFingerprint(projectRoot, fingerprint) {
156
+ try {
157
+ const fpPath = getDependencyFingerprintPath(projectRoot);
158
+ const dir = dirname(fpPath);
159
+ if (!existsSync(dir)) {
160
+ mkdirSync(dir, { recursive: true });
161
+ }
162
+ writeFileSync(fpPath, fingerprint, "utf-8");
163
+ }
164
+ catch {
165
+ // 忽略写入异常(如只读文件系统或权限限制)
166
+ }
167
+ }
168
+ /**
169
+ * 递归对对象所有键名进行升序排序,保证 JSON.stringify 序列化结果的唯一性与确定性。
170
+ */
171
+ function sortObjectKeys(obj) {
172
+ if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
173
+ return obj;
174
+ }
175
+ const sorted = {};
176
+ for (const key of Object.keys(obj).sort()) {
177
+ sorted[key] = sortObjectKeys(obj[key]);
178
+ }
179
+ return sorted;
180
+ }
181
+ /**
182
+ * 计算项目依赖的指纹哈希(基于 package.json 依赖声明及可能存在的锁文件内容)。
183
+ *
184
+ * @param projectRoot 项目根目录
185
+ * @returns 64位十六进制哈希字符串,若 package.json 不存在或解析失败则返回 null
186
+ */
187
+ export function computeDependencyFingerprint(projectRoot) {
188
+ const pkgJsonPath = join(projectRoot, "package.json");
189
+ if (!existsSync(pkgJsonPath)) {
190
+ return null;
191
+ }
192
+ try {
193
+ const raw = readFileSync(pkgJsonPath, "utf-8");
194
+ const pkg = JSON.parse(raw);
195
+ const depSpec = {
196
+ dependencies: pkg.dependencies || {},
197
+ devDependencies: pkg.devDependencies || {},
198
+ peerDependencies: pkg.peerDependencies || {},
199
+ optionalDependencies: pkg.optionalDependencies || {},
200
+ overrides: pkg.overrides || {},
201
+ resolutions: pkg.resolutions || {},
202
+ };
203
+ const hash = createHash("sha256");
204
+ hash.update(JSON.stringify(sortObjectKeys(depSpec)));
205
+ // 锁文件变化(如团队协同合并或手动更新锁文件)同样代表依赖版本变更
206
+ const lockFiles = [
207
+ "package-lock.json",
208
+ "npm-shrinkwrap.json",
209
+ "bun.lockb",
210
+ "bun.lock",
211
+ ];
212
+ for (const lockFile of lockFiles) {
213
+ const lockPath = join(projectRoot, lockFile);
214
+ if (existsSync(lockPath)) {
215
+ try {
216
+ hash.update(lockFile);
217
+ hash.update(readFileSync(lockPath));
218
+ }
219
+ catch {
220
+ // 忽略不可读的锁文件
221
+ }
222
+ }
223
+ }
224
+ return hash.digest("hex");
225
+ }
226
+ catch {
227
+ return null;
228
+ }
229
+ }
230
+ /**
231
+ * 确保项目依赖(node_modules)已正确安装。
232
+ * 若尚未安装、依赖版本变更(指纹不匹配)或加载失败时,自动触发包管理器执行依赖安装。
233
+ *
234
+ * @param projectRoot 项目根目录
235
+ * @param force 是否强制重新安装
236
+ * @returns 是否成功执行了安装或更新
237
+ */
238
+ export function ensureProjectDependencies(projectRoot, force = false) {
239
+ if (process.env.ACTIONDOCK_AUTO_INSTALL === "false") {
240
+ return false;
241
+ }
242
+ const pkgJsonPath = join(projectRoot, "package.json");
243
+ if (!existsSync(pkgJsonPath)) {
244
+ return false;
245
+ }
246
+ let pkg;
247
+ try {
248
+ const raw = readFileSync(pkgJsonPath, "utf-8");
249
+ pkg = JSON.parse(raw);
250
+ }
251
+ catch {
252
+ return false;
253
+ }
254
+ const hasDeps = (pkg.dependencies && Object.keys(pkg.dependencies).length > 0) ||
255
+ (pkg.devDependencies && Object.keys(pkg.devDependencies).length > 0) ||
256
+ (pkg.peerDependencies && Object.keys(pkg.peerDependencies).length > 0) ||
257
+ (pkg.optionalDependencies && Object.keys(pkg.optionalDependencies).length > 0);
258
+ if (!hasDeps && !force) {
259
+ return false;
260
+ }
261
+ const nodeModulesPath = join(projectRoot, "node_modules");
262
+ const nodeModulesExists = existsSync(nodeModulesPath);
263
+ const currentFingerprint = computeDependencyFingerprint(projectRoot);
264
+ if (!force && nodeModulesExists) {
265
+ const storedFingerprint = readStoredDependencyFingerprint(projectRoot);
266
+ if (storedFingerprint === null) {
267
+ // 首次接入已包含 node_modules 的外部环境或既有项目,信任现有依赖并记录初始指纹
268
+ if (currentFingerprint) {
269
+ saveDependencyFingerprint(projectRoot, currentFingerprint);
270
+ }
271
+ return false;
272
+ }
273
+ if (currentFingerprint && storedFingerprint === currentFingerprint) {
274
+ // 依赖声明与锁文件指纹一致,无需重新安装
275
+ return false;
276
+ }
277
+ }
278
+ // ActionDock 2.0 彻底移除运行时静默在线安装依赖逻辑,严禁静默联网安装
279
+ return false;
280
+ }
281
+ /**
282
+ * 递归扫描指定目录下的特定后缀文件(自动排除测试文件 *.test.ts, *.spec.ts 和 *.d.ts)。
283
+ */
284
+ function scanFiles(dir, extension) {
285
+ if (!existsSync(dir))
286
+ return [];
287
+ const results = [];
288
+ function walk(current) {
289
+ const entries = readdirSync(current);
290
+ for (const entry of entries) {
291
+ const fullPath = join(current, entry);
292
+ const stat = statSync(fullPath);
293
+ if (stat.isDirectory()) {
294
+ walk(fullPath);
295
+ }
296
+ else if (stat.isFile() && fullPath.endsWith(extension)) {
297
+ // 排除测试与类型声明文件
298
+ if (!fullPath.endsWith(".test.ts") &&
299
+ !fullPath.endsWith(".spec.ts") &&
300
+ !fullPath.endsWith(".d.ts")) {
301
+ results.push(fullPath);
302
+ }
303
+ }
304
+ }
305
+ }
306
+ walk(dir);
307
+ return results;
308
+ }
309
+ /**
310
+ * 发现并检索项目 actions 目录下的所有 Action 源码文件(.ts)。
311
+ *
312
+ * @param projectRoot 项目根目录
313
+ * @param actionsDir actions 子目录名称(默认 "actions")
314
+ */
315
+ export function discoverActionFiles(projectRoot, actionsDir = "actions") {
316
+ const fullDir = join(projectRoot, actionsDir);
317
+ return scanFiles(fullDir, ".ts");
318
+ }
319
+ /**
320
+ * 动态导入并加载项目下的所有 Action 定义对象。
321
+ * 以 actiondock.json 为唯一事实源读取 Action 的元数据契约(id, description, inputSchema, outputSchema, tags, annotations, uses),
322
+ * 源码文件仅提供执行 Handler。
323
+ *
324
+ * @param projectRoot 项目根目录
325
+ * @param _actionsDir actions 子目录(向后兼容保留参数)
326
+ * @param options 模块加载器等选项
327
+ * @returns Map<ActionId, ActionDefinition> 映射
328
+ */
329
+ export async function loadActions(projectRoot, _actionsDir = "actions", options = {}) {
330
+ const actions = new Map();
331
+ const loader = options.loader || new DefaultModuleLoader();
332
+ let manifest = null;
333
+ try {
334
+ manifest = loadManifest(projectRoot);
335
+ }
336
+ catch (err) {
337
+ throw new Error(`Failed to load manifest in ${projectRoot}: ${err.message}`);
338
+ }
339
+ // 以 actiondock.json 为唯一事实源读取 Action 的元数据契约与入口
340
+ if (manifest?.actions && Object.keys(manifest.actions).length > 0) {
341
+ for (const [actionId, item] of Object.entries(manifest.actions)) {
342
+ if (!ACTION_ID_REGEX.test(actionId)) {
343
+ throw new Error(`Invalid action ID '${actionId}' in actiondock.json. Action IDs must match ${ACTION_ID_REGEX}`);
344
+ }
345
+ const entryPath = resolve(projectRoot, item.entry);
346
+ assertPathWithinRoot(projectRoot, entryPath, `action entry '${item.entry}'`);
347
+ if (!existsSync(entryPath)) {
348
+ const error = new Error(`Action '${actionId}' entry file not found: ${item.entry}`);
349
+ error.code = ACTION_LOAD_FAILED;
350
+ error.details = {
351
+ actionId,
352
+ entry: item.entry,
353
+ entryPath,
354
+ hint: `请检查 actiondock.json 中 action '${actionId}' 的 entry 配置 '${item.entry}' 是否正确。`,
355
+ };
356
+ throw error;
357
+ }
358
+ let imported;
359
+ try {
360
+ imported = await loader.load(entryPath);
361
+ }
362
+ catch (err) {
363
+ // 复用 errors.ts 的模块缺失判定单一事实源,保留加载器视角的中文修复提示
364
+ const msg = String(err.message || "");
365
+ const error = new Error(`Failed to load action '${actionId}' from '${item.entry}': ${msg}`);
366
+ error.code = ACTION_LOAD_FAILED;
367
+ error.details = {
368
+ actionId,
369
+ entry: item.entry,
370
+ entryPath,
371
+ rootCause: msg,
372
+ hint: isMissingModuleError(msg)
373
+ ? `项目依赖缺失,请在 '${projectRoot}' 目录下运行 npm install 安装依赖。`
374
+ : undefined,
375
+ };
376
+ throw error;
377
+ }
378
+ const exported = imported?.default ?? imported?.action ?? imported;
379
+ let runFn;
380
+ if (typeof exported === "function") {
381
+ runFn = exported;
382
+ }
383
+ else if (exported && typeof exported.run === "function") {
384
+ runFn = exported.run.bind(exported);
385
+ }
386
+ if (!runFn) {
387
+ const error = new Error(`Action '${actionId}' in '${item.entry}' does not export a runnable handler`);
388
+ error.code = ACTION_LOAD_FAILED;
389
+ error.details = {
390
+ actionId,
391
+ entry: item.entry,
392
+ entryPath,
393
+ hint: `确保 '${item.entry}' 使用 export default defineAction(...) 导出了可执行处理函数。`,
394
+ };
395
+ throw error;
396
+ }
397
+ const def = {
398
+ run: runFn,
399
+ };
400
+ actions.set(actionId, def);
401
+ }
402
+ }
403
+ return actions;
404
+ }
405
+ /**
406
+ * 发现项目 playbooks 目录下的所有 Playbook Markdown 文档(.md)。
407
+ */
408
+ export function discoverPlaybookFiles(projectRoot, playbooksDir = "playbooks") {
409
+ const fullDir = join(projectRoot, playbooksDir);
410
+ return scanFiles(fullDir, ".md");
411
+ }
412
+ /**
413
+ * 解析单个 Playbook Markdown 文件的内容与元数据。
414
+ * Playbook 规程正文为纯 Markdown,其元数据(id, description, actions)直接由调用方传入(源自 actiondock.json)。
415
+ *
416
+ * @param content 文件 Markdown 文本内容
417
+ * @param filePath 物理文件路径
418
+ * @param metadata Playbook 清单元数据
419
+ * @returns PlaybookDefinition 对象
420
+ */
421
+ export function parsePlaybookContent(content, filePath, metadata) {
422
+ const filename = basename(filePath.replace(/\\/g, "/"));
423
+ const defaultId = filename.replace(/\.md$/, "");
424
+ const playbookId = metadata?.id || defaultId;
425
+ if (!PLAYBOOK_ID_REGEX.test(playbookId)) {
426
+ throw new Error(`Invalid playbook ID '${playbookId}' found in ${filePath}. Playbook IDs must match ${PLAYBOOK_ID_REGEX}`);
427
+ }
428
+ // 规程正文为纯 Markdown,剔除可能残存的旧式 Frontmatter 包裹块
429
+ const body = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
430
+ return {
431
+ id: playbookId,
432
+ name: metadata?.name,
433
+ description: metadata?.description,
434
+ actions: Array.isArray(metadata?.actions) ? [...metadata.actions] : [],
435
+ content: body.trim(),
436
+ filePath,
437
+ };
438
+ }
439
+ /**
440
+ * 加载项目声明的 Playbook SOP 规程文档。
441
+ * 以 actiondock.json 中的 playbooks 声明作为唯一事实源。
442
+ *
443
+ * @param projectRoot 项目根目录
444
+ * @param _playbooksDir playbooks 子目录(向后兼容保留参数)
445
+ * @param customManifest 可选的显式清单对象(若未提供则从磁盘加载)
446
+ * @returns Map<PlaybookId, PlaybookDefinition> 映射
447
+ */
448
+ export function loadPlaybooks(projectRoot, _playbooksDir = "playbooks", customManifest) {
449
+ const playbooks = new Map();
450
+ let manifest = null;
451
+ if (customManifest !== undefined) {
452
+ manifest = customManifest;
453
+ }
454
+ else {
455
+ try {
456
+ manifest = loadManifest(projectRoot);
457
+ }
458
+ catch {
459
+ return playbooks;
460
+ }
461
+ }
462
+ // 以 actiondock.json 为唯一事实源
463
+ if (manifest?.playbooks && Object.keys(manifest.playbooks).length > 0) {
464
+ for (const [playbookId, pbEntry] of Object.entries(manifest.playbooks)) {
465
+ if (!PLAYBOOK_ID_REGEX.test(playbookId)) {
466
+ throw new Error(`Invalid playbook ID '${playbookId}' in actiondock.json. Playbook IDs must match ${PLAYBOOK_ID_REGEX}`);
467
+ }
468
+ const fullPath = resolve(projectRoot, pbEntry.entry);
469
+ assertPathWithinRoot(projectRoot, fullPath, `playbook entry '${pbEntry.entry}'`);
470
+ if (!existsSync(fullPath)) {
471
+ throw new Error(`Playbook file '${pbEntry.entry}' for '${playbookId}' not found in ${projectRoot}`);
472
+ }
473
+ const content = readFileSync(fullPath, "utf-8");
474
+ const def = parsePlaybookContent(content, fullPath, {
475
+ id: playbookId,
476
+ name: pbEntry?.name,
477
+ description: pbEntry.description,
478
+ actions: pbEntry.actions,
479
+ });
480
+ playbooks.set(playbookId, def);
481
+ }
482
+ }
483
+ return playbooks;
484
+ }
@@ -0,0 +1,55 @@
1
+ export declare const LOCKFILE_NAME = "actiondock.lock.json";
2
+ export declare const LOCKFILE_VERSION = 1;
3
+ /**
4
+ * 锁文件中记录的单一被锁定的 Action 包元数据。
5
+ */
6
+ export interface LockedPackage {
7
+ /** npm 包名(如 @someone/actiondock-github-actions) */
8
+ package: string;
9
+ /** 解析版本或来源 */
10
+ resolved: string;
11
+ /** 依赖来源,标准为 "npm" */
12
+ source?: "npm" | string;
13
+ /** 完整性校验摘要(可选) */
14
+ integrity?: string;
15
+ /** 基于 RFC 8785 计算的清单确定性摘要 */
16
+ manifestDigest: string;
17
+ /** 该包声明的下级依赖映射 */
18
+ dependencies?: Record<string, string>;
19
+ /** 逻辑包标识(可选兼容别名) */
20
+ packageId?: string;
21
+ /** npm 包名别名(向前兼容) */
22
+ npmPackage?: string;
23
+ /** 语义化版本别名(向前兼容) */
24
+ version?: string;
25
+ }
26
+ /**
27
+ * ActionDock 锁文件契约(actiondock.lock.json)。
28
+ */
29
+ export interface ActionDockLockfile {
30
+ /** 锁文件规范版本(固定为 1) */
31
+ lockfileVersion: 1;
32
+ /** 已锁定解析的包字典映射 */
33
+ packages: Record<string, LockedPackage>;
34
+ }
35
+ /**
36
+ * 从指定工程根目录读取并解析 actiondock.lock.json 锁文件。
37
+ * 若文件不存在则返回 null。
38
+ */
39
+ export declare function loadLockfile(projectRoot: string): ActionDockLockfile | null;
40
+ /**
41
+ * 将锁文件保存至工程根目录(actiondock.lock.json)。
42
+ * 保证各包按键名升序排序,实现确定性输出。
43
+ */
44
+ export declare function saveLockfile(projectRoot: string, lockfile: ActionDockLockfile): void;
45
+ /**
46
+ * 校验锁文件结构的合法性及内容完整性。
47
+ * 当提供 projectRoot 时,逐一比对各包实际 actiondock.json 的 RFC 8785 manifestDigest,
48
+ * 若摘要不一致,校验失败并提示需要重新解析。
49
+ */
50
+ export declare function validateLockfile(lockfile: unknown, options?: {
51
+ projectRoot?: string;
52
+ }): {
53
+ valid: boolean;
54
+ errors?: string[];
55
+ };