@actiondock/core 2.0.12 → 2.2.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.
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 +631 -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 +122 -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,175 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { computeManifestDigest, parseJsonWithoutDuplicates } from "./digest.js";
4
+ import { MANIFEST_FILE_NAME } from "./manifest.js";
5
+ export const LOCKFILE_NAME = "actiondock.lock.json";
6
+ export const LOCKFILE_VERSION = 1;
7
+ /**
8
+ * 从指定工程根目录读取并解析 actiondock.lock.json 锁文件。
9
+ * 若文件不存在则返回 null。
10
+ */
11
+ export function loadLockfile(projectRoot) {
12
+ const filePath = join(projectRoot, LOCKFILE_NAME);
13
+ if (!existsSync(filePath)) {
14
+ return null;
15
+ }
16
+ let raw;
17
+ try {
18
+ raw = readFileSync(filePath, "utf-8");
19
+ }
20
+ catch (err) {
21
+ throw new Error(`Failed to read lockfile at ${filePath}: ${err.message}`);
22
+ }
23
+ let parsed;
24
+ try {
25
+ parsed = parseJsonWithoutDuplicates(raw);
26
+ }
27
+ catch (err) {
28
+ throw new Error(`Corrupted or duplicate keys in lockfile at ${filePath}: ${err.message}`);
29
+ }
30
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31
+ throw new Error(`Invalid lockfile format in ${filePath}: expected a JSON object`);
32
+ }
33
+ if (parsed.lockfileVersion !== LOCKFILE_VERSION) {
34
+ throw new Error(`Unsupported lockfileVersion in ${filePath}: received '${parsed.lockfileVersion}', expected ${LOCKFILE_VERSION}`);
35
+ }
36
+ if (!parsed.packages || typeof parsed.packages !== "object" || Array.isArray(parsed.packages)) {
37
+ throw new Error(`Invalid lockfile format in ${filePath}: 'packages' must be an object`);
38
+ }
39
+ // 规范化并注入兼容字段
40
+ for (const [key, item] of Object.entries(parsed.packages)) {
41
+ if (item && typeof item === "object") {
42
+ const locked = item;
43
+ const npmPkg = locked.package || locked.npmPackage || key;
44
+ const resVer = locked.resolved || locked.version || "0.0.0";
45
+ locked.package = npmPkg;
46
+ locked.npmPackage = npmPkg;
47
+ locked.resolved = resVer;
48
+ locked.version = resVer;
49
+ locked.packageId = locked.packageId || key;
50
+ locked.source = locked.source || "npm";
51
+ }
52
+ }
53
+ return parsed;
54
+ }
55
+ /**
56
+ * 将锁文件保存至工程根目录(actiondock.lock.json)。
57
+ * 保证各包按键名升序排序,实现确定性输出。
58
+ */
59
+ export function saveLockfile(projectRoot, lockfile) {
60
+ const filePath = join(projectRoot, LOCKFILE_NAME);
61
+ const sortedPackages = {};
62
+ const sortedKeys = Object.keys(lockfile.packages || {}).sort();
63
+ for (const key of sortedKeys) {
64
+ const pkg = lockfile.packages[key];
65
+ const npmPackage = pkg.package || pkg.npmPackage || key;
66
+ const resolvedVersion = pkg.resolved || pkg.version || "0.0.0";
67
+ const sortedDeps = pkg.dependencies
68
+ ? Object.keys(pkg.dependencies)
69
+ .sort()
70
+ .reduce((acc, depKey) => {
71
+ acc[depKey] = pkg.dependencies[depKey];
72
+ return acc;
73
+ }, {})
74
+ : undefined;
75
+ const entry = {
76
+ package: npmPackage,
77
+ resolved: resolvedVersion,
78
+ source: pkg.source || "npm",
79
+ integrity: pkg.integrity,
80
+ manifestDigest: pkg.manifestDigest,
81
+ dependencies: sortedDeps,
82
+ packageId: pkg.packageId || key,
83
+ npmPackage: npmPackage,
84
+ version: resolvedVersion,
85
+ };
86
+ sortedPackages[key] = entry;
87
+ }
88
+ const payload = {
89
+ lockfileVersion: LOCKFILE_VERSION,
90
+ packages: sortedPackages,
91
+ };
92
+ writeFileSync(filePath, JSON.stringify(payload, null, 2) + "\n", "utf-8");
93
+ }
94
+ /**
95
+ * 校验锁文件结构的合法性及内容完整性。
96
+ * 当提供 projectRoot 时,逐一比对各包实际 actiondock.json 的 RFC 8785 manifestDigest,
97
+ * 若摘要不一致,校验失败并提示需要重新解析。
98
+ */
99
+ export function validateLockfile(lockfile, options) {
100
+ if (!lockfile || typeof lockfile !== "object" || Array.isArray(lockfile)) {
101
+ return { valid: false, errors: ["Lockfile must be an object"] };
102
+ }
103
+ const lf = lockfile;
104
+ const errors = [];
105
+ if (lf.lockfileVersion !== LOCKFILE_VERSION) {
106
+ errors.push(`Lockfile 'lockfileVersion' must be ${LOCKFILE_VERSION} (received '${lf.lockfileVersion}')`);
107
+ }
108
+ if (!lf.packages || typeof lf.packages !== "object" || Array.isArray(lf.packages)) {
109
+ errors.push("Lockfile 'packages' must be an object");
110
+ return { valid: false, errors };
111
+ }
112
+ for (const [key, item] of Object.entries(lf.packages)) {
113
+ if (!item || typeof item !== "object") {
114
+ errors.push(`Package entry '${key}' must be an object`);
115
+ continue;
116
+ }
117
+ const pkgName = item.package || item.npmPackage;
118
+ if (!pkgName || typeof pkgName !== "string") {
119
+ errors.push(`Package '${key}' missing required string property 'package'`);
120
+ }
121
+ const resolved = item.resolved || item.version;
122
+ if (!resolved || typeof resolved !== "string") {
123
+ errors.push(`Package '${key}' missing required string property 'resolved'`);
124
+ }
125
+ if (!item.manifestDigest || typeof item.manifestDigest !== "string") {
126
+ errors.push(`Package '${key}' missing required string property 'manifestDigest'`);
127
+ }
128
+ // 检查项目根目录下的实际清单摘要匹配性
129
+ if (options?.projectRoot && item.manifestDigest) {
130
+ let manifestPath = null;
131
+ const rootManifestPath = join(options.projectRoot, MANIFEST_FILE_NAME);
132
+ // 若为当前工程根包
133
+ if (existsSync(rootManifestPath)) {
134
+ try {
135
+ const rootRaw = readFileSync(rootManifestPath, "utf-8");
136
+ const rootParsed = parseJsonWithoutDuplicates(rootRaw);
137
+ if (rootParsed.id === (item.packageId || key)) {
138
+ manifestPath = rootManifestPath;
139
+ }
140
+ }
141
+ catch {
142
+ // 忽略解析异常
143
+ }
144
+ }
145
+ // 若为外部依赖包,从 node_modules 探测
146
+ if (!manifestPath && pkgName) {
147
+ const candidateNpm = join(options.projectRoot, "node_modules", pkgName, MANIFEST_FILE_NAME);
148
+ const candidateId = join(options.projectRoot, "node_modules", key, MANIFEST_FILE_NAME);
149
+ if (existsSync(candidateNpm)) {
150
+ manifestPath = candidateNpm;
151
+ }
152
+ else if (existsSync(candidateId)) {
153
+ manifestPath = candidateId;
154
+ }
155
+ }
156
+ if (manifestPath && existsSync(manifestPath)) {
157
+ try {
158
+ const manifestRaw = readFileSync(manifestPath, "utf-8");
159
+ const actualParsed = parseJsonWithoutDuplicates(manifestRaw);
160
+ const actualDigest = computeManifestDigest(actualParsed);
161
+ if (actualDigest !== item.manifestDigest) {
162
+ errors.push(`Manifest digest mismatch for package '${item.packageId || key}': expected '${item.manifestDigest}', got '${actualDigest}'. Re-resolution required.`);
163
+ }
164
+ }
165
+ catch (err) {
166
+ errors.push(`Failed to verify manifest digest for package '${item.packageId || key}': ${err.message}`);
167
+ }
168
+ }
169
+ }
170
+ }
171
+ return {
172
+ valid: errors.length === 0,
173
+ errors: errors.length > 0 ? errors : undefined,
174
+ };
175
+ }
@@ -1,12 +1,14 @@
1
- import type { ActionDockManifest, ActionManifestEntry, ManifestSyncResult, SyncManifestOptions } from "./types";
2
- export declare const MANIFEST_FILE_NAME = "actiondock.manifest.json";
1
+ import type { ActionDockManifest } from "./types.js";
2
+ export declare const MANIFEST_FILE_NAME = "actiondock.json";
3
+ export declare const ACTION_ID_REGEX: RegExp;
4
+ export declare const PLAYBOOK_ID_REGEX: RegExp;
3
5
  /**
4
- * 读取并解析项目的声明式清单文件。
6
+ * 读取并解析项目的声明式清单文件(actiondock.json 为唯一事实源)。
5
7
  * 若文件不存在则返回 null。
6
8
  */
7
9
  export declare function loadManifest(projectRoot: string): ActionDockManifest | null;
8
10
  /**
9
- * 保存声明式清单文件至项目根目录。
11
+ * 保存声明式清单文件至项目根目录(actiondock.json)。
10
12
  */
11
13
  export declare function saveManifest(projectRoot: string, manifest: ActionDockManifest): void;
12
14
  /**
@@ -18,27 +20,3 @@ export declare function validateManifest(manifest: unknown, options?: {
18
20
  valid: boolean;
19
21
  errors?: string[];
20
22
  };
21
- /**
22
- * 为单个 Action 构建清单项。
23
- */
24
- export declare function createManifestEntry(options: {
25
- entry: string;
26
- description?: string;
27
- inputSchema?: Record<string, unknown> | boolean;
28
- outputSchema?: Record<string, unknown> | boolean;
29
- uses?: string[];
30
- tags?: string[];
31
- annotations?: Record<string, unknown>;
32
- }): ActionManifestEntry;
33
- /**
34
- * 依据动作源码目录中的 Action 定义,增量同步或校验 actiondock.manifest.json 文件。
35
- *
36
- * @param projectRoot 项目根目录绝对路径
37
- * @param options 同步选项(包括 actionsDir、check、prune、autoInstall)
38
- * @returns 同步变更结果报告
39
- */
40
- export declare function syncManifest(projectRoot: string, options?: SyncManifestOptions): Promise<ManifestSyncResult>;
41
- /**
42
- * 校验当前 actiondock.manifest.json 是否与动作源码定义保持一致(只读检查)。
43
- */
44
- export declare function checkManifestSync(projectRoot: string, options?: Omit<SyncManifestOptions, "check">): Promise<ManifestSyncResult>;
@@ -0,0 +1,220 @@
1
+ import { existsSync, readFileSync, realpathSync, writeFileSync } from "node:fs";
2
+ import { basename, isAbsolute, join, relative, resolve } from "node:path";
3
+ import { isPathOutsideBoundary, PACKAGE_ID_REGEX } from "../utils/index.js";
4
+ export const MANIFEST_FILE_NAME = "actiondock.json";
5
+ export const ACTION_ID_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
6
+ export const PLAYBOOK_ID_REGEX = /^[a-z0-9][a-z0-9._-]*$/;
7
+ /**
8
+ * 读取并解析项目的声明式清单文件(actiondock.json 为唯一事实源)。
9
+ * 若文件不存在则返回 null。
10
+ */
11
+ export function loadManifest(projectRoot) {
12
+ const filePath = join(projectRoot, MANIFEST_FILE_NAME);
13
+ if (!existsSync(filePath)) {
14
+ return null;
15
+ }
16
+ let raw;
17
+ try {
18
+ raw = readFileSync(filePath, "utf-8");
19
+ }
20
+ catch (err) {
21
+ throw new Error(`Failed to read manifest at ${filePath}: ${err.message}`);
22
+ }
23
+ let parsed;
24
+ try {
25
+ parsed = JSON.parse(raw);
26
+ }
27
+ catch (err) {
28
+ throw new Error(`Corrupted JSON in manifest at ${filePath}: ${err.message}`);
29
+ }
30
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
31
+ throw new Error(`Invalid manifest format in ${filePath}: expected a JSON object`);
32
+ }
33
+ if (parsed.schemaVersion !== undefined &&
34
+ (typeof parsed.schemaVersion !== "number" || parsed.schemaVersion < 1 || parsed.schemaVersion > 2)) {
35
+ throw new Error(`Unsupported manifest schemaVersion in ${filePath}: received '${parsed.schemaVersion}', expected 2`);
36
+ }
37
+ if (!parsed.id) {
38
+ parsed.id = basename(projectRoot);
39
+ }
40
+ return parsed;
41
+ }
42
+ /**
43
+ * 保存声明式清单文件至项目根目录(actiondock.json)。
44
+ */
45
+ export function saveManifest(projectRoot, manifest) {
46
+ const filePath = join(projectRoot, MANIFEST_FILE_NAME);
47
+ writeFileSync(filePath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
48
+ }
49
+ /**
50
+ * 校验清单数据结构的合法性与安全性。
51
+ */
52
+ export function validateManifest(manifest, options) {
53
+ if (!manifest || typeof manifest !== "object") {
54
+ return { valid: false, errors: ["Manifest must be an object"] };
55
+ }
56
+ const m = manifest;
57
+ const errors = [];
58
+ if (m.schemaVersion !== undefined &&
59
+ (typeof m.schemaVersion !== "number" || m.schemaVersion < 1 || m.schemaVersion > 2)) {
60
+ errors.push(`Manifest 'schemaVersion' must be 1 or 2 (received '${m.schemaVersion}')`);
61
+ }
62
+ if (m.id !== undefined) {
63
+ if (typeof m.id !== "string" || !PACKAGE_ID_REGEX.test(m.id)) {
64
+ errors.push(`Manifest 'id' is invalid: '${m.id}' (must match ${PACKAGE_ID_REGEX})`);
65
+ }
66
+ }
67
+ if (m.actions !== undefined) {
68
+ if (!m.actions || typeof m.actions !== "object" || Array.isArray(m.actions)) {
69
+ errors.push("Manifest 'actions' must be an object");
70
+ }
71
+ else {
72
+ for (const [actionId, item] of Object.entries(m.actions)) {
73
+ if (!ACTION_ID_REGEX.test(actionId)) {
74
+ errors.push(`Invalid action ID '${actionId}' in manifest. Action IDs must match ${ACTION_ID_REGEX}`);
75
+ }
76
+ if (!item || typeof item !== "object") {
77
+ errors.push(`Action entry '${actionId}' must be an object`);
78
+ continue;
79
+ }
80
+ if (!item.entry || typeof item.entry !== "string") {
81
+ errors.push(`Action '${actionId}' must specify string 'entry'`);
82
+ }
83
+ else {
84
+ if (isAbsolute(item.entry)) {
85
+ errors.push(`Action '${actionId}' entry cannot be an absolute path: ${item.entry}`);
86
+ }
87
+ else if (item.entry.split("/").some((part) => part === ".." || part === "\\..")) {
88
+ errors.push(`Action '${actionId}' entry cannot contain path traversal: ${item.entry}`);
89
+ }
90
+ else if (options?.projectRoot) {
91
+ const resolvedPath = resolve(options.projectRoot, item.entry);
92
+ const rel = relative(options.projectRoot, resolvedPath);
93
+ if (isPathOutsideBoundary(rel)) {
94
+ errors.push(`Action '${actionId}' entry escapes project root: ${item.entry}`);
95
+ }
96
+ else if (existsSync(resolvedPath)) {
97
+ try {
98
+ const realEntry = realpathSync(resolvedPath);
99
+ const realRoot = realpathSync(options.projectRoot);
100
+ const relReal = relative(realRoot, realEntry);
101
+ if (isPathOutsideBoundary(relReal)) {
102
+ errors.push(`Action '${actionId}' entry symlink resolves outside project root: ${item.entry}`);
103
+ }
104
+ }
105
+ catch (err) {
106
+ errors.push(`Failed to resolve real path for Action '${actionId}': ${err.message}`);
107
+ }
108
+ }
109
+ }
110
+ }
111
+ if (item.uses && !Array.isArray(item.uses)) {
112
+ errors.push(`Action '${actionId}' property 'uses' must be an array`);
113
+ }
114
+ if (item.tags && !Array.isArray(item.tags)) {
115
+ errors.push(`Action '${actionId}' property 'tags' must be an array`);
116
+ }
117
+ }
118
+ }
119
+ }
120
+ if (m.playbooks !== undefined) {
121
+ if (!m.playbooks || typeof m.playbooks !== "object" || Array.isArray(m.playbooks)) {
122
+ errors.push("Manifest 'playbooks' must be an object");
123
+ }
124
+ else {
125
+ for (const [playbookId, item] of Object.entries(m.playbooks)) {
126
+ if (!PLAYBOOK_ID_REGEX.test(playbookId)) {
127
+ errors.push(`Invalid playbook ID '${playbookId}' in manifest. Playbook IDs must match ${PLAYBOOK_ID_REGEX}`);
128
+ }
129
+ if (!item || typeof item !== "object") {
130
+ errors.push(`Playbook entry '${playbookId}' must be an object`);
131
+ continue;
132
+ }
133
+ if (!item.entry || typeof item.entry !== "string") {
134
+ errors.push(`Playbook '${playbookId}' must specify string 'entry'`);
135
+ }
136
+ else {
137
+ if (isAbsolute(item.entry)) {
138
+ errors.push(`Playbook '${playbookId}' entry cannot be an absolute path: ${item.entry}`);
139
+ }
140
+ else if (item.entry.split("/").some((part) => part === ".." || part === "\\..")) {
141
+ errors.push(`Playbook '${playbookId}' entry cannot contain path traversal: ${item.entry}`);
142
+ }
143
+ else if (options?.projectRoot) {
144
+ const resolvedPath = resolve(options.projectRoot, item.entry);
145
+ const rel = relative(options.projectRoot, resolvedPath);
146
+ if (isPathOutsideBoundary(rel)) {
147
+ errors.push(`Playbook '${playbookId}' entry escapes project root: ${item.entry}`);
148
+ }
149
+ else if (existsSync(resolvedPath)) {
150
+ try {
151
+ const realEntry = realpathSync(resolvedPath);
152
+ const realRoot = realpathSync(options.projectRoot);
153
+ const relReal = relative(realRoot, realEntry);
154
+ if (isPathOutsideBoundary(relReal)) {
155
+ errors.push(`Playbook '${playbookId}' entry symlink resolves outside project root: ${item.entry}`);
156
+ }
157
+ }
158
+ catch (err) {
159
+ errors.push(`Failed to resolve real path for Playbook '${playbookId}': ${err.message}`);
160
+ }
161
+ }
162
+ }
163
+ }
164
+ if (item.actions && !Array.isArray(item.actions)) {
165
+ errors.push(`Playbook '${playbookId}' property 'actions' must be an array`);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ if (m.dependencies !== undefined) {
171
+ if (!m.dependencies || typeof m.dependencies !== "object" || Array.isArray(m.dependencies)) {
172
+ errors.push("Manifest 'dependencies' must be an object");
173
+ }
174
+ }
175
+ if (m.assets !== undefined) {
176
+ if (!Array.isArray(m.assets)) {
177
+ errors.push("Manifest 'assets' must be an array");
178
+ }
179
+ else {
180
+ for (const asset of m.assets) {
181
+ if (typeof asset !== "string") {
182
+ errors.push("Asset entry must be a string");
183
+ }
184
+ else if (isAbsolute(asset)) {
185
+ errors.push(`Asset path cannot be an absolute path: ${asset}`);
186
+ }
187
+ else if (asset.split("/").some((part) => part === ".." || part === "\\..")) {
188
+ errors.push(`Asset path cannot contain path traversal: ${asset}`);
189
+ }
190
+ else if (options?.projectRoot) {
191
+ const resolvedPath = resolve(options.projectRoot, asset);
192
+ const rel = relative(options.projectRoot, resolvedPath);
193
+ if (isPathOutsideBoundary(rel)) {
194
+ errors.push(`Asset path escapes project root: ${asset}`);
195
+ }
196
+ else if (existsSync(resolvedPath)) {
197
+ try {
198
+ const realAsset = realpathSync(resolvedPath);
199
+ const realRoot = realpathSync(options.projectRoot);
200
+ const relReal = relative(realRoot, realAsset);
201
+ if (isPathOutsideBoundary(relReal)) {
202
+ errors.push(`Asset symlink resolves outside project root: ${asset}`);
203
+ }
204
+ }
205
+ catch (err) {
206
+ errors.push(`Failed to resolve real path for asset '${asset}': ${err.message}`);
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+ }
213
+ if (m.files !== undefined && !Array.isArray(m.files)) {
214
+ errors.push("Manifest 'files' must be an array");
215
+ }
216
+ return {
217
+ valid: errors.length === 0,
218
+ errors: errors.length > 0 ? errors : undefined,
219
+ };
220
+ }
@@ -0,0 +1,116 @@
1
+ import { type ActionDockLockfile } from "./lockfile.js";
2
+ import type { ActionDockManifest, PlaybookDefinition } from "./types.js";
3
+ /**
4
+ * 语义化版本元数据结构。
5
+ */
6
+ export interface SemVer {
7
+ major: number;
8
+ minor: number;
9
+ patch: number;
10
+ prerelease?: string;
11
+ }
12
+ /**
13
+ * 解析基础 SemVer 版本字符串。
14
+ */
15
+ export declare function parseSemVer(v: string): SemVer | null;
16
+ /**
17
+ * 校验两个语义化版本范围是否兼容并能收敛为单一解析版本。
18
+ * 遵循设计规范:直接或传递版本范围无法收敛时拒绝并抛出 ACTION_PACKAGE_VERSION_CONFLICT。
19
+ */
20
+ export declare function areVersionsCompatible(v1: string, v2: string): boolean;
21
+ /**
22
+ * 版本冲突错误。
23
+ */
24
+ export declare class ActionPackageVersionConflictError extends Error {
25
+ readonly code = "ACTION_PACKAGE_VERSION_CONFLICT";
26
+ readonly packageId: string;
27
+ readonly conflicts: Array<{
28
+ requester: string;
29
+ rangeOrVersion: string;
30
+ }>;
31
+ constructor(packageId: string, conflicts: Array<{
32
+ requester: string;
33
+ rangeOrVersion: string;
34
+ }>);
35
+ }
36
+ /**
37
+ * 未声明依赖调用拦截错误。
38
+ */
39
+ export declare class UndeclaredActionDependencyError extends Error {
40
+ readonly code = "UNDECLARED_ACTION_DEPENDENCY";
41
+ readonly targetRef: string;
42
+ readonly caller?: string;
43
+ constructor(targetRef: string, caller?: string, reason?: string);
44
+ }
45
+ /**
46
+ * 已解析 Action 包信息。
47
+ */
48
+ export interface ResolvedPackageInfo {
49
+ packageId: string;
50
+ npmPackage: string;
51
+ version: string;
52
+ packageRoot: string;
53
+ manifest: ActionDockManifest;
54
+ manifestDigest: string;
55
+ isDirect: boolean;
56
+ isRoot: boolean;
57
+ }
58
+ /**
59
+ * 完整 Action 包依赖图结构。
60
+ */
61
+ export interface DependencyGraph {
62
+ rootPackageId: string;
63
+ packages: Map<string, ResolvedPackageInfo>;
64
+ directDependencyIds: Set<string>;
65
+ transitiveDependencyIds: Set<string>;
66
+ visiblePlaybooks: Map<string, PlaybookDefinition>;
67
+ delegatedActions: Set<string>;
68
+ }
69
+ export interface ActionPackageResolverOptions {
70
+ projectRoot: string;
71
+ manifest?: ActionDockManifest;
72
+ lockfile?: ActionDockLockfile;
73
+ allowDevLinks?: boolean;
74
+ customHome?: string;
75
+ packageRoots?: Record<string, string>;
76
+ }
77
+ /**
78
+ * 依赖解析器 ActionPackageResolver。
79
+ * 负责从 actiondock.json.dependencies 与 actiondock.lock.json 建立包索引与依赖闭包,
80
+ * 实施版本冲突检测与调用可见性鉴权。
81
+ */
82
+ export declare class ActionPackageResolver {
83
+ readonly projectRoot: string;
84
+ private options;
85
+ private resolvedGraph?;
86
+ constructor(options: ActionPackageResolverOptions);
87
+ /**
88
+ * 同步解析依赖图。
89
+ */
90
+ resolveSync(): DependencyGraph;
91
+ /**
92
+ * 异步解析依赖图。
93
+ */
94
+ resolve(): Promise<DependencyGraph>;
95
+ /**
96
+ * 判定目标 Action 是否允许作为根调用发起。
97
+ * 规则:
98
+ * - 目标属于当前根包;
99
+ * - 目标属于 actiondock.json 直接依赖;
100
+ * - 目标属于可见 Playbook 精确委托点名的 Action。
101
+ */
102
+ canRootCall(targetPackageId: string, targetActionId: string): boolean;
103
+ /**
104
+ * 校验根调用权限,不合法时抛出 UNDECLARED_ACTION_DEPENDENCY。
105
+ */
106
+ assertRootCallAllowed(targetPackageId: string, targetActionId: string): void;
107
+ /**
108
+ * 判定跨包级联调用是否合法。
109
+ * 规则:调用者 Action 必须在 actiondock.json 的 uses 中显式声明对目标 Action 的依赖。
110
+ */
111
+ canCascadeCall(callerPackageId: string, callerActionId: string, targetPackageId: string, targetActionId: string): boolean;
112
+ /**
113
+ * 校验跨包级联调用权限,不合法时抛出 UNDECLARED_ACTION_DEPENDENCY。
114
+ */
115
+ assertCascadeCallAllowed(callerPackageId: string, callerActionId: string, targetPackageId: string, targetActionId: string): void;
116
+ }