@actiondock/core 2.7.0 → 2.8.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 -68
- package/dist/catalog/action-catalog.d.ts +56 -0
- package/dist/catalog/action-catalog.js +202 -0
- package/dist/catalog/discovery.d.ts +60 -0
- package/dist/catalog/discovery.js +119 -0
- package/dist/catalog/graph.d.ts +190 -0
- package/dist/catalog/graph.js +589 -0
- package/dist/catalog/index.d.ts +5 -5
- package/dist/catalog/index.js +5 -5
- package/dist/catalog/resolve-action.d.ts +43 -0
- package/dist/catalog/resolve-action.js +119 -0
- package/dist/catalog/resolve-playbook.d.ts +26 -0
- package/dist/catalog/resolve-playbook.js +114 -0
- package/dist/doctor/checks/project.js +27 -8
- package/dist/doctor/checks/registry.js +2 -2
- package/dist/doctor/checks/runtime.js +34 -1
- package/dist/errors.d.ts +102 -10
- package/dist/errors.js +103 -10
- package/dist/execution/service.d.ts +18 -16
- package/dist/execution/service.js +183 -252
- package/dist/execution/types.d.ts +25 -69
- package/dist/execution/types.js +2 -1
- package/dist/filter/intent.d.ts +8 -9
- package/dist/filter/intent.js +49 -10
- package/dist/graph/index.d.ts +4 -0
- package/dist/graph/index.js +4 -0
- package/dist/host/host.d.ts +74 -32
- package/dist/host/host.js +585 -336
- package/dist/host/routing.d.ts +15 -48
- package/dist/host/routing.js +90 -153
- package/dist/host/types.d.ts +50 -17
- package/dist/index.d.ts +21 -24
- package/dist/index.js +19 -23
- package/dist/input/advice.d.ts +5 -1
- package/dist/input/advice.js +163 -214
- package/dist/input/describe.d.ts +1 -1
- package/dist/input/flat-errors.d.ts +5 -6
- package/dist/input/flat-errors.js +6 -6
- package/dist/input/flat-materializer.js +42 -44
- package/dist/invocation/index.d.ts +2 -0
- package/dist/invocation/index.js +2 -0
- package/dist/invocation/policy.d.ts +157 -0
- package/dist/invocation/policy.js +231 -0
- package/dist/invocation/types.d.ts +172 -0
- package/dist/invocation/types.js +97 -0
- package/dist/ipc/diagnostic.d.ts +0 -12
- package/dist/ipc/diagnostic.js +0 -21
- package/dist/ipc/host.d.ts +4 -4
- package/dist/ipc/host.js +44 -47
- package/dist/ipc/index.d.ts +1 -1
- package/dist/ipc/index.js +1 -1
- package/dist/ipc/service.d.ts +40 -0
- package/dist/ipc/service.js +349 -0
- package/dist/ipc/types.d.ts +21 -15
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +1 -0
- package/dist/node/module-loader.d.ts +63 -0
- package/dist/node/module-loader.js +112 -0
- package/dist/package/index.d.ts +23 -0
- package/dist/package/index.js +18 -0
- package/dist/{app/app.d.ts → package/runtime.d.ts} +41 -20
- package/dist/{app/app.js → package/runtime.js} +140 -95
- package/dist/{app → package}/static-index.js +58 -41
- package/dist/package/storage-view.d.ts +13 -0
- package/dist/package/storage-view.js +22 -0
- package/dist/{app → package}/types.d.ts +76 -57
- package/dist/package/types.js +22 -0
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js +1 -1
- package/dist/platform/node.d.ts +48 -0
- package/dist/platform/node.js +96 -0
- package/dist/platform/types.d.ts +0 -9
- package/dist/process/control-arbiter.d.ts +2 -1
- package/dist/process/control-arbiter.js +1 -2
- package/dist/process/driver.d.ts +9 -8
- package/dist/process/driver.js +11 -22
- package/dist/process/index.d.ts +2 -0
- package/dist/process/index.js +2 -0
- package/dist/process/managed-record.d.ts +2 -2
- package/dist/process/process-driver.d.ts +109 -0
- package/dist/process/process-driver.js +650 -0
- package/dist/process/process-executor.d.ts +47 -0
- package/dist/process/process-executor.js +296 -0
- package/dist/process/process-manager.js +48 -26
- package/dist/process/run-executor.js +12 -12
- package/dist/profile/client-actions.js +2 -2
- package/dist/profile/client-health.js +2 -2
- package/dist/profile/client-query.js +5 -6
- package/dist/profile/client-transport.d.ts +19 -17
- package/dist/profile/client-transport.js +47 -80
- package/dist/profile/client.d.ts +2 -2
- package/dist/profile/client.js +1 -1
- package/dist/profile/index.d.ts +1 -0
- package/dist/profile/index.js +1 -0
- package/dist/profile/manager.d.ts +5 -0
- package/dist/profile/manager.js +41 -6
- package/dist/project/closure.js +2 -2
- package/dist/project/index.d.ts +8 -1
- package/dist/project/index.js +10 -1
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +1 -1
- package/dist/project/loader.js +26 -29
- package/dist/project/transactions.js +5 -10
- package/dist/registry/index.d.ts +3 -1
- package/dist/registry/index.js +2 -1
- package/dist/registry/lock.d.ts +28 -7
- package/dist/registry/lock.js +136 -16
- package/dist/registry/registry.d.ts +1 -5
- package/dist/registry/registry.js +1 -53
- package/dist/registry/scan.d.ts +0 -15
- package/dist/registry/scan.js +0 -59
- package/dist/registry/store.d.ts +42 -0
- package/dist/registry/store.js +43 -0
- package/dist/registry/types.d.ts +0 -24
- package/dist/runtime/action-collection.d.ts +1 -1
- package/dist/runtime/action-registry.d.ts +0 -4
- package/dist/runtime/action-registry.js +0 -7
- package/dist/runtime/context.js +5 -11
- package/dist/runtime/events.js +3 -6
- package/dist/runtime/identity.d.ts +22 -0
- package/dist/runtime/identity.js +10 -0
- package/dist/runtime/index.d.ts +1 -2
- package/dist/runtime/index.js +1 -2
- package/dist/runtime/module-loader.d.ts +2 -34
- package/dist/runtime/module-loader.js +1 -16
- package/dist/runtime/process.d.ts +6 -24
- package/dist/runtime/process.js +1 -118
- package/dist/runtime/run-persistence.js +2 -5
- package/dist/runtime/runner.d.ts +33 -93
- package/dist/runtime/runner.js +132 -364
- package/dist/runtime/standalone.d.ts +23 -8
- package/dist/runtime/standalone.js +100 -147
- package/dist/server/body.d.ts +16 -8
- package/dist/server/body.js +33 -16
- package/dist/server/dispatcher.d.ts +6 -10
- package/dist/server/dispatcher.js +24 -32
- package/dist/server/http-server.d.ts +87 -0
- package/dist/server/http-server.js +304 -0
- package/dist/server/index.d.ts +11 -0
- package/dist/server/index.js +10 -0
- package/dist/server/mcp-endpoint.d.ts +61 -0
- package/dist/server/mcp-endpoint.js +118 -0
- package/dist/server/routes/actions.js +34 -28
- package/dist/server/routes/common.d.ts +10 -14
- package/dist/server/routes/common.js +32 -56
- package/dist/server/routes/config.js +54 -64
- package/dist/server/routes/doctor.d.ts +1 -1
- package/dist/server/routes/doctor.js +10 -18
- package/dist/server/routes/health.d.ts +1 -1
- package/dist/server/routes/health.js +2 -3
- package/dist/server/routes/info.d.ts +2 -2
- package/dist/server/routes/info.js +12 -13
- package/dist/server/routes/playbooks.js +16 -29
- package/dist/server/routes/runs.js +95 -117
- package/dist/server/routes/state.js +63 -62
- package/dist/server/server.d.ts +2 -2
- package/dist/server/server.js +43 -227
- package/dist/server/types.d.ts +8 -19
- package/dist/service/factory.d.ts +12 -0
- package/dist/service/factory.js +85 -0
- package/dist/{target → service}/index.d.ts +1 -1
- package/dist/{target → service}/index.js +1 -1
- package/dist/service/local.d.ts +27 -0
- package/dist/service/local.js +138 -0
- package/dist/{target → service}/remote-errors.d.ts +3 -6
- package/dist/service/remote-errors.js +38 -0
- package/dist/{target → service}/remote-polling.d.ts +2 -2
- package/dist/{target → service}/remote-polling.js +8 -8
- package/dist/service/remote.d.ts +34 -0
- package/dist/service/remote.js +634 -0
- package/dist/service/sse-stream.d.ts +24 -0
- package/dist/service/sse-stream.js +139 -0
- package/dist/service/types.d.ts +272 -0
- package/dist/service/types.js +32 -0
- package/dist/storage/data-dir-lock.js +10 -5
- package/dist/storage/driver.d.ts +2 -1
- package/dist/storage/driver.js +3 -52
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +1 -0
- package/dist/storage/lock-core.d.ts +2 -6
- package/dist/storage/lock-core.js +4 -17
- package/dist/storage/params.d.ts +2 -8
- package/dist/storage/params.js +2 -8
- package/dist/storage/sqlite-driver.d.ts +40 -0
- package/dist/storage/sqlite-driver.js +116 -0
- package/dist/storage/sqlite.js +21 -6
- package/dist/storage/types.d.ts +4 -4
- package/dist/storage/types.js +2 -2
- package/dist/utils/index.d.ts +53 -0
- package/dist/utils/index.js +99 -4
- package/dist/utils/process.d.ts +10 -0
- package/dist/utils/process.js +21 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +41 -3
- package/dist/app/index.d.ts +0 -2
- package/dist/app/index.js +0 -2
- package/dist/app/types.js +0 -1
- package/dist/catalog/action-index.d.ts +0 -9
- package/dist/catalog/action-index.js +0 -95
- package/dist/catalog/action-resolver.d.ts +0 -20
- package/dist/catalog/action-resolver.js +0 -95
- package/dist/catalog/location-registry.d.ts +0 -15
- package/dist/catalog/location-registry.js +0 -138
- package/dist/catalog/package-catalog.d.ts +0 -7
- package/dist/catalog/package-catalog.js +0 -77
- package/dist/catalog/types.d.ts +0 -68
- package/dist/catalog/types.js +0 -1
- package/dist/export/index.d.ts +0 -1
- package/dist/export/index.js +0 -1
- package/dist/export/templates.d.ts +0 -71
- package/dist/export/templates.js +0 -635
- package/dist/ipc/target.d.ts +0 -74
- package/dist/ipc/target.js +0 -365
- package/dist/platform/default.d.ts +0 -38
- package/dist/platform/default.js +0 -78
- package/dist/project/resolver.d.ts +0 -116
- package/dist/project/resolver.js +0 -376
- package/dist/registry/resolve.d.ts +0 -62
- package/dist/registry/resolve.js +0 -195
- package/dist/runtime/execution-manager.d.ts +0 -48
- package/dist/runtime/execution-manager.js +0 -66
- package/dist/runtime/package-runner-factory.d.ts +0 -82
- package/dist/runtime/package-runner-factory.js +0 -186
- package/dist/target/local.d.ts +0 -53
- package/dist/target/local.js +0 -349
- package/dist/target/remote-errors.js +0 -51
- package/dist/target/remote.d.ts +0 -86
- package/dist/target/remote.js +0 -818
- package/dist/target/target.d.ts +0 -6
- package/dist/target/target.js +0 -37
- package/dist/target/types.d.ts +0 -261
- package/dist/target/types.js +0 -35
- /package/dist/{app → package}/static-index.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.d.ts +0 -0
- /package/dist/{target → service}/sse-parser.js +0 -0
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 远端客户端传输层。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 职责单一聚焦:鉴权头拼装、明文传输安全校验与 insecure dispatcher 注入,
|
|
5
|
+
* 作为全部远端端点函数的传输样板单一事实源。
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
* 若请求携带认证 Token 且目标为非本地回环的明文 http://,默认报错拒绝。
|
|
10
|
-
* 可通过 allowInsecureHttp 选项、insecure 选项、环境变量或命令行参数豁免。
|
|
8
|
+
* 传输层安全豁免控制选项。
|
|
11
9
|
*/
|
|
12
|
-
export
|
|
10
|
+
export interface SecureTransportOptions {
|
|
11
|
+
/** 是否允许向非回环地址发送明文 HTTP 请求 */
|
|
13
12
|
allowInsecureHttp?: boolean;
|
|
13
|
+
/** 是否跳过 TLS 证书合法性校验 */
|
|
14
14
|
insecure?: boolean;
|
|
15
|
-
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 校验在携带认证 Token 时传输层协议与目标地址是否安全。
|
|
18
|
+
* 若请求携带认证 Token 且目标为非本地回环的明文 http://,默认报错拒绝。
|
|
19
|
+
* 可通过 allowInsecureHttp 选项、insecure 选项、环境变量或命令行参数豁免明文限制。
|
|
20
|
+
* 目标 URL 无法解析时无论豁免与否均 fail-closed 抛 INVALID_ARGUMENT,
|
|
21
|
+
* 杜绝携凭据请求发往不可控的畸形地址。
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertSecureTransport(serverUrl: string, token?: string, options?: SecureTransportOptions): void;
|
|
16
24
|
/**
|
|
17
25
|
* 构造携带鉴权信息的标准请求头。
|
|
18
26
|
*/
|
|
@@ -43,19 +51,13 @@ export interface RemoteFetchInit {
|
|
|
43
51
|
* 构建绑定传输上下文(Token、安全选项与调度器)的远端请求函数。
|
|
44
52
|
*
|
|
45
53
|
* 单一事实源收敛所有 fetchRemoteXxx 端点的传输样板:
|
|
46
|
-
* 调用方仅需声明 path
|
|
47
|
-
* insecure dispatcher
|
|
54
|
+
* 调用方仅需声明 path 与查询参数,鉴权头拼装、明文传输校验与
|
|
55
|
+
* insecure dispatcher 注入均在此统一处理。
|
|
48
56
|
*/
|
|
49
57
|
export declare function createRemoteFetch(serverUrl: string, token?: string, options?: RemoteClientRequestOptions): (path: string, init?: RemoteFetchInit) => Promise<Response>;
|
|
50
58
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* v1 回退请求若网络失败则沿用 v2 响应,保留原始状态与错误体供上层透传。
|
|
59
|
+
* 远端请求单一入口:执行目标协议路由请求。
|
|
54
60
|
*/
|
|
55
|
-
export declare function
|
|
61
|
+
export declare function fetchRemoteRoute(base: string, path: string, init: RequestInit & {
|
|
56
62
|
dispatcher?: any;
|
|
57
63
|
}): Promise<Response>;
|
|
58
|
-
/**
|
|
59
|
-
* 列出指定路由的全部协议版本候选 URL(按优先级排序,含原始路由自身)。
|
|
60
|
-
*/
|
|
61
|
-
export declare function listProtocolRouteCandidates(base: string, path: string): string[];
|
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
import { isLoopbackHost } from "../utils/net.js";
|
|
2
2
|
import { normalizeServerUrl } from "./manager.js";
|
|
3
3
|
import { getInsecureDispatcher } from "../server/dispatcher.js";
|
|
4
|
+
import { ActionDockError, INSECURE_TRANSPORT, INVALID_ARGUMENT } from "../errors.js";
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* 职责单一聚焦:鉴权头拼装、明文传输安全校验、insecure dispatcher 注入
|
|
8
|
-
* 与 v2 -> v1 协议回退,作为全部远端端点函数的传输样板单一事实源。
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
* 校验在携带认证 Token 时传输层协议是否安全。
|
|
6
|
+
* 校验在携带认证 Token 时传输层协议与目标地址是否安全。
|
|
12
7
|
* 若请求携带认证 Token 且目标为非本地回环的明文 http://,默认报错拒绝。
|
|
13
|
-
* 可通过 allowInsecureHttp 选项、insecure
|
|
8
|
+
* 可通过 allowInsecureHttp 选项、insecure 选项、环境变量或命令行参数豁免明文限制。
|
|
9
|
+
* 目标 URL 无法解析时无论豁免与否均 fail-closed 抛 INVALID_ARGUMENT,
|
|
10
|
+
* 杜绝携凭据请求发往不可控的畸形地址。
|
|
14
11
|
*/
|
|
15
|
-
export function assertSecureTransport(serverUrl, token,
|
|
12
|
+
export function assertSecureTransport(serverUrl, token, options) {
|
|
16
13
|
if (!token || !token.trim()) {
|
|
17
14
|
return;
|
|
18
15
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (typeof allowInsecureHttpOrOptions === "object" && allowInsecureHttpOrOptions !== null) {
|
|
22
|
-
allowInsecureHttp = Boolean(allowInsecureHttpOrOptions.allowInsecureHttp);
|
|
23
|
-
insecure = Boolean(allowInsecureHttpOrOptions.insecure);
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
allowInsecureHttp = Boolean(allowInsecureHttpOrOptions);
|
|
27
|
-
insecure = Boolean(insecureArg);
|
|
28
|
-
}
|
|
16
|
+
const allowInsecureHttp = Boolean(options?.allowInsecureHttp);
|
|
17
|
+
const insecure = Boolean(options?.insecure);
|
|
29
18
|
const allow = allowInsecureHttp ||
|
|
30
19
|
insecure ||
|
|
31
20
|
(typeof process !== "undefined" &&
|
|
@@ -33,25 +22,44 @@ export function assertSecureTransport(serverUrl, token, allowInsecureHttpOrOptio
|
|
|
33
22
|
process.env?.ACTIONDOCK_ALLOW_INSECURE_HTTP === "1" ||
|
|
34
23
|
process.env?.ACTIONDOCK_INSECURE === "true" ||
|
|
35
24
|
process.env?.ACTIONDOCK_INSECURE === "1"));
|
|
36
|
-
if (allow) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
25
|
const base = normalizeServerUrl(serverUrl);
|
|
40
|
-
|
|
26
|
+
// 目标地址可解析性前置校验(fail-closed):携带 token 时若目标 URL 无法解析,
|
|
27
|
+
// 严禁继续放行——后续请求会携凭据发往不可控的畸形目标。错误信息仅携带
|
|
28
|
+
// 脱敏地址与解析原因。allow 豁免仅作用于明文传输策略,不豁免地址合法性。
|
|
29
|
+
if (base.startsWith("http://") || base.startsWith("https://")) {
|
|
30
|
+
let parsed;
|
|
41
31
|
try {
|
|
42
|
-
|
|
43
|
-
if (!isLoopbackHost(parsed.hostname)) {
|
|
44
|
-
const err = new Error(`Insecure HTTP connection with authentication token to non-loopback host '${parsed.hostname}' is prohibited. Use HTTPS or pass --allow-insecure-http to override.`);
|
|
45
|
-
err.code = "INSECURE_TRANSPORT";
|
|
46
|
-
throw err;
|
|
47
|
-
}
|
|
32
|
+
parsed = new URL(base);
|
|
48
33
|
}
|
|
49
|
-
catch (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
34
|
+
catch (err) {
|
|
35
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
36
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Invalid server URL '${redactUrl(base)}' with authentication token: URL parse failed (${reason}). Fix the profile server URL before sending credentials.`);
|
|
37
|
+
}
|
|
38
|
+
// 明文传输策略:仅非回环明文 http 需要豁免,https 不受限制
|
|
39
|
+
if (!allow && base.startsWith("http://") && !isLoopbackHost(parsed.hostname)) {
|
|
40
|
+
throw new ActionDockError(INSECURE_TRANSPORT, `Insecure HTTP connection with authentication token to non-loopback host '${parsed.hostname}' is prohibited. Use HTTPS or pass --allow-insecure-http to override.`);
|
|
53
41
|
}
|
|
54
42
|
}
|
|
43
|
+
// 非 http/https 协议形态(如 local):交给后续请求层处理,此处无从校验
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 脱敏 URL:移除 userinfo 凭据与查询串后再返回;仅保留协议、主机与路径。
|
|
47
|
+
*/
|
|
48
|
+
function redactUrl(url) {
|
|
49
|
+
let candidate = url;
|
|
50
|
+
try {
|
|
51
|
+
const u = new URL(candidate);
|
|
52
|
+
u.username = "";
|
|
53
|
+
u.password = "";
|
|
54
|
+
u.search = "";
|
|
55
|
+
return u.toString();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// 解析失败的畸形地址:以正则剥离 userinfo 形态后截断长度,
|
|
59
|
+
// 避免原始串携带敏感片段外泄到错误信息与日志
|
|
60
|
+
candidate = candidate.replace(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^\/@]+@/, "$1");
|
|
61
|
+
return candidate.length > 128 ? `${candidate.slice(0, 128)}...` : candidate;
|
|
62
|
+
}
|
|
55
63
|
}
|
|
56
64
|
/**
|
|
57
65
|
* 构造携带鉴权信息的标准请求头。
|
|
@@ -69,8 +77,8 @@ export function buildHeaders(token) {
|
|
|
69
77
|
* 构建绑定传输上下文(Token、安全选项与调度器)的远端请求函数。
|
|
70
78
|
*
|
|
71
79
|
* 单一事实源收敛所有 fetchRemoteXxx 端点的传输样板:
|
|
72
|
-
* 调用方仅需声明 path
|
|
73
|
-
* insecure dispatcher
|
|
80
|
+
* 调用方仅需声明 path 与查询参数,鉴权头拼装、明文传输校验与
|
|
81
|
+
* insecure dispatcher 注入均在此统一处理。
|
|
74
82
|
*/
|
|
75
83
|
export function createRemoteFetch(serverUrl, token, options) {
|
|
76
84
|
assertSecureTransport(serverUrl, token, {
|
|
@@ -103,54 +111,13 @@ export function createRemoteFetch(serverUrl, token, options) {
|
|
|
103
111
|
fetchInit.dispatcher = getInsecureDispatcher();
|
|
104
112
|
fetchInit.tls = { rejectUnauthorized: false };
|
|
105
113
|
}
|
|
106
|
-
return
|
|
114
|
+
return fetchRemoteRoute(base, path, fetchInit);
|
|
107
115
|
};
|
|
108
116
|
}
|
|
109
|
-
/** v2 优先、v1 兼容回退的协议版本优先级列表。 */
|
|
110
|
-
const PROTOCOL_PREFERENCE = ["v2", "v1"];
|
|
111
117
|
/**
|
|
112
|
-
*
|
|
113
|
-
* 仅 404(路由不存在)回退;其余状态(如 401、403、500)原样透传。
|
|
118
|
+
* 远端请求单一入口:执行目标协议路由请求。
|
|
114
119
|
*/
|
|
115
|
-
function
|
|
116
|
-
return res.status === 404;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* 携带协议回退的远端请求单一入口:先打 /api/v2/ 路由,404 时改打 /api/v1/ 路由。
|
|
120
|
-
*
|
|
121
|
-
* v1 回退请求若网络失败则沿用 v2 响应,保留原始状态与错误体供上层透传。
|
|
122
|
-
*/
|
|
123
|
-
export async function fetchWithProtocolFallback(base, path, init) {
|
|
120
|
+
export async function fetchRemoteRoute(base, path, init) {
|
|
124
121
|
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
125
|
-
|
|
126
|
-
let res = await fetch(`${base}${normalizedPath}`, init);
|
|
127
|
-
if (!isVersioned || !shouldFallbackToNextProtocol(res)) {
|
|
128
|
-
return res;
|
|
129
|
-
}
|
|
130
|
-
const candidates = listProtocolRouteCandidates(base, normalizedPath);
|
|
131
|
-
// 首个候选即当前已返回 404 的路由,从次优先级继续尝试
|
|
132
|
-
for (let i = 1; i < candidates.length; i++) {
|
|
133
|
-
try {
|
|
134
|
-
const fallbackRes = await fetch(candidates[i], init);
|
|
135
|
-
if (!shouldFallbackToNextProtocol(fallbackRes)) {
|
|
136
|
-
return fallbackRes;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
// 回退请求网络异常时保持既有 v2 响应,由上层统一处理
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return res;
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* 列出指定路由的全部协议版本候选 URL(按优先级排序,含原始路由自身)。
|
|
147
|
-
*/
|
|
148
|
-
export function listProtocolRouteCandidates(base, path) {
|
|
149
|
-
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
150
|
-
const versionMatch = normalizedPath.match(/^\/api\/(v\d+)\/(.*)$/);
|
|
151
|
-
if (!versionMatch) {
|
|
152
|
-
return [`${base}${normalizedPath}`];
|
|
153
|
-
}
|
|
154
|
-
const rest = versionMatch[2];
|
|
155
|
-
return PROTOCOL_PREFERENCE.map((version) => `${base}/api/${version}/${rest}`);
|
|
122
|
+
return fetch(`${base}${normalizedPath}`, init);
|
|
156
123
|
}
|
package/dist/profile/client.d.ts
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* - client-state:状态存储管理
|
|
16
16
|
* - client-config:配置管理
|
|
17
17
|
*/
|
|
18
|
-
export { assertSecureTransport, createRemoteFetch,
|
|
19
|
-
export type { RemoteClientRequestOptions, RemoteFetchInit, } from "./client-transport.js";
|
|
18
|
+
export { assertSecureTransport, createRemoteFetch, fetchRemoteRoute, } from "./client-transport.js";
|
|
19
|
+
export type { RemoteClientRequestOptions, RemoteFetchInit, SecureTransportOptions, } from "./client-transport.js";
|
|
20
20
|
export { checkRemoteHealth } from "./client-health.js";
|
|
21
21
|
export { executeRemoteAction, fetchRemoteActionShow, fetchRemoteActions, fetchRemoteDoctor, fetchRemoteInfo, } from "./client-actions.js";
|
|
22
22
|
export type { RemoteExecuteOptions, RemoteExecutionResult } from "./client-actions.js";
|
package/dist/profile/client.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - client-config:配置管理
|
|
17
17
|
*/
|
|
18
18
|
// 传输层与通用类型
|
|
19
|
-
export { assertSecureTransport, createRemoteFetch,
|
|
19
|
+
export { assertSecureTransport, createRemoteFetch, fetchRemoteRoute, } from "./client-transport.js";
|
|
20
20
|
// 健康检查
|
|
21
21
|
export { checkRemoteHealth } from "./client-health.js";
|
|
22
22
|
// 执行与 Action 查询
|
package/dist/profile/index.d.ts
CHANGED
package/dist/profile/index.js
CHANGED
|
@@ -13,6 +13,11 @@ export declare function normalizeServerUrl(url: string): string;
|
|
|
13
13
|
export declare function getProfilesFilePath(customHome?: string): string;
|
|
14
14
|
/**
|
|
15
15
|
* 加载并读取 profiles.json 配置文件。
|
|
16
|
+
*
|
|
17
|
+
* 损坏语义与注册表(registry.ts)对齐:JSON 损坏或结构非法时将原文件原子留档为
|
|
18
|
+
* `profiles.json.corrupt` 并抛出带恢复指引的异常,绝不静默返回默认配置——否则
|
|
19
|
+
* 后续任意一次 saveProfiles 会用默认配置覆写原文件,导致用户全部远端环境配置
|
|
20
|
+
* 永久丢失。文件不存在时仍走默认初始化。
|
|
16
21
|
*/
|
|
17
22
|
export declare function loadProfiles(customHome?: string): ProfilesConfig;
|
|
18
23
|
/**
|
package/dist/profile/manager.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
3
|
import { toSnakeUpperCase } from "../runtime/env.js";
|
|
4
4
|
import { isLoopbackHost } from "../utils/net.js";
|
|
5
5
|
import { getActionDockHome } from "../utils/index.js";
|
|
6
|
+
import { ActionDockError, INVALID_ARGUMENT } from "../errors.js";
|
|
6
7
|
const PROFILE_NAME_REGEX = /^[a-zA-Z0-9_\-\.]+$/;
|
|
7
8
|
/**
|
|
8
9
|
* 默认初始 Profiles 配置(预设 local 本地环境)。
|
|
@@ -56,22 +57,56 @@ export function getProfilesFilePath(customHome) {
|
|
|
56
57
|
}
|
|
57
58
|
/**
|
|
58
59
|
* 加载并读取 profiles.json 配置文件。
|
|
60
|
+
*
|
|
61
|
+
* 损坏语义与注册表(registry.ts)对齐:JSON 损坏或结构非法时将原文件原子留档为
|
|
62
|
+
* `profiles.json.corrupt` 并抛出带恢复指引的异常,绝不静默返回默认配置——否则
|
|
63
|
+
* 后续任意一次 saveProfiles 会用默认配置覆写原文件,导致用户全部远端环境配置
|
|
64
|
+
* 永久丢失。文件不存在时仍走默认初始化。
|
|
59
65
|
*/
|
|
60
66
|
export function loadProfiles(customHome) {
|
|
61
67
|
const filePath = getProfilesFilePath(customHome);
|
|
62
68
|
if (!existsSync(filePath)) {
|
|
63
69
|
return structuredClone(DEFAULT_PROFILES_CONFIG);
|
|
64
70
|
}
|
|
71
|
+
let raw;
|
|
65
72
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
raw = readFileSync(filePath, "utf-8");
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
if (err.code === "ENOENT") {
|
|
69
77
|
return structuredClone(DEFAULT_PROFILES_CONFIG);
|
|
70
78
|
}
|
|
71
|
-
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(raw);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
archiveCorruptProfiles(filePath);
|
|
87
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Profiles file '${filePath}' is corrupted (invalid JSON: ${err.message}). ` +
|
|
88
|
+
`The original data has been preserved as '${filePath}.corrupt'. ` +
|
|
89
|
+
`Restore it manually or remove it to start with the default profiles.`);
|
|
90
|
+
}
|
|
91
|
+
if (!parsed || typeof parsed !== "object" || !parsed.profiles) {
|
|
92
|
+
archiveCorruptProfiles(filePath);
|
|
93
|
+
throw new ActionDockError(INVALID_ARGUMENT, `Profiles file '${filePath}' is corrupted (missing or invalid 'profiles' field). ` +
|
|
94
|
+
`The original data has been preserved as '${filePath}.corrupt'. ` +
|
|
95
|
+
`Restore it manually or remove it to start with the default profiles.`);
|
|
96
|
+
}
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 将损坏的 profiles 文件原子留档为 `${filePath}.corrupt`,
|
|
101
|
+
* 后续 saveProfiles 的直接写入不会覆盖留档(文件名不同)。
|
|
102
|
+
*/
|
|
103
|
+
function archiveCorruptProfiles(filePath) {
|
|
104
|
+
try {
|
|
105
|
+
renameSync(filePath, `${filePath}.corrupt`);
|
|
72
106
|
}
|
|
73
107
|
catch {
|
|
74
|
-
|
|
108
|
+
// 留档失败(如文件被其他进程先行移动)时仅放弃留档:错误仍会向上抛出,
|
|
109
|
+
// 不会出现静默清空
|
|
75
110
|
}
|
|
76
111
|
}
|
|
77
112
|
/**
|
package/dist/project/closure.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { parseActionRef } from "../catalog/resolve-action.js";
|
|
3
3
|
import { resolvePackageRoot } from "../registry/registry.js";
|
|
4
4
|
import { loadManifest } from "./manifest.js";
|
|
5
5
|
/**
|
|
@@ -52,7 +52,7 @@ export async function ensureDependencyClosure(roots, opts = {}) {
|
|
|
52
52
|
continue;
|
|
53
53
|
let parsed;
|
|
54
54
|
try {
|
|
55
|
-
parsed =
|
|
55
|
+
parsed = parseActionRef(rawRef);
|
|
56
56
|
}
|
|
57
57
|
catch {
|
|
58
58
|
warnings.push(`uses 声明 '${rawRef}' 未在注册表中解析到包`);
|
package/dist/project/index.d.ts
CHANGED
|
@@ -4,7 +4,14 @@ export * from "./init.js";
|
|
|
4
4
|
export * from "./loader.js";
|
|
5
5
|
export * from "./lockfile.js";
|
|
6
6
|
export * from "./manifest.js";
|
|
7
|
-
export * from "./resolver.js";
|
|
8
7
|
export { SNAPSHOT_TRACKED_FILES, type TransactionFileRecord, type TransactionMetadata, type ProjectTransaction, isPidAlive, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
9
8
|
export * from "./types.js";
|
|
10
9
|
export * from "./types-generator.js";
|
|
10
|
+
export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, type TraverseDirectoryEntry, type TraverseDirectoryOptions, } from "../utils/index.js";
|
|
11
|
+
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
|
|
12
|
+
export { runDoctorChecks, } from "../doctor/doctor.js";
|
|
13
|
+
export { resolveActionInput, buildActionInputAdvice, formatActionDetail, buildActionDescribePayload, mapInputValidationFailure, stripBom, readStdinBounded, type ResolveActionInputOptions, type ReadStdinBoundedOptions, } from "../input/index.js";
|
|
14
|
+
export { validateActionInputValue } from "../json/value-validator.js";
|
|
15
|
+
export { parseJson } from "../input/input-resolver.js";
|
|
16
|
+
export { InputError, FlatInputError, } from "../input/flat-errors.js";
|
|
17
|
+
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, type Extractor, } from "../filter/intent.js";
|
package/dist/project/index.js
CHANGED
|
@@ -4,7 +4,16 @@ export * from "./init.js";
|
|
|
4
4
|
export * from "./loader.js";
|
|
5
5
|
export * from "./lockfile.js";
|
|
6
6
|
export * from "./manifest.js";
|
|
7
|
-
export * from "./resolver.js";
|
|
8
7
|
export { SNAPSHOT_TRACKED_FILES, isPidAlive, isProjectLockHeld, acquireProjectLock, runFrozenInstall, hasPendingTransactions, beginTransaction, recoverPendingTransactions, } from "./transactions.js";
|
|
9
8
|
export * from "./types.js";
|
|
10
9
|
export * from "./types-generator.js";
|
|
10
|
+
export { assertPathWithinRoot, isPathOutsideBoundary, getPackageSlug, parseDuration, traverseDirectory, } from "../utils/index.js";
|
|
11
|
+
export { isSecretConfigKey, maskSecretValue, sanitizeConfigDefinitions, } from "../storage/mask.js";
|
|
12
|
+
export { runDoctorChecks, } from "../doctor/doctor.js";
|
|
13
|
+
// 动作入参解析、校验与格式化(含 BOM 剥离与标准输入有界读取的单一事实源转引)
|
|
14
|
+
export { resolveActionInput, buildActionInputAdvice, formatActionDetail, buildActionDescribePayload, mapInputValidationFailure, stripBom, readStdinBounded, } from "../input/index.js";
|
|
15
|
+
export { validateActionInputValue } from "../json/value-validator.js";
|
|
16
|
+
export { parseJson } from "../input/input-resolver.js";
|
|
17
|
+
export { InputError, FlatInputError, } from "../input/flat-errors.js";
|
|
18
|
+
// 意图过滤与查询
|
|
19
|
+
export { filterWithFallbackInfo, compileIntentRegex, matchIntent, } from "../filter/intent.js";
|
package/dist/project/init.js
CHANGED
|
@@ -86,10 +86,10 @@ export function initProject(targetDir, options = {}) {
|
|
|
86
86
|
node: ">=24.12.0",
|
|
87
87
|
},
|
|
88
88
|
dependencies: {
|
|
89
|
-
"@actiondock/sdk": "^2.
|
|
89
|
+
"@actiondock/sdk": "^2.8.0",
|
|
90
90
|
},
|
|
91
91
|
devDependencies: {
|
|
92
|
-
"@actiondock/testing": "^2.
|
|
92
|
+
"@actiondock/testing": "^2.8.0",
|
|
93
93
|
"@types/node": "^22.13.0",
|
|
94
94
|
"tsx": "^4.19.0",
|
|
95
95
|
"typescript": "^5.7.0",
|
package/dist/project/loader.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActionDefinition } from "@actiondock/sdk";
|
|
2
|
-
import { type ModuleLoader } from "../
|
|
2
|
+
import { type ModuleLoader } from "../node/module-loader.js";
|
|
3
3
|
import type { ActionDockManifest, PlaybookDefinition, PlaybookManifestEntry, ProjectConfig } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* 从指定目录开始向上逐级递归查找包含 `actiondock.json` 的项目根目录。
|
package/dist/project/loader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { NodeModuleLoader, unwrapDefaultExport } from "../node/module-loader.js";
|
|
5
5
|
import { ACTION_LOAD_FAILED, isMissingModuleError } from "../errors.js";
|
|
6
6
|
import { loadManifest, ACTION_ID_REGEX, PLAYBOOK_ID_REGEX } from "./manifest.js";
|
|
7
7
|
/**
|
|
@@ -36,7 +36,7 @@ export function findProjectRoot(cwd) {
|
|
|
36
36
|
}
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
|
-
import { PACKAGE_ID_REGEX, assertPathWithinRoot, assertWithinProjectRoot, } from "../utils/index.js";
|
|
39
|
+
import { PACKAGE_ID_REGEX, assertPathWithinRoot, assertWithinProjectRoot, traverseDirectory, } from "../utils/index.js";
|
|
40
40
|
/**
|
|
41
41
|
* 加载并校验指定目录下的 `actiondock.json` 配置文件。
|
|
42
42
|
*
|
|
@@ -126,32 +126,29 @@ export function getInstallCommand(projectRoot) {
|
|
|
126
126
|
return ["npm", "install"];
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
|
-
*
|
|
129
|
+
* 测试与类型声明文件后缀常量(与 builder 的 isIgnoredPath 保持同一语义口径)。
|
|
130
|
+
*/
|
|
131
|
+
const TEST_FILE_SUFFIXES = [
|
|
132
|
+
".test.ts",
|
|
133
|
+
".test.js",
|
|
134
|
+
".test.tsx",
|
|
135
|
+
".test.jsx",
|
|
136
|
+
".spec.ts",
|
|
137
|
+
".spec.js",
|
|
138
|
+
".spec.tsx",
|
|
139
|
+
".spec.jsx",
|
|
140
|
+
".d.ts",
|
|
141
|
+
];
|
|
142
|
+
/**
|
|
143
|
+
* 递归扫描指定目录下的特定后缀文件(自动排除测试文件与类型声明文件)。
|
|
144
|
+
* 目录遍历统一复用 core utils 的 traverseDirectory 单一事实源,
|
|
145
|
+
* 自带软链接越界与循环防护。
|
|
130
146
|
*/
|
|
131
147
|
function scanFiles(dir, extension) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const entries = readdirSync(current);
|
|
137
|
-
for (const entry of entries) {
|
|
138
|
-
const fullPath = join(current, entry);
|
|
139
|
-
const stat = statSync(fullPath);
|
|
140
|
-
if (stat.isDirectory()) {
|
|
141
|
-
walk(fullPath);
|
|
142
|
-
}
|
|
143
|
-
else if (stat.isFile() && fullPath.endsWith(extension)) {
|
|
144
|
-
// 排除测试与类型声明文件
|
|
145
|
-
if (!fullPath.endsWith(".test.ts") &&
|
|
146
|
-
!fullPath.endsWith(".spec.ts") &&
|
|
147
|
-
!fullPath.endsWith(".d.ts")) {
|
|
148
|
-
results.push(fullPath);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
walk(dir);
|
|
154
|
-
return results;
|
|
148
|
+
return traverseDirectory(dir)
|
|
149
|
+
.filter((entry) => entry.relPath.endsWith(extension) &&
|
|
150
|
+
!TEST_FILE_SUFFIXES.some((suffix) => entry.relPath.endsWith(suffix)))
|
|
151
|
+
.map((entry) => entry.fullPath);
|
|
155
152
|
}
|
|
156
153
|
/**
|
|
157
154
|
* 发现并检索项目 actions 目录下的所有 Action 源码文件(.ts)。
|
|
@@ -175,7 +172,7 @@ export function discoverActionFiles(projectRoot, actionsDir = "actions") {
|
|
|
175
172
|
*/
|
|
176
173
|
export async function loadActions(projectRoot, _actionsDir = "actions", options = {}) {
|
|
177
174
|
const actions = new Map();
|
|
178
|
-
const loader = options.loader || new
|
|
175
|
+
const loader = options.loader || new NodeModuleLoader();
|
|
179
176
|
let manifest = null;
|
|
180
177
|
try {
|
|
181
178
|
manifest = loadManifest(projectRoot);
|
|
@@ -222,7 +219,7 @@ export async function loadActions(projectRoot, _actionsDir = "actions", options
|
|
|
222
219
|
};
|
|
223
220
|
throw error;
|
|
224
221
|
}
|
|
225
|
-
const exported = imported
|
|
222
|
+
const exported = unwrapDefaultExport(imported);
|
|
226
223
|
let runFn;
|
|
227
224
|
if (typeof exported === "function") {
|
|
228
225
|
runFn = exported;
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { acquireDirectoryLock, checkReclaimGuard, cleanStaleQuarantines, isProcessAlive, parseQuarantineTimestamp as parseQuarantineTimestampCore, readLockWithGracePeriod, safeReleaseLock as coreSafeReleaseLock, safeRemoveStaleReclaimGuard, safeRollbackLock, } from "../storage/lock-core.js";
|
|
6
|
+
import { ActionDockError, PROJECT_BUSY, PROJECT_RECOVERY_REQUIRED } from "../errors.js";
|
|
6
7
|
import { LOCKFILE_NAME } from "./lockfile.js";
|
|
7
8
|
import { MANIFEST_FILE_NAME } from "./manifest.js";
|
|
8
9
|
export const SNAPSHOT_TRACKED_FILES = [
|
|
@@ -138,9 +139,7 @@ export function acquireProjectLock(projectRoot, options = {}) {
|
|
|
138
139
|
lockToken,
|
|
139
140
|
acquireTimeoutMs: options.acquireTimeoutMs,
|
|
140
141
|
createLockError(message) {
|
|
141
|
-
|
|
142
|
-
busyErr.code = "PROJECT_BUSY";
|
|
143
|
-
return busyErr;
|
|
142
|
+
return new ActionDockError(PROJECT_BUSY, message);
|
|
144
143
|
},
|
|
145
144
|
messages: {
|
|
146
145
|
timeoutWaitingReclaimGuard: (holderPid) => `PROJECT_BUSY: Timeout waiting for active reclaim guard (PID ${holderPid ?? "unknown"}) on project '${projectRoot}' after ${timeoutMs}ms`,
|
|
@@ -154,9 +153,7 @@ export function acquireProjectLock(projectRoot, options = {}) {
|
|
|
154
153
|
assertStaleHolderReclaimable(info) {
|
|
155
154
|
const holderPid = info.pid;
|
|
156
155
|
if (isProcessAlive(holderPid)) {
|
|
157
|
-
|
|
158
|
-
busyErr.code = "PROJECT_BUSY";
|
|
159
|
-
throw busyErr;
|
|
156
|
+
throw new ActionDockError(PROJECT_BUSY, `PROJECT_BUSY: Project modification lock is held by PID ${holderPid}. Another command is running in ${projectRoot}.`);
|
|
160
157
|
}
|
|
161
158
|
},
|
|
162
159
|
onAcquired: () => true,
|
|
@@ -223,9 +220,7 @@ export function runFrozenInstall(projectRoot) {
|
|
|
223
220
|
});
|
|
224
221
|
if (proc.status !== 0) {
|
|
225
222
|
const errorMsg = proc.stderr?.toString() || proc.stdout?.toString() || "Unknown error";
|
|
226
|
-
|
|
227
|
-
err.code = "PROJECT_RECOVERY_REQUIRED";
|
|
228
|
-
throw err;
|
|
223
|
+
throw new ActionDockError(PROJECT_RECOVERY_REQUIRED, `PROJECT_RECOVERY_REQUIRED: Frozen install failed during recovery in ${projectRoot}: ${errorMsg}`);
|
|
229
224
|
}
|
|
230
225
|
}
|
|
231
226
|
/**
|
|
@@ -366,7 +361,7 @@ export async function recoverPendingTransactions(projectRoot, options) {
|
|
|
366
361
|
releaseLock = acquireProjectLock(projectRoot);
|
|
367
362
|
}
|
|
368
363
|
catch (err) {
|
|
369
|
-
if (err?.code === "PROJECT_BUSY"
|
|
364
|
+
if (err?.code === "PROJECT_BUSY") {
|
|
370
365
|
// 仅当项目锁被其他活跃进程占用时,安全退出并返回空数组
|
|
371
366
|
return [];
|
|
372
367
|
}
|
package/dist/registry/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from "./lock.js";
|
|
2
|
+
export type { RegistryLockOptions } from "./lock.js";
|
|
2
3
|
export * from "./registry.js";
|
|
3
|
-
export * from "./resolve.js";
|
|
4
4
|
export * from "./scan.js";
|
|
5
|
+
export * from "./store.js";
|
|
5
6
|
export * from "./types.js";
|
|
7
|
+
export { getActionDockHome, getPackageSlug, } from "../utils/index.js";
|
package/dist/registry/index.js
CHANGED
package/dist/registry/lock.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 锁目录最近一次心跳(创建或续期)后超过该时长且持有进程已死亡,
|
|
3
|
+
* 才视为持有进程异常退出的残留锁。
|
|
3
4
|
*/
|
|
4
5
|
export declare const REGISTRY_LOCK_STALE_MS = 10000;
|
|
5
6
|
/**
|
|
@@ -11,17 +12,37 @@ export declare const REGISTRY_LOCK_ACQUIRE_TIMEOUT_MS = 15000;
|
|
|
11
12
|
* 两次获取尝试之间的退避间隔,休眠让出事件循环,避免忙等阻塞。
|
|
12
13
|
*/
|
|
13
14
|
export declare const REGISTRY_LOCK_RETRY_DELAY_MS = 25;
|
|
15
|
+
/**
|
|
16
|
+
* 持锁期间的心跳续期间隔。持有者定期 touch 锁目录刷新 mtime,
|
|
17
|
+
* 使长耗时持锁操作(如 linkPackage 的深度扫描)不会仅因耗时超过
|
|
18
|
+
* stale 阈值而被等待方误判为残留锁抢占。
|
|
19
|
+
*/
|
|
20
|
+
export declare const REGISTRY_LOCK_HEARTBEAT_MS = 3000;
|
|
21
|
+
/**
|
|
22
|
+
* 锁配置选项。
|
|
23
|
+
*/
|
|
24
|
+
export interface RegistryLockOptions {
|
|
25
|
+
staleMs?: number;
|
|
26
|
+
acquireTimeoutMs?: number;
|
|
27
|
+
retryDelayMs?: number;
|
|
28
|
+
heartbeatMs?: number;
|
|
29
|
+
}
|
|
14
30
|
/**
|
|
15
31
|
* 异步注册表锁(防止多进程并发读写导致 registry.json 损坏)。
|
|
16
32
|
*
|
|
17
|
-
* 获取方式:fs/promises mkdir
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
33
|
+
* 获取方式:fs/promises mkdir 的原子性保证同一时刻仅一个进程能创建锁目录,
|
|
34
|
+
* 创建成功后立即写入含 pid 与随机 token 的 metadata.json。
|
|
35
|
+
* 冲突处理:EEXIST 时按保守语义判定残留(见 isStaleLock):mtime 超龄且
|
|
36
|
+
* pid 已死才抢占——抢占仍通过原子 rename 到隔离名实现,rename 仅有一个
|
|
37
|
+
* 进程能成功,失败方回到等待重试,消除「检查后删除再重建」的竞态窗口;
|
|
38
|
+
* 未过期的锁通过 sleep 让出事件循环后重试。
|
|
39
|
+
* 心跳续期:持锁期间以固定间隔 touch 锁目录刷新 mtime,使 linkPackage 等
|
|
40
|
+
* 长耗时持锁操作不会仅因耗时被误判残留;fn 完成后停止心跳并删除锁目录。
|
|
21
41
|
*
|
|
22
42
|
* @param filePath 注册表文件路径,锁目录为其同级 `${filePath}.lock`
|
|
23
43
|
* @param fn 持锁期间执行的操作(同步或异步)
|
|
44
|
+
* @param options 可选锁超时与重试配置
|
|
24
45
|
* @returns fn 的返回值
|
|
25
|
-
* @throws 超过
|
|
46
|
+
* @throws 超过 acquireTimeoutMs 仍未获取时抛出描述性错误
|
|
26
47
|
*/
|
|
27
|
-
export declare function withRegistryLock<T>(filePath: string, fn: () => T | Promise<T
|
|
48
|
+
export declare function withRegistryLock<T>(filePath: string, fn: () => T | Promise<T>, options?: RegistryLockOptions): Promise<T>;
|