@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.
- package/README.md +61 -30
- package/dist/app/app.d.ts +73 -0
- package/dist/app/app.js +700 -0
- package/dist/app/index.d.ts +2 -0
- package/dist/app/index.js +2 -0
- package/dist/app/types.d.ts +283 -0
- package/dist/app/types.js +1 -0
- package/dist/catalog/action-index.d.ts +1 -1
- package/dist/catalog/action-index.js +93 -0
- package/dist/catalog/action-resolver.d.ts +2 -2
- package/dist/catalog/action-resolver.js +95 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -0
- package/dist/catalog/location-registry.d.ts +5 -5
- package/dist/catalog/location-registry.js +138 -0
- package/dist/catalog/package-catalog.d.ts +2 -2
- package/dist/catalog/package-catalog.js +76 -0
- package/dist/catalog/types.d.ts +2 -2
- package/dist/catalog/types.js +1 -0
- package/dist/doctor/doctor.d.ts +2 -1
- package/dist/doctor/doctor.js +613 -0
- package/dist/doctor/index.d.ts +2 -2
- package/dist/doctor/index.js +2 -0
- package/dist/doctor/types.js +1 -0
- package/dist/errors.d.ts +122 -0
- package/dist/errors.js +123 -0
- package/dist/execution/index.d.ts +2 -2
- package/dist/execution/index.js +2 -0
- package/dist/execution/service.d.ts +46 -41
- package/dist/execution/service.js +633 -0
- package/dist/execution/types.d.ts +59 -2
- package/dist/execution/types.js +1 -0
- package/dist/export/index.d.ts +1 -1
- package/dist/export/index.js +1 -0
- package/dist/export/templates.d.ts +45 -7
- package/dist/export/templates.js +621 -0
- package/dist/filter/index.d.ts +1 -1
- package/dist/filter/index.js +1 -0
- package/dist/filter/intent.js +123 -0
- package/dist/host/host.d.ts +55 -0
- package/dist/host/host.js +740 -0
- package/dist/host/index.d.ts +2 -0
- package/dist/host/index.js +2 -0
- package/dist/host/types.d.ts +81 -0
- package/dist/host/types.js +1 -0
- package/dist/index.d.ts +21 -16
- package/dist/index.js +20 -8458
- package/dist/ipc/diagnostic.d.ts +65 -0
- package/dist/ipc/diagnostic.js +129 -0
- package/dist/ipc/host.d.ts +7 -0
- package/dist/ipc/host.js +93 -0
- package/dist/ipc/index.d.ts +4 -0
- package/dist/ipc/index.js +4 -0
- package/dist/ipc/target.d.ts +56 -0
- package/dist/ipc/target.js +297 -0
- package/dist/ipc/types.d.ts +42 -0
- package/dist/ipc/types.js +1 -0
- package/dist/platform/default.d.ts +32 -0
- package/dist/platform/default.js +40 -0
- package/dist/platform/index.d.ts +3 -0
- package/dist/platform/index.js +3 -0
- package/dist/platform/node-fs.d.ts +37 -0
- package/dist/platform/node-fs.js +81 -0
- package/dist/platform/types.d.ts +104 -0
- package/dist/platform/types.js +1 -0
- package/dist/profile/client.d.ts +58 -12
- package/dist/profile/client.js +467 -0
- package/dist/profile/index.d.ts +3 -3
- package/dist/profile/index.js +3 -0
- package/dist/profile/manager.d.ts +2 -2
- package/dist/profile/manager.js +294 -0
- package/dist/profile/types.d.ts +1 -1
- package/dist/profile/types.js +1 -0
- package/dist/project/closure.js +68 -0
- package/dist/project/digest.d.ts +29 -0
- package/dist/project/digest.js +324 -0
- package/dist/project/index.d.ts +10 -5
- package/dist/project/index.js +10 -0
- package/dist/project/init.d.ts +3 -4
- package/dist/project/init.js +175 -0
- package/dist/project/loader.d.ts +21 -27
- package/dist/project/loader.js +484 -0
- package/dist/project/lockfile.d.ts +55 -0
- package/dist/project/lockfile.js +175 -0
- package/dist/project/manifest.d.ts +6 -28
- package/dist/project/manifest.js +220 -0
- package/dist/project/resolver.d.ts +116 -0
- package/dist/project/resolver.js +376 -0
- package/dist/project/transactions.d.ts +103 -0
- package/dist/project/transactions.js +1203 -0
- package/dist/project/types-generator.d.ts +33 -0
- package/dist/project/types-generator.js +168 -0
- package/dist/project/types.d.ts +58 -82
- package/dist/project/types.js +1 -0
- package/dist/registry/index.d.ts +5 -2
- package/dist/registry/index.js +5 -0
- package/dist/registry/lock.d.ts +27 -0
- package/dist/registry/lock.js +93 -0
- package/dist/registry/registry.d.ts +32 -11
- package/dist/registry/registry.js +611 -0
- package/dist/registry/resolve.d.ts +62 -0
- package/dist/registry/resolve.js +195 -0
- package/dist/registry/scan.d.ts +38 -0
- package/dist/registry/scan.js +135 -0
- package/dist/registry/types.d.ts +1 -1
- package/dist/registry/types.js +1 -0
- package/dist/runtime/action-collection.d.ts +27 -0
- package/dist/runtime/action-collection.js +57 -0
- package/dist/runtime/clock.d.ts +0 -2
- package/dist/runtime/clock.js +17 -0
- package/dist/runtime/context.d.ts +16 -5
- package/dist/runtime/context.js +242 -0
- package/dist/runtime/env.d.ts +3 -1
- package/dist/runtime/env.js +159 -0
- package/dist/runtime/events.d.ts +46 -14
- package/dist/runtime/events.js +604 -0
- package/dist/runtime/execution-manager.d.ts +1 -1
- package/dist/runtime/execution-manager.js +66 -0
- package/dist/runtime/index.d.ts +10 -9
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/module-loader.d.ts +0 -8
- package/dist/runtime/module-loader.js +16 -0
- package/dist/runtime/process.d.ts +2 -4
- package/dist/runtime/process.js +146 -0
- package/dist/runtime/runner.d.ts +117 -10
- package/dist/runtime/runner.js +984 -0
- package/dist/runtime/standalone.d.ts +68 -19
- package/dist/runtime/standalone.js +536 -0
- package/dist/schema/validator.js +116 -0
- package/dist/server/body.js +93 -0
- package/dist/server/index.d.ts +5 -6
- package/dist/server/index.js +5 -0
- package/dist/server/routes/actions.d.ts +2 -2
- package/dist/server/routes/actions.js +298 -0
- package/dist/server/routes/common.d.ts +28 -17
- package/dist/server/routes/common.js +104 -0
- package/dist/server/routes/config.d.ts +2 -1
- package/dist/server/routes/config.js +173 -0
- package/dist/server/routes/doctor.d.ts +2 -2
- package/dist/server/routes/doctor.js +63 -0
- package/dist/server/routes/health.d.ts +2 -2
- package/dist/server/routes/health.js +35 -0
- package/dist/server/routes/index.d.ts +9 -9
- package/dist/server/routes/index.js +9 -0
- package/dist/server/routes/info.d.ts +4 -2
- package/dist/server/routes/info.js +158 -0
- package/dist/server/routes/playbooks.d.ts +2 -2
- package/dist/server/routes/playbooks.js +134 -0
- package/dist/server/routes/runs.d.ts +2 -2
- package/dist/server/routes/runs.js +343 -0
- package/dist/server/routes/state.d.ts +2 -1
- package/dist/server/routes/state.js +169 -0
- package/dist/server/security.d.ts +11 -4
- package/dist/server/security.js +101 -0
- package/dist/server/server.d.ts +6 -6
- package/dist/server/server.js +355 -0
- package/dist/server/types.d.ts +27 -8
- package/dist/server/types.js +1 -0
- package/dist/storage/data-dir-lock.d.ts +119 -0
- package/dist/storage/data-dir-lock.js +1016 -0
- package/dist/storage/driver.d.ts +4 -10
- package/dist/storage/driver.js +59 -0
- package/dist/storage/index.d.ts +30 -10
- package/dist/storage/index.js +98 -0
- package/dist/storage/lazy.d.ts +8 -0
- package/dist/storage/lazy.js +44 -0
- package/dist/storage/mask.d.ts +12 -5
- package/dist/storage/mask.js +48 -0
- package/dist/storage/params.d.ts +13 -0
- package/dist/storage/params.js +32 -0
- package/dist/storage/sqlite.d.ts +19 -6
- package/dist/storage/sqlite.js +689 -0
- package/dist/storage/types.d.ts +57 -6
- package/dist/storage/types.js +14 -0
- package/dist/target/index.d.ts +4 -0
- package/dist/target/index.js +4 -0
- package/dist/target/local.d.ts +52 -0
- package/dist/target/local.js +343 -0
- package/dist/target/remote.d.ts +76 -0
- package/dist/target/remote.js +839 -0
- package/dist/target/target.d.ts +6 -0
- package/dist/target/target.js +35 -0
- package/dist/target/types.d.ts +246 -0
- package/dist/target/types.js +34 -0
- package/dist/utils/index.d.ts +22 -2
- package/dist/utils/index.js +180 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +4 -0
- package/package.json +6 -7
- package/dist/build/index.d.ts +0 -1
- package/dist/build/templates.d.ts +0 -19
- package/dist/server/runtime-registry.d.ts +0 -78
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { listLinkedPackages } from "../registry/registry.js";
|
|
4
|
+
import { computeManifestDigest, parseJsonWithoutDuplicates } from "./digest.js";
|
|
5
|
+
import { loadLockfile } from "./lockfile.js";
|
|
6
|
+
import { loadPlaybooks } from "./loader.js";
|
|
7
|
+
import { loadManifest, MANIFEST_FILE_NAME } from "./manifest.js";
|
|
8
|
+
/**
|
|
9
|
+
* 解析基础 SemVer 版本字符串。
|
|
10
|
+
*/
|
|
11
|
+
export function parseSemVer(v) {
|
|
12
|
+
const match = v.trim().replace(/^[v=]/, "").match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/);
|
|
13
|
+
if (!match)
|
|
14
|
+
return null;
|
|
15
|
+
return {
|
|
16
|
+
major: parseInt(match[1], 10),
|
|
17
|
+
minor: parseInt(match[2], 10),
|
|
18
|
+
patch: parseInt(match[3], 10),
|
|
19
|
+
prerelease: match[4],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 校验两个语义化版本范围是否兼容并能收敛为单一解析版本。
|
|
24
|
+
* 遵循设计规范:直接或传递版本范围无法收敛时拒绝并抛出 ACTION_PACKAGE_VERSION_CONFLICT。
|
|
25
|
+
*/
|
|
26
|
+
export function areVersionsCompatible(v1, v2) {
|
|
27
|
+
if (v1 === v2 || v1 === "*" || v2 === "*" || !v1 || !v2) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
// 处理前缀范围符(如 ^1.0.0, ~1.0.0)
|
|
31
|
+
const clean1 = v1.trim().replace(/^[\^~=]/, "");
|
|
32
|
+
const clean2 = v2.trim().replace(/^[\^~=]/, "");
|
|
33
|
+
const sem1 = parseSemVer(clean1);
|
|
34
|
+
const sem2 = parseSemVer(clean2);
|
|
35
|
+
if (!sem1 || !sem2) {
|
|
36
|
+
// 非标准 SemVer 时按字面匹配
|
|
37
|
+
return v1 === v2;
|
|
38
|
+
}
|
|
39
|
+
// 若存在主版本号不一致,则严格视为不兼容
|
|
40
|
+
if (sem1.major !== sem2.major) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
// 若带波浪号(~1.2.0),次版本号必须一致
|
|
44
|
+
if (v1.startsWith("~") || v2.startsWith("~")) {
|
|
45
|
+
if (sem1.minor !== sem2.minor) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 版本冲突错误。
|
|
53
|
+
*/
|
|
54
|
+
export class ActionPackageVersionConflictError extends Error {
|
|
55
|
+
code = "ACTION_PACKAGE_VERSION_CONFLICT";
|
|
56
|
+
packageId;
|
|
57
|
+
conflicts;
|
|
58
|
+
constructor(packageId, conflicts) {
|
|
59
|
+
const details = conflicts.map((c) => `'${c.requester}' 要求 '${c.rangeOrVersion}'`).join(", ");
|
|
60
|
+
super(`ACTION_PACKAGE_VERSION_CONFLICT: 逻辑包 '${packageId}' 存在不可收敛的版本冲突: ${details}`);
|
|
61
|
+
this.name = "ActionPackageVersionConflictError";
|
|
62
|
+
this.packageId = packageId;
|
|
63
|
+
this.conflicts = conflicts;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 未声明依赖调用拦截错误。
|
|
68
|
+
*/
|
|
69
|
+
export class UndeclaredActionDependencyError extends Error {
|
|
70
|
+
code = "UNDECLARED_ACTION_DEPENDENCY";
|
|
71
|
+
targetRef;
|
|
72
|
+
caller;
|
|
73
|
+
constructor(targetRef, caller, reason) {
|
|
74
|
+
const msg = caller
|
|
75
|
+
? `UNDECLARED_ACTION_DEPENDENCY: Action '${caller}' 未在 'uses' 中声明对 '${targetRef}' 的跨包依赖`
|
|
76
|
+
: `UNDECLARED_ACTION_DEPENDENCY: 根调用 Action '${targetRef}' 被拒绝: 目标包既非 actiondock.json 直接依赖,亦未被可见 Playbook 委托${reason ? ` (${reason})` : ""}`;
|
|
77
|
+
super(msg);
|
|
78
|
+
this.name = "UndeclaredActionDependencyError";
|
|
79
|
+
this.targetRef = targetRef;
|
|
80
|
+
this.caller = caller;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 依赖解析器 ActionPackageResolver。
|
|
85
|
+
* 负责从 actiondock.json.dependencies 与 actiondock.lock.json 建立包索引与依赖闭包,
|
|
86
|
+
* 实施版本冲突检测与调用可见性鉴权。
|
|
87
|
+
*/
|
|
88
|
+
export class ActionPackageResolver {
|
|
89
|
+
projectRoot;
|
|
90
|
+
options;
|
|
91
|
+
resolvedGraph;
|
|
92
|
+
constructor(options) {
|
|
93
|
+
this.projectRoot = resolve(options.projectRoot);
|
|
94
|
+
this.options = { ...options, projectRoot: this.projectRoot };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 同步解析依赖图。
|
|
98
|
+
*/
|
|
99
|
+
resolveSync() {
|
|
100
|
+
if (this.resolvedGraph) {
|
|
101
|
+
return this.resolvedGraph;
|
|
102
|
+
}
|
|
103
|
+
const rootManifest = this.options.manifest ||
|
|
104
|
+
loadManifest(this.projectRoot) || {
|
|
105
|
+
id: basename(this.projectRoot),
|
|
106
|
+
version: "0.1.0",
|
|
107
|
+
};
|
|
108
|
+
const rootPackageId = rootManifest.id;
|
|
109
|
+
const lockfile = this.options.lockfile ?? loadLockfile(this.projectRoot);
|
|
110
|
+
const packages = new Map();
|
|
111
|
+
const directDependencyIds = new Set();
|
|
112
|
+
const transitiveDependencyIds = new Set();
|
|
113
|
+
const versionRequirements = new Map();
|
|
114
|
+
// 记录根包自身
|
|
115
|
+
const rootDigest = computeManifestDigest(rootManifest);
|
|
116
|
+
packages.set(rootPackageId, {
|
|
117
|
+
packageId: rootPackageId,
|
|
118
|
+
npmPackage: rootPackageId,
|
|
119
|
+
version: rootManifest.version || "0.1.0",
|
|
120
|
+
packageRoot: this.projectRoot,
|
|
121
|
+
manifest: rootManifest,
|
|
122
|
+
manifestDigest: rootDigest,
|
|
123
|
+
isDirect: true,
|
|
124
|
+
isRoot: true,
|
|
125
|
+
});
|
|
126
|
+
directDependencyIds.add(rootPackageId);
|
|
127
|
+
// 探测开发本地软链接覆盖
|
|
128
|
+
const devLinkMap = new Map();
|
|
129
|
+
if (this.options.allowDevLinks) {
|
|
130
|
+
try {
|
|
131
|
+
const linked = listLinkedPackages(this.options.customHome);
|
|
132
|
+
for (const entry of linked) {
|
|
133
|
+
if (existsSync(entry.path)) {
|
|
134
|
+
devLinkMap.set(entry.id, entry.path);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// 忽略扫描外部链接包异常
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function recordRequirement(pkgId, requester, rangeOrVersion) {
|
|
143
|
+
const existing = versionRequirements.get(pkgId) || [];
|
|
144
|
+
for (const req of existing) {
|
|
145
|
+
if (!areVersionsCompatible(req.rangeOrVersion, rangeOrVersion)) {
|
|
146
|
+
throw new ActionPackageVersionConflictError(pkgId, [
|
|
147
|
+
req,
|
|
148
|
+
{ requester, rangeOrVersion },
|
|
149
|
+
]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
existing.push({ requester, rangeOrVersion });
|
|
153
|
+
versionRequirements.set(pkgId, existing);
|
|
154
|
+
}
|
|
155
|
+
// 广度优先遍历解析直接与间接依赖
|
|
156
|
+
const queue = [];
|
|
157
|
+
for (const [depId, spec] of Object.entries(rootManifest.dependencies || {})) {
|
|
158
|
+
directDependencyIds.add(depId);
|
|
159
|
+
recordRequirement(depId, rootPackageId, spec);
|
|
160
|
+
queue.push({
|
|
161
|
+
pkgId: depId,
|
|
162
|
+
requester: rootPackageId,
|
|
163
|
+
rangeOrSpec: spec,
|
|
164
|
+
isDirect: true,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
while (queue.length > 0) {
|
|
168
|
+
const item = queue.shift();
|
|
169
|
+
if (packages.has(item.pkgId)) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// 寻址包安装根目录
|
|
173
|
+
let pkgDir = null;
|
|
174
|
+
const locked = lockfile?.packages?.[item.pkgId];
|
|
175
|
+
// 优先级:显式注入路径 -> 开发软链接覆盖 -> 锁文件记录 -> node_modules 探测
|
|
176
|
+
if (this.options.packageRoots?.[item.pkgId]) {
|
|
177
|
+
pkgDir = this.options.packageRoots[item.pkgId];
|
|
178
|
+
}
|
|
179
|
+
else if (devLinkMap.has(item.pkgId)) {
|
|
180
|
+
pkgDir = devLinkMap.get(item.pkgId);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const candidateNames = [];
|
|
184
|
+
if (locked?.npmPackage) {
|
|
185
|
+
candidateNames.push(locked.npmPackage);
|
|
186
|
+
}
|
|
187
|
+
candidateNames.push(item.rangeOrSpec);
|
|
188
|
+
candidateNames.push(item.pkgId);
|
|
189
|
+
let cur = this.projectRoot;
|
|
190
|
+
while (true) {
|
|
191
|
+
for (const candName of candidateNames) {
|
|
192
|
+
const candPath = join(cur, "node_modules", candName);
|
|
193
|
+
if (existsSync(candPath) && existsSync(join(candPath, MANIFEST_FILE_NAME))) {
|
|
194
|
+
pkgDir = candPath;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (pkgDir)
|
|
199
|
+
break;
|
|
200
|
+
const parent = dirname(cur);
|
|
201
|
+
if (parent === cur)
|
|
202
|
+
break;
|
|
203
|
+
cur = parent;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (!pkgDir || !existsSync(pkgDir)) {
|
|
207
|
+
throw new Error(`PACKAGE_NOT_FOUND: Package '${item.pkgId}' required by '${item.requester}' is not installed in ${this.projectRoot}. Please run 'ad add ${item.rangeOrSpec}'.`);
|
|
208
|
+
}
|
|
209
|
+
const manifestPath = join(pkgDir, MANIFEST_FILE_NAME);
|
|
210
|
+
if (!existsSync(manifestPath)) {
|
|
211
|
+
throw new Error(`Action package '${item.pkgId}' at '${pkgDir}' misses required manifest 'actiondock.json'`);
|
|
212
|
+
}
|
|
213
|
+
let manifestRaw;
|
|
214
|
+
try {
|
|
215
|
+
manifestRaw = readFileSync(manifestPath, "utf-8");
|
|
216
|
+
}
|
|
217
|
+
catch (err) {
|
|
218
|
+
throw new Error(`Failed to read manifest for '${item.pkgId}': ${err.message}`);
|
|
219
|
+
}
|
|
220
|
+
const depManifest = parseJsonWithoutDuplicates(manifestRaw);
|
|
221
|
+
const actualDigest = computeManifestDigest(depManifest);
|
|
222
|
+
// 锁文件摘要校验
|
|
223
|
+
if (locked?.manifestDigest && locked.manifestDigest !== actualDigest) {
|
|
224
|
+
throw new Error(`Manifest digest mismatch for package '${item.pkgId}': expected '${locked.manifestDigest}', got '${actualDigest}'. Re-resolution required.`);
|
|
225
|
+
}
|
|
226
|
+
const pkgVersion = depManifest.version || locked?.version || "0.1.0";
|
|
227
|
+
// 校验版本是否满足已知要求
|
|
228
|
+
const reqs = versionRequirements.get(item.pkgId) || [];
|
|
229
|
+
for (const req of reqs) {
|
|
230
|
+
if (!areVersionsCompatible(req.rangeOrVersion, pkgVersion)) {
|
|
231
|
+
throw new ActionPackageVersionConflictError(item.pkgId, [
|
|
232
|
+
req,
|
|
233
|
+
{ requester: item.pkgId, rangeOrVersion: pkgVersion },
|
|
234
|
+
]);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const info = {
|
|
238
|
+
packageId: item.pkgId,
|
|
239
|
+
npmPackage: locked?.npmPackage || item.rangeOrSpec,
|
|
240
|
+
version: pkgVersion,
|
|
241
|
+
packageRoot: pkgDir,
|
|
242
|
+
manifest: depManifest,
|
|
243
|
+
manifestDigest: actualDigest,
|
|
244
|
+
isDirect: item.isDirect,
|
|
245
|
+
isRoot: false,
|
|
246
|
+
};
|
|
247
|
+
packages.set(item.pkgId, info);
|
|
248
|
+
if (!item.isDirect) {
|
|
249
|
+
transitiveDependencyIds.add(item.pkgId);
|
|
250
|
+
}
|
|
251
|
+
// 继续搜寻传递依赖
|
|
252
|
+
for (const [subDepId, subSpec] of Object.entries(depManifest.dependencies || {})) {
|
|
253
|
+
recordRequirement(subDepId, item.pkgId, subSpec);
|
|
254
|
+
if (!packages.has(subDepId)) {
|
|
255
|
+
queue.push({
|
|
256
|
+
pkgId: subDepId,
|
|
257
|
+
requester: item.pkgId,
|
|
258
|
+
rangeOrSpec: subSpec,
|
|
259
|
+
isDirect: false,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// 收集可见 Playbook 与被委托 Action
|
|
265
|
+
const visiblePlaybooks = new Map();
|
|
266
|
+
const delegatedActions = new Set();
|
|
267
|
+
// 根包 Playbooks
|
|
268
|
+
try {
|
|
269
|
+
const rootPbs = loadPlaybooks(this.projectRoot);
|
|
270
|
+
for (const [id, pb] of rootPbs.entries()) {
|
|
271
|
+
visiblePlaybooks.set(id, pb);
|
|
272
|
+
visiblePlaybooks.set(`${rootPackageId}/${id}`, pb);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// 忽略根包 Playbook 加载异常
|
|
277
|
+
}
|
|
278
|
+
// 直接依赖包 Playbooks
|
|
279
|
+
for (const depId of directDependencyIds) {
|
|
280
|
+
if (depId === rootPackageId)
|
|
281
|
+
continue;
|
|
282
|
+
const depPkg = packages.get(depId);
|
|
283
|
+
if (depPkg) {
|
|
284
|
+
try {
|
|
285
|
+
const depPbs = loadPlaybooks(depPkg.packageRoot);
|
|
286
|
+
for (const [id, pb] of depPbs.entries()) {
|
|
287
|
+
visiblePlaybooks.set(`${depId}/${id}`, pb);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
// 忽略依赖包 Playbook 加载异常
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// 可见 Playbook 点名委托的 Action 收集
|
|
296
|
+
for (const pb of visiblePlaybooks.values()) {
|
|
297
|
+
if (Array.isArray(pb.actions)) {
|
|
298
|
+
for (const actRef of pb.actions) {
|
|
299
|
+
if (typeof actRef === "string" && actRef.trim()) {
|
|
300
|
+
delegatedActions.add(actRef.trim());
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
this.resolvedGraph = {
|
|
306
|
+
rootPackageId,
|
|
307
|
+
packages,
|
|
308
|
+
directDependencyIds,
|
|
309
|
+
transitiveDependencyIds,
|
|
310
|
+
visiblePlaybooks,
|
|
311
|
+
delegatedActions,
|
|
312
|
+
};
|
|
313
|
+
return this.resolvedGraph;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* 异步解析依赖图。
|
|
317
|
+
*/
|
|
318
|
+
async resolve() {
|
|
319
|
+
return this.resolveSync();
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 判定目标 Action 是否允许作为根调用发起。
|
|
323
|
+
* 规则:
|
|
324
|
+
* - 目标属于当前根包;
|
|
325
|
+
* - 目标属于 actiondock.json 直接依赖;
|
|
326
|
+
* - 目标属于可见 Playbook 精确委托点名的 Action。
|
|
327
|
+
*/
|
|
328
|
+
canRootCall(targetPackageId, targetActionId) {
|
|
329
|
+
const graph = this.resolveSync();
|
|
330
|
+
if (targetPackageId === graph.rootPackageId) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
if (graph.directDependencyIds.has(targetPackageId)) {
|
|
334
|
+
return true;
|
|
335
|
+
}
|
|
336
|
+
const qualifiedRef = `${targetPackageId}/${targetActionId}`;
|
|
337
|
+
if (graph.delegatedActions.has(qualifiedRef)) {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* 校验根调用权限,不合法时抛出 UNDECLARED_ACTION_DEPENDENCY。
|
|
344
|
+
*/
|
|
345
|
+
assertRootCallAllowed(targetPackageId, targetActionId) {
|
|
346
|
+
if (!this.canRootCall(targetPackageId, targetActionId)) {
|
|
347
|
+
throw new UndeclaredActionDependencyError(`${targetPackageId}/${targetActionId}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* 判定跨包级联调用是否合法。
|
|
352
|
+
* 规则:调用者 Action 必须在 actiondock.json 的 uses 中显式声明对目标 Action 的依赖。
|
|
353
|
+
*/
|
|
354
|
+
canCascadeCall(callerPackageId, callerActionId, targetPackageId, targetActionId) {
|
|
355
|
+
if (callerPackageId === targetPackageId) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
const graph = this.resolveSync();
|
|
359
|
+
const callerPkg = graph.packages.get(callerPackageId);
|
|
360
|
+
if (!callerPkg) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
const callerActionEntry = callerPkg.manifest.actions?.[callerActionId];
|
|
364
|
+
const usesList = callerActionEntry?.uses || [];
|
|
365
|
+
const targetRef = `${targetPackageId}/${targetActionId}`;
|
|
366
|
+
return usesList.some((u) => u === targetRef || u === `${targetPackageId}/*` || u === targetPackageId);
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* 校验跨包级联调用权限,不合法时抛出 UNDECLARED_ACTION_DEPENDENCY。
|
|
370
|
+
*/
|
|
371
|
+
assertCascadeCallAllowed(callerPackageId, callerActionId, targetPackageId, targetActionId) {
|
|
372
|
+
if (!this.canCascadeCall(callerPackageId, callerActionId, targetPackageId, targetActionId)) {
|
|
373
|
+
throw new UndeclaredActionDependencyError(`${targetPackageId}/${targetActionId}`, `${callerPackageId}/${callerActionId}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export declare const SNAPSHOT_TRACKED_FILES: readonly ["package.json", "actiondock.json", "actiondock.lock.json", "package-lock.json", "npm-shrinkwrap.json", "bun.lock", "bun.lockb", "pnpm-lock.yaml", "yarn.lock"];
|
|
2
|
+
export interface TransactionFileRecord {
|
|
3
|
+
name: string;
|
|
4
|
+
existed: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface TransactionMetadata {
|
|
7
|
+
id: string;
|
|
8
|
+
status: "pending" | "committed" | "rolled_back";
|
|
9
|
+
createdAt: number;
|
|
10
|
+
description?: string;
|
|
11
|
+
files: TransactionFileRecord[];
|
|
12
|
+
}
|
|
13
|
+
export interface ProjectTransaction {
|
|
14
|
+
id: string;
|
|
15
|
+
projectRoot: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
commit(): Promise<void>;
|
|
18
|
+
rollback(options?: {
|
|
19
|
+
frozenInstall?: boolean;
|
|
20
|
+
}): Promise<void>;
|
|
21
|
+
releaseLock(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 检查当前进程 PID 是否处于活跃运行状态。
|
|
25
|
+
*/
|
|
26
|
+
export declare function isPidAlive(pid: number): boolean;
|
|
27
|
+
/**
|
|
28
|
+
* 安全清理陈旧接管守卫(reclaim guard)。
|
|
29
|
+
* 采用原子重命名检疫并严格核对 guardToken 与持有者存活状态,确保仅删除目标陈旧 guard,严禁误删活跃守卫或并发新守卫。
|
|
30
|
+
*
|
|
31
|
+
* @param reclaimPath 目标守卫路径
|
|
32
|
+
* @param expectedGuardToken 预期持有的守卫令牌(必填,拒绝未指定令牌的盲目清理)
|
|
33
|
+
*/
|
|
34
|
+
export declare function safeRemoveStaleProjectReclaimGuard(reclaimPath: string, expectedGuardToken: string): void;
|
|
35
|
+
/**
|
|
36
|
+
* 从工程锁隔离或回滚临时目录名称中解析操作者 PID 与隔离生成时间戳。
|
|
37
|
+
* 命名规范:*.(quarantine|rollback|release).<pid>.<timestamp>.<uuid> 或 *.orphan.<timestamp>.<uuid>
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseProjectQuarantineTimestamp(entryName: string): {
|
|
40
|
+
operatorPid?: number;
|
|
41
|
+
timestamp?: number;
|
|
42
|
+
};
|
|
43
|
+
export declare const parseQuarantineTimestamp: typeof parseProjectQuarantineTimestamp;
|
|
44
|
+
/**
|
|
45
|
+
* 清理过期的工程锁隔离目录(GC 回收机制)。
|
|
46
|
+
* 实施分流存活校验,防止误删活跃持锁者与回滚中目录,同时防止孤儿目录无限泄漏:
|
|
47
|
+
* - 共同前置条件:存活年龄必须超过 maxAgeMs(默认 10000ms),未超期前严禁清理(保持宽限期)。
|
|
48
|
+
* - 活跃隔离目录(quarantine / rollback / release):若 operatorPid 存活或内部 metadata.json 记录的 pid 与 childPids 存活,跳过不予清理。
|
|
49
|
+
* - 孤儿脱离目录(orphan):内部旧 metadata.pid 不再作为续命依据;提取其内部 orphanToken,仅当当前规范主路径(主锁目录与 reclaim 目录)依然持有该凭证时保守跳过;规范路径不存在或已变更凭据时直接安全清理。
|
|
50
|
+
*/
|
|
51
|
+
export declare function cleanStaleProjectQuarantines(parentDir: string, basePrefix: string, maxAgeMs?: number, deadline?: number): void;
|
|
52
|
+
/**
|
|
53
|
+
* 安全回滚当前进程创建的工程修改锁。
|
|
54
|
+
* 优先核对 lockToken,回退核对 sessionToken,防止误删接管者或并发新锁。
|
|
55
|
+
*/
|
|
56
|
+
export declare function safeRollbackProjectLock(lockPath: string, expectedSessionToken: string, expectedLockToken?: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* 安全释放工程主锁。
|
|
59
|
+
* 仅当锁目录中持有当前 lockToken / sessionToken 时才删除,杜绝删除他人新锁。
|
|
60
|
+
*/
|
|
61
|
+
export declare function safeReleaseProjectLock(lockPath: string, expectedSessionToken: string, expectedLockToken?: string): void;
|
|
62
|
+
/**
|
|
63
|
+
* 检查项目修改锁 .actiondock/project.lock 是否存在且持有者 PID 处于存活状态。
|
|
64
|
+
*
|
|
65
|
+
* @param projectRoot 项目根目录
|
|
66
|
+
* @param excludeSelf 是否排除当前进程本身(默认为 true,即仅当其他活跃进程持锁时判定为锁被占用)
|
|
67
|
+
*/
|
|
68
|
+
export declare function isProjectLockHeld(projectRoot: string, excludeSelf?: boolean): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* 获取工程修改排他锁(.actiondock/project.lock)。
|
|
71
|
+
* 引入所有竞争者均遵守的原子 reclaim guard 机制(project.lock.reclaim)。
|
|
72
|
+
* 采用 mkdirSync 原子排他目录创建、renameSync 元数据写入、宽限期重试检测与基于 sessionToken 的释放函数。
|
|
73
|
+
* 避免空文件窗口与 truncate。
|
|
74
|
+
*
|
|
75
|
+
* @param projectRoot 项目根目录
|
|
76
|
+
* @param options 锁配置参数
|
|
77
|
+
*/
|
|
78
|
+
export declare function acquireProjectLock(projectRoot: string, options?: {
|
|
79
|
+
sessionToken?: string;
|
|
80
|
+
acquireTimeoutMs?: number;
|
|
81
|
+
}): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* 依据恢复后的锁文件执行禁用安装脚本的冻结安装,使 node_modules 与声明重新一致。
|
|
84
|
+
*/
|
|
85
|
+
export declare function runFrozenInstall(projectRoot: string): void;
|
|
86
|
+
/**
|
|
87
|
+
* 检查项目是否存在未完成提交的悬空事务。
|
|
88
|
+
*/
|
|
89
|
+
export declare function hasPendingTransactions(projectRoot: string): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* 启动新的工程修改事务快照。
|
|
92
|
+
* 捕获 package.json、actiondock.json、actiondock.lock.json 及各包管理器锁文件当前快照。
|
|
93
|
+
*/
|
|
94
|
+
export declare function beginTransaction(projectRoot: string, description?: string): Promise<ProjectTransaction>;
|
|
95
|
+
/**
|
|
96
|
+
* 依据事务日志恢复所有待恢复的悬空事务快照,并在成功后执行冻结安装。
|
|
97
|
+
* 进入恢复前通过 acquireProjectLock 原子获取排他锁,持有锁执行恢复,完成后通过释放函数安全释放锁。
|
|
98
|
+
* 若获取锁失败(已有活跃进程正在执行)则安全退出并返回空数组,杜绝 TOCTOU 竞争。
|
|
99
|
+
* 若冻结安装失败则抛出 PROJECT_RECOVERY_REQUIRED 并阻止 Host 启动。
|
|
100
|
+
*/
|
|
101
|
+
export declare function recoverPendingTransactions(projectRoot: string, options?: {
|
|
102
|
+
frozenInstall?: boolean;
|
|
103
|
+
}): Promise<string[]>;
|