@actiondock/core 2.5.0 → 2.6.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/dist/app/app.d.ts +14 -1
- package/dist/app/app.js +88 -215
- package/dist/app/static-index.d.ts +39 -0
- package/dist/app/static-index.js +164 -0
- package/dist/app/types.d.ts +5 -0
- package/dist/catalog/action-index.js +6 -4
- package/dist/catalog/package-catalog.js +2 -1
- package/dist/doctor/checks/index.d.ts +5 -0
- package/dist/doctor/checks/index.js +5 -0
- package/dist/doctor/checks/project-locate.d.ts +6 -0
- package/dist/doctor/checks/project-locate.js +36 -0
- package/dist/doctor/checks/project.d.ts +6 -0
- package/dist/doctor/checks/project.js +383 -0
- package/dist/doctor/checks/registry.d.ts +13 -0
- package/dist/doctor/checks/registry.js +207 -0
- package/dist/doctor/checks/runtime.d.ts +13 -0
- package/dist/doctor/checks/runtime.js +90 -0
- package/dist/doctor/checks/storage.d.ts +5 -0
- package/dist/doctor/checks/storage.js +34 -0
- package/dist/doctor/context.d.ts +35 -0
- package/dist/doctor/context.js +11 -0
- package/dist/doctor/doctor.d.ts +5 -0
- package/dist/doctor/doctor.js +33 -602
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +2 -0
- package/dist/execution/service.js +14 -9
- package/dist/host/host.d.ts +33 -0
- package/dist/host/host.js +222 -362
- package/dist/host/routing.d.ts +92 -0
- package/dist/host/routing.js +238 -0
- package/dist/host/types.d.ts +8 -0
- package/dist/ipc/host.js +38 -0
- package/dist/platform/types.d.ts +7 -0
- package/dist/process/control-arbiter.d.ts +107 -0
- package/dist/process/control-arbiter.js +251 -0
- package/dist/process/diagnostics-sink.d.ts +26 -0
- package/dist/process/diagnostics-sink.js +35 -0
- package/dist/process/index.d.ts +7 -0
- package/dist/process/index.js +7 -0
- package/dist/process/input-dispatcher.d.ts +88 -0
- package/dist/process/input-dispatcher.js +240 -0
- package/dist/process/managed-record.d.ts +78 -0
- package/dist/process/managed-record.js +1 -0
- package/dist/process/process-manager.d.ts +15 -56
- package/dist/process/process-manager.js +119 -707
- package/dist/process/reservation-table.d.ts +39 -0
- package/dist/process/reservation-table.js +80 -0
- package/dist/process/run-executor.d.ts +16 -0
- package/dist/process/run-executor.js +155 -0
- package/dist/process/terminal-output-cache.d.ts +66 -0
- package/dist/process/terminal-output-cache.js +132 -0
- package/dist/profile/client-actions.d.ts +56 -0
- package/dist/profile/client-actions.js +215 -0
- package/dist/profile/client-config.d.ts +20 -0
- package/dist/profile/client-config.js +51 -0
- package/dist/profile/client-health.d.ts +14 -0
- package/dist/profile/client-health.js +68 -0
- package/dist/profile/client-playbooks.d.ts +19 -0
- package/dist/profile/client-playbooks.js +36 -0
- package/dist/profile/client-query.d.ts +30 -0
- package/dist/profile/client-query.js +70 -0
- package/dist/profile/client-runs.d.ts +42 -0
- package/dist/profile/client-runs.js +58 -0
- package/dist/profile/client-state.d.ts +56 -0
- package/dist/profile/client-state.js +83 -0
- package/dist/profile/client-transport.d.ts +61 -0
- package/dist/profile/client-transport.js +155 -0
- package/dist/profile/client.d.ts +23 -142
- package/dist/profile/client.js +28 -588
- package/dist/profile/manager.js +1 -1
- package/dist/project/closure.d.ts +4 -4
- package/dist/project/closure.js +11 -5
- package/dist/project/init.js +2 -2
- package/dist/project/loader.d.ts +0 -25
- package/dist/project/loader.js +1 -154
- package/dist/project/transactions.d.ts +12 -14
- package/dist/project/transactions.js +60 -836
- package/dist/registry/registry.js +6 -4
- package/dist/registry/resolve.js +1 -1
- package/dist/runtime/action-registry.d.ts +45 -0
- package/dist/runtime/action-registry.js +108 -0
- package/dist/runtime/clock.d.ts +5 -17
- package/dist/runtime/clock.js +5 -15
- package/dist/runtime/context.d.ts +3 -0
- package/dist/runtime/context.js +17 -56
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +3 -0
- package/dist/runtime/package-runner-factory.d.ts +82 -0
- package/dist/runtime/package-runner-factory.js +186 -0
- package/dist/runtime/run-persistence.d.ts +85 -0
- package/dist/runtime/run-persistence.js +111 -0
- package/dist/runtime/runner.d.ts +23 -33
- package/dist/runtime/runner.js +92 -286
- package/dist/runtime/standalone.d.ts +11 -1
- package/dist/runtime/standalone.js +137 -25
- package/dist/server/dispatcher.d.ts +8 -3
- package/dist/server/dispatcher.js +16 -40
- package/dist/server/routes/runs.js +6 -4
- package/dist/server/security.d.ts +1 -7
- package/dist/server/security.js +2 -13
- package/dist/storage/clock.d.ts +25 -0
- package/dist/storage/clock.js +23 -0
- package/dist/storage/data-dir-lock.d.ts +3 -36
- package/dist/storage/data-dir-lock.js +48 -881
- package/dist/storage/index.d.ts +8 -1
- package/dist/storage/index.js +8 -2
- package/dist/storage/lazy.js +1 -0
- package/dist/storage/lock-core.d.ts +162 -0
- package/dist/storage/lock-core.js +885 -0
- package/dist/storage/sqlite.d.ts +19 -6
- package/dist/storage/sqlite.js +81 -89
- package/dist/storage/types.d.ts +27 -5
- package/dist/storage/types.js +21 -0
- package/dist/storage/utils.d.ts +22 -0
- package/dist/storage/utils.js +34 -0
- package/dist/target/local.d.ts +1 -0
- package/dist/target/local.js +23 -17
- package/dist/target/remote-errors.d.ts +21 -0
- package/dist/target/remote-errors.js +51 -0
- package/dist/target/remote-polling.d.ts +35 -0
- package/dist/target/remote-polling.js +104 -0
- package/dist/target/remote.d.ts +4 -0
- package/dist/target/remote.js +108 -301
- package/dist/target/sse-parser.d.ts +35 -0
- package/dist/target/sse-parser.js +163 -0
- package/dist/target/target.js +2 -0
- package/dist/target/types.d.ts +6 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/net.d.ts +10 -0
- package/dist/utils/net.js +16 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/app/types.d.ts
CHANGED
|
@@ -187,6 +187,11 @@ export interface ActionDockAppOptions {
|
|
|
187
187
|
packageInstanceId?: string;
|
|
188
188
|
/** 快照代次标识 */
|
|
189
189
|
generationId?: string;
|
|
190
|
+
/**
|
|
191
|
+
* 是否以数据目录持有者身份打开存储:true 时构造阶段收割遗留非终态运行记录。
|
|
192
|
+
* 默认 true(App 主路径即持有者);外部工具创建旁观视图时置 false。
|
|
193
|
+
*/
|
|
194
|
+
recoverOrphans?: boolean;
|
|
190
195
|
/** 临时配置覆写字典 */
|
|
191
196
|
configOverrides?: Record<string, unknown>;
|
|
192
197
|
/** 宿主所有者标识 */
|
|
@@ -12,8 +12,9 @@ export class ActionIndex {
|
|
|
12
12
|
try {
|
|
13
13
|
manifest = loadManifest(pkg.projectRoot);
|
|
14
14
|
}
|
|
15
|
-
catch {
|
|
16
|
-
//
|
|
15
|
+
catch (err) {
|
|
16
|
+
// 清单损坏时跳过索引但输出告警含路径,回退扫描 actions 目录
|
|
17
|
+
console.warn(`[Catalog] Failed to load manifest for package '${packageId}' at '${pkg.projectRoot}': ${err?.message || String(err)}`);
|
|
17
18
|
}
|
|
18
19
|
if (manifest && manifest.actions) {
|
|
19
20
|
for (const [actionId, item] of Object.entries(manifest.actions)) {
|
|
@@ -57,8 +58,9 @@ export class ActionIndex {
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
|
-
catch {
|
|
61
|
-
//
|
|
61
|
+
catch (err) {
|
|
62
|
+
// 目录扫描失败时输出告警含路径,不再无声吞没
|
|
63
|
+
console.warn(`[Catalog] Failed to scan actions directory for package '${packageId}' at '${actionsDir}': ${err?.message || String(err)}`);
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
}
|
|
@@ -44,7 +44,8 @@ export class PackageCatalog {
|
|
|
44
44
|
if (e.message?.startsWith("PACKAGE_ID_CONFLICT")) {
|
|
45
45
|
throw e;
|
|
46
46
|
}
|
|
47
|
-
//
|
|
47
|
+
// 无效项目跳过注册但输出告警含路径,杜绝无声丢弃
|
|
48
|
+
console.warn(`[Catalog] Skipping invalid package at '${abs}': ${e?.message || String(e)}`);
|
|
48
49
|
}
|
|
49
50
|
};
|
|
50
51
|
// 优先包含当前工作目录所在项目
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { checkBunRuntime, checkCliExecutable, checkNodeRuntime } from "./runtime.js";
|
|
2
|
+
export { checkGlobalStorage } from "./storage.js";
|
|
3
|
+
export { checkGlobalRegistry, checkLinkedPackageDependencies, checkUsesClosure, } from "./registry.js";
|
|
4
|
+
export { locateProject } from "./project-locate.js";
|
|
5
|
+
export { checkProject } from "./project.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { checkBunRuntime, checkCliExecutable, checkNodeRuntime } from "./runtime.js";
|
|
2
|
+
export { checkGlobalStorage } from "./storage.js";
|
|
3
|
+
export { checkGlobalRegistry, checkLinkedPackageDependencies, checkUsesClosure, } from "./registry.js";
|
|
4
|
+
export { locateProject } from "./project-locate.js";
|
|
5
|
+
export { checkProject } from "./project.js";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { findProjectRoot, loadProjectConfig } from "../../project/loader.js";
|
|
2
|
+
import { resolvePackageRoot } from "../../registry/registry.js";
|
|
3
|
+
/**
|
|
4
|
+
* 定位诊断目标工程根目录(支持包 ID / 磁盘路径 / 当前目录向上查找),
|
|
5
|
+
* 并按白名单预校验;结果写入 ctx.projectRoot,供后续工程级检查消费。
|
|
6
|
+
*/
|
|
7
|
+
export const locateProject = {
|
|
8
|
+
id: "project.locate",
|
|
9
|
+
run: (ctx) => {
|
|
10
|
+
let projectRoot = null;
|
|
11
|
+
if (ctx.packageIdOrPath) {
|
|
12
|
+
projectRoot =
|
|
13
|
+
resolvePackageRoot(ctx.packageIdOrPath, ctx.cwd, ctx.customHome) ||
|
|
14
|
+
findProjectRoot(ctx.packageIdOrPath);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
projectRoot = findProjectRoot(ctx.cwd);
|
|
18
|
+
}
|
|
19
|
+
if (projectRoot) {
|
|
20
|
+
try {
|
|
21
|
+
const config = loadProjectConfig(projectRoot);
|
|
22
|
+
if (ctx.packageAllowlist &&
|
|
23
|
+
Array.isArray(ctx.packageAllowlist) &&
|
|
24
|
+
ctx.packageAllowlist.length > 0 &&
|
|
25
|
+
!ctx.packageAllowlist.includes(config.id)) {
|
|
26
|
+
projectRoot = null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
// 预校验失败不改变根目录定位,交由后续详细诊断呈现具体错误;但绝不无声吞没,输出告警
|
|
31
|
+
console.warn(`[Doctor] Failed to pre-validate project config at '${projectRoot}': ${err?.message || String(err)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
ctx.projectRoot = projectRoot;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { discoverActionFiles, loadActions, loadPlaybooks, loadProjectConfig } from "../../project/loader.js";
|
|
4
|
+
import { loadManifest, MANIFEST_FILE_NAME } from "../../project/manifest.js";
|
|
5
|
+
import { createStorage, resolveDatabasePath } from "../../storage/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* 检查工程配置可解析性,并写入 ctx.packageId 供报告汇总使用。
|
|
8
|
+
*/
|
|
9
|
+
function checkProjectConfig(scope) {
|
|
10
|
+
const { ctx, config } = scope;
|
|
11
|
+
ctx.packageId = config.id;
|
|
12
|
+
ctx.checks.push({
|
|
13
|
+
id: "project.config",
|
|
14
|
+
category: "project",
|
|
15
|
+
name: "Project Configuration",
|
|
16
|
+
status: "ok",
|
|
17
|
+
message: `Valid (${config.id} v${config.version})`,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 检查工程 node_modules 中 @actiondock/sdk 的可解析性。
|
|
22
|
+
*/
|
|
23
|
+
function checkProjectSdk(scope) {
|
|
24
|
+
const { ctx, projectRoot } = scope;
|
|
25
|
+
const hasSdkInNodeModules = existsSync(join(projectRoot, "node_modules", "@actiondock", "sdk")) ||
|
|
26
|
+
existsSync(join(projectRoot, "node_modules", "@actiondock", "sdk", "package.json"));
|
|
27
|
+
if (hasSdkInNodeModules) {
|
|
28
|
+
ctx.checks.push({
|
|
29
|
+
id: "project.sdk",
|
|
30
|
+
category: "project",
|
|
31
|
+
name: "SDK Dependency",
|
|
32
|
+
status: "ok",
|
|
33
|
+
message: "Resolved @actiondock/sdk in node_modules",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
ctx.checks.push({
|
|
38
|
+
id: "project.sdk",
|
|
39
|
+
category: "project",
|
|
40
|
+
name: "SDK Dependency",
|
|
41
|
+
status: "warn",
|
|
42
|
+
message: "@actiondock/sdk not found in project node_modules",
|
|
43
|
+
fix: "Run 'npm link @actiondock/sdk' or 'npm install' in project directory",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 检查工程运行时数据库可写性。
|
|
49
|
+
*/
|
|
50
|
+
async function checkProjectStorage(scope) {
|
|
51
|
+
const { ctx, config } = scope;
|
|
52
|
+
const dbPath = resolveDatabasePath(config.id, { customHome: ctx.customHome });
|
|
53
|
+
try {
|
|
54
|
+
const projectStorage = createStorage(config.id, { customHome: ctx.customHome });
|
|
55
|
+
await projectStorage.setConfig("_doctor_probe_", "ok");
|
|
56
|
+
await projectStorage.deleteConfig("_doctor_probe_");
|
|
57
|
+
projectStorage.close();
|
|
58
|
+
ctx.checks.push({
|
|
59
|
+
id: "project.storage",
|
|
60
|
+
category: "project",
|
|
61
|
+
name: "Project Database",
|
|
62
|
+
status: "ok",
|
|
63
|
+
message: `Database writable at ${dbPath}`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
ctx.checks.push({
|
|
68
|
+
id: "project.storage",
|
|
69
|
+
category: "project",
|
|
70
|
+
name: "Project Database",
|
|
71
|
+
status: "error",
|
|
72
|
+
message: `Failed to write project runtime database: ${err.message}`,
|
|
73
|
+
fix: `Check write permissions for '${dirname(dbPath)}'`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* 检查工程 Actions 可加载性与数量。
|
|
79
|
+
*/
|
|
80
|
+
async function checkProjectActions(scope) {
|
|
81
|
+
const { ctx, projectRoot, config } = scope;
|
|
82
|
+
try {
|
|
83
|
+
const manifest = loadManifest(projectRoot);
|
|
84
|
+
let actionsCount = 0;
|
|
85
|
+
if (manifest?.actions) {
|
|
86
|
+
actionsCount = Object.keys(manifest.actions).length;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const actions = await loadActions(projectRoot, config.actionsDir);
|
|
90
|
+
actionsCount = actions.size;
|
|
91
|
+
}
|
|
92
|
+
if (actionsCount === 0) {
|
|
93
|
+
ctx.checks.push({
|
|
94
|
+
id: "project.actions",
|
|
95
|
+
category: "project",
|
|
96
|
+
name: "Actions",
|
|
97
|
+
status: "warn",
|
|
98
|
+
message: `No actions found in '${config.actionsDir || "actions"}'`,
|
|
99
|
+
fix: "Run 'ad action new <id>' to create your first action",
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
ctx.checks.push({
|
|
104
|
+
id: "project.actions",
|
|
105
|
+
category: "project",
|
|
106
|
+
name: "Actions",
|
|
107
|
+
status: "ok",
|
|
108
|
+
message: `${actionsCount} action(s) valid and loaded`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
ctx.checks.push({
|
|
114
|
+
id: "project.actions",
|
|
115
|
+
category: "project",
|
|
116
|
+
name: "Actions",
|
|
117
|
+
status: "error",
|
|
118
|
+
message: `Failed to load actions: ${err.message}`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* 检查 Action 清单与源码文件的双向一致性(轻量静态检测)。
|
|
124
|
+
*/
|
|
125
|
+
function checkProjectManifest(scope) {
|
|
126
|
+
const { ctx, projectRoot, config } = scope;
|
|
127
|
+
try {
|
|
128
|
+
const manifestPath = join(projectRoot, MANIFEST_FILE_NAME);
|
|
129
|
+
const manifest = loadManifest(projectRoot);
|
|
130
|
+
const actionFiles = discoverActionFiles(projectRoot, config.actionsDir || "actions");
|
|
131
|
+
if (!existsSync(manifestPath)) {
|
|
132
|
+
if (actionFiles.length > 0) {
|
|
133
|
+
ctx.checks.push({
|
|
134
|
+
id: "project.manifest",
|
|
135
|
+
category: "project",
|
|
136
|
+
name: "Action Manifest",
|
|
137
|
+
status: "warn",
|
|
138
|
+
message: `${MANIFEST_FILE_NAME} not found (${actionFiles.length} action source file(s) exist)`,
|
|
139
|
+
fix: "Run 'ad validate' to generate or check manifest",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else if (manifest && manifest.actions) {
|
|
144
|
+
const missingFiles = Object.entries(manifest.actions)
|
|
145
|
+
.filter(([_, item]) => !existsSync(join(projectRoot, item.entry)))
|
|
146
|
+
.map(([id, item]) => `${id} (${item.entry})`);
|
|
147
|
+
const manifestEntries = new Set(Object.values(manifest.actions).map((a) => a.entry.replace(/\\/g, "/")));
|
|
148
|
+
const untracked = actionFiles
|
|
149
|
+
.map((f) => relative(projectRoot, f).replace(/\\/g, "/"))
|
|
150
|
+
.filter((rel) => !manifestEntries.has(rel));
|
|
151
|
+
if (missingFiles.length > 0) {
|
|
152
|
+
ctx.checks.push({
|
|
153
|
+
id: "project.manifest",
|
|
154
|
+
category: "project",
|
|
155
|
+
name: "Action Manifest",
|
|
156
|
+
status: "warn",
|
|
157
|
+
message: `${missingFiles.length} action(s) in manifest point to missing files: ${missingFiles.join(", ")}`,
|
|
158
|
+
fix: "Run 'ad validate' to check or update actiondock.json",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
else if (untracked.length > 0) {
|
|
162
|
+
ctx.checks.push({
|
|
163
|
+
id: "project.manifest",
|
|
164
|
+
category: "project",
|
|
165
|
+
name: "Action Manifest",
|
|
166
|
+
status: "warn",
|
|
167
|
+
message: `${untracked.length} action file(s) not declared in manifest: ${untracked.join(", ")}`,
|
|
168
|
+
fix: "Run 'ad validate' to check or update actiondock.json",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const manifestStat = statSync(manifestPath);
|
|
173
|
+
const newerFiles = actionFiles.filter((f) => statSync(f).mtimeMs > manifestStat.mtimeMs + 2000);
|
|
174
|
+
if (newerFiles.length > 0) {
|
|
175
|
+
ctx.checks.push({
|
|
176
|
+
id: "project.manifest",
|
|
177
|
+
category: "project",
|
|
178
|
+
name: "Action Manifest",
|
|
179
|
+
status: "ok",
|
|
180
|
+
message: `Manifest valid (Note: ${newerFiles.length} action file(s) modified after manifest; run 'ad validate' if definitions changed)`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
ctx.checks.push({
|
|
185
|
+
id: "project.manifest",
|
|
186
|
+
category: "project",
|
|
187
|
+
name: "Action Manifest",
|
|
188
|
+
status: "ok",
|
|
189
|
+
message: "Manifest synchronized with action files",
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (err) {
|
|
196
|
+
ctx.checks.push({
|
|
197
|
+
id: "project.manifest",
|
|
198
|
+
category: "project",
|
|
199
|
+
name: "Action Manifest",
|
|
200
|
+
status: "error",
|
|
201
|
+
message: `Failed to inspect manifest: ${err.message}`,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 检查 files 声明边界与源码目录未声明引用。
|
|
207
|
+
*/
|
|
208
|
+
function checkProjectFiles(scope) {
|
|
209
|
+
const { ctx, projectRoot, config } = scope;
|
|
210
|
+
try {
|
|
211
|
+
const declaredFiles = config.files || [];
|
|
212
|
+
const hasSrc = existsSync(join(projectRoot, "src"));
|
|
213
|
+
const hasLib = existsSync(join(projectRoot, "lib"));
|
|
214
|
+
if (declaredFiles.length > 0) {
|
|
215
|
+
const missingDeclared = declaredFiles.filter((f) => !existsSync(join(projectRoot, f)));
|
|
216
|
+
if (missingDeclared.length > 0) {
|
|
217
|
+
ctx.checks.push({
|
|
218
|
+
id: "project.files",
|
|
219
|
+
category: "project",
|
|
220
|
+
name: "Declared Files",
|
|
221
|
+
status: "error",
|
|
222
|
+
message: `${missingDeclared.length} path(s) declared in 'files' do not exist: ${missingDeclared.join(", ")}`,
|
|
223
|
+
fix: `Verify and update 'files' in ${MANIFEST_FILE_NAME}`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
ctx.checks.push({
|
|
228
|
+
id: "project.files",
|
|
229
|
+
category: "project",
|
|
230
|
+
name: "Declared Files",
|
|
231
|
+
status: "ok",
|
|
232
|
+
message: `All ${declaredFiles.length} declared file/directory boundaries verified`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (hasSrc || hasLib) {
|
|
237
|
+
const actionFiles = discoverActionFiles(projectRoot, config.actionsDir || "actions");
|
|
238
|
+
let hasReferenceToSrcOrLib = false;
|
|
239
|
+
const unreadableFiles = [];
|
|
240
|
+
for (const actFile of actionFiles) {
|
|
241
|
+
try {
|
|
242
|
+
const src = readFileSync(actFile, "utf-8");
|
|
243
|
+
if (/['"](?:\.\.\/(?:src|lib)|\.\/(?:src|lib))[^'"]*['"]/.test(src)) {
|
|
244
|
+
hasReferenceToSrcOrLib = true;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
// 单文件读取失败:计入告警名单,边界检测结论不再基于无声缺失的数据
|
|
250
|
+
unreadableFiles.push(`${actFile} (${err?.message || String(err)})`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (hasReferenceToSrcOrLib) {
|
|
254
|
+
ctx.checks.push({
|
|
255
|
+
id: "project.files",
|
|
256
|
+
category: "project",
|
|
257
|
+
name: "Declared Files",
|
|
258
|
+
status: "error",
|
|
259
|
+
message: `Actions import modules from ${hasSrc ? "'src/'" : ""}${hasSrc && hasLib ? " and " : ""}${hasLib ? "'lib/'" : ""}, but 'files' is not declared in ${MANIFEST_FILE_NAME}`,
|
|
260
|
+
fix: `Add "files": [${hasSrc ? '"src"' : ""}${hasSrc && hasLib ? ', "lib"' : hasLib && !hasSrc ? '"lib"' : ""}] to ${MANIFEST_FILE_NAME}`,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
ctx.checks.push({
|
|
265
|
+
id: "project.files",
|
|
266
|
+
category: "project",
|
|
267
|
+
name: "Declared Files",
|
|
268
|
+
status: unreadableFiles.length > 0 ? "warn" : "ok",
|
|
269
|
+
message: unreadableFiles.length > 0
|
|
270
|
+
? `Project source directories clean, but ${unreadableFiles.length} action file(s) unreadable during boundary scan: ${unreadableFiles.join(", ")}`
|
|
271
|
+
: `Project source directories clean (no undeclared references to ${hasSrc ? "src/" : "lib/"})`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
// 文件边界检测整体失败:转为 error 检查项呈现在报告中,而非无声跳过
|
|
278
|
+
ctx.checks.push({
|
|
279
|
+
id: "project.files",
|
|
280
|
+
category: "project",
|
|
281
|
+
name: "Declared Files",
|
|
282
|
+
status: "error",
|
|
283
|
+
message: `Failed to inspect declared files and source boundaries: ${err?.message || String(err)}`,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* 检查工程 Playbook 规程文档可加载性。
|
|
289
|
+
*/
|
|
290
|
+
function checkProjectPlaybooks(scope) {
|
|
291
|
+
const { ctx, projectRoot, config } = scope;
|
|
292
|
+
try {
|
|
293
|
+
const playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
|
|
294
|
+
ctx.checks.push({
|
|
295
|
+
id: "project.playbooks",
|
|
296
|
+
category: "project",
|
|
297
|
+
name: "Playbooks",
|
|
298
|
+
status: "ok",
|
|
299
|
+
message: `${playbooks.size} playbook(s) valid`,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
catch (err) {
|
|
303
|
+
ctx.checks.push({
|
|
304
|
+
id: "project.playbooks",
|
|
305
|
+
category: "project",
|
|
306
|
+
name: "Playbooks",
|
|
307
|
+
status: "warn",
|
|
308
|
+
message: `Playbooks issue: ${err.message}`,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* 检查声明配置项的就绪状态(存储值 / 环境变量 / 默认值兜底)。
|
|
314
|
+
*/
|
|
315
|
+
async function checkProjectConfigReadiness(scope) {
|
|
316
|
+
const { ctx, config } = scope;
|
|
317
|
+
if (config.config && Object.keys(config.config).length > 0) {
|
|
318
|
+
const missingKeys = [];
|
|
319
|
+
const projectStorage = createStorage(config.id, { customHome: ctx.customHome });
|
|
320
|
+
for (const [key, def] of Object.entries(config.config)) {
|
|
321
|
+
const inStorage = await projectStorage.getConfig(key);
|
|
322
|
+
const envNames = Array.isArray(def.env) ? def.env : def.env ? [def.env] : [key];
|
|
323
|
+
const inEnv = envNames.some((e) => process.env[e] !== undefined);
|
|
324
|
+
const isRequired = def.required || (def.default === undefined && def.secret);
|
|
325
|
+
if ((isRequired || def.default === undefined) && inStorage === undefined && !inEnv) {
|
|
326
|
+
missingKeys.push(key);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
projectStorage.close();
|
|
330
|
+
if (missingKeys.length > 0) {
|
|
331
|
+
ctx.checks.push({
|
|
332
|
+
id: "project.config_readiness",
|
|
333
|
+
category: "project",
|
|
334
|
+
name: "Config Readiness",
|
|
335
|
+
status: "warn",
|
|
336
|
+
message: `Required config item(s) missing: ${missingKeys.join(", ")}`,
|
|
337
|
+
fix: `Run 'ad config set <KEY> <VALUE>' to configure missing keys`,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
ctx.checks.push({
|
|
342
|
+
id: "project.config_readiness",
|
|
343
|
+
category: "project",
|
|
344
|
+
name: "Config Readiness",
|
|
345
|
+
status: "ok",
|
|
346
|
+
message: "All declared configuration dependencies satisfied",
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* 工程级诊断入口:加载工程配置后按序执行全部工程检查;
|
|
353
|
+
* 配置损坏时转为 project.config error 检查项(此时 packageId 已就绪的值保持不回滚)。
|
|
354
|
+
*/
|
|
355
|
+
export const checkProject = {
|
|
356
|
+
id: "project.detail",
|
|
357
|
+
run: async (ctx) => {
|
|
358
|
+
const projectRoot = ctx.projectRoot;
|
|
359
|
+
if (!projectRoot)
|
|
360
|
+
return;
|
|
361
|
+
try {
|
|
362
|
+
const config = loadProjectConfig(projectRoot);
|
|
363
|
+
const scope = { ctx, projectRoot, config };
|
|
364
|
+
checkProjectConfig(scope);
|
|
365
|
+
checkProjectSdk(scope);
|
|
366
|
+
await checkProjectStorage(scope);
|
|
367
|
+
await checkProjectActions(scope);
|
|
368
|
+
checkProjectManifest(scope);
|
|
369
|
+
checkProjectFiles(scope);
|
|
370
|
+
checkProjectPlaybooks(scope);
|
|
371
|
+
await checkProjectConfigReadiness(scope);
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
ctx.checks.push({
|
|
375
|
+
id: "project.config",
|
|
376
|
+
category: "project",
|
|
377
|
+
name: "Project Configuration",
|
|
378
|
+
status: "error",
|
|
379
|
+
message: `Invalid actiondock.json: ${err.message}`,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DoctorCheck } from "../context.js";
|
|
2
|
+
/**
|
|
3
|
+
* 检查全局注册表健康度(链接数量与失效路径)。
|
|
4
|
+
*/
|
|
5
|
+
export declare const checkGlobalRegistry: DoctorCheck;
|
|
6
|
+
/**
|
|
7
|
+
* 检查已链接包的依赖完整性(声明依赖但缺失 node_modules)。
|
|
8
|
+
*/
|
|
9
|
+
export declare const checkLinkedPackageDependencies: DoctorCheck;
|
|
10
|
+
/**
|
|
11
|
+
* 检查清单 uses 声明的跨包依赖闭包可解析性。
|
|
12
|
+
*/
|
|
13
|
+
export declare const checkUsesClosure: DoctorCheck;
|