@actiondock/builder 2.9.1 → 2.11.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/archive.js +2 -1
- package/dist/build.d.ts +25 -2
- package/dist/build.js +99 -309
- package/dist/dependency-check.js +3 -3
- package/dist/dependency-resolver.d.ts +17 -0
- package/dist/dependency-resolver.js +69 -0
- package/dist/entry-generators.d.ts +15 -0
- package/dist/entry-generators.js +211 -0
- package/dist/export-composite.js +19 -1
- package/dist/export-source.d.ts +6 -0
- package/dist/export-source.js +24 -4
- package/dist/exporter.d.ts +1 -10
- package/dist/exporter.js +1 -16
- package/dist/fs-utils.js +2 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/lockfile-resolver.d.ts +9 -0
- package/dist/lockfile-resolver.js +56 -0
- package/dist/npm-pack.d.ts +6 -0
- package/dist/npm-pack.js +73 -0
- package/dist/pack.d.ts +2 -2
- package/dist/pack.js +8 -212
- package/dist/planner.js +40 -162
- package/dist/skill/custom-parser.d.ts +28 -0
- package/dist/skill/custom-parser.js +61 -0
- package/dist/skill/index.d.ts +1 -0
- package/dist/skill/index.js +1 -0
- package/dist/skill/templates.d.ts +28 -29
- package/dist/skill/templates.js +74 -144
- package/dist/skill-md.d.ts +3 -14
- package/dist/skill-md.js +5 -11
- package/dist/types.d.ts +19 -20
- package/dist/vendor.d.ts +5 -0
- package/dist/vendor.js +76 -0
- package/package.json +3 -3
- package/dist/export-batch.d.ts +0 -13
- package/dist/export-batch.js +0 -37
- package/dist/export-node.d.ts +0 -11
- package/dist/export-node.js +0 -48
package/dist/archive.js
CHANGED
|
@@ -4,6 +4,7 @@ import { basename, join } from "node:path";
|
|
|
4
4
|
import { Transform, Writable } from "node:stream";
|
|
5
5
|
import { pipeline } from "node:stream/promises";
|
|
6
6
|
import { traverseDirectory } from "@actiondock/core/project";
|
|
7
|
+
import { BuilderError } from "./errors.js";
|
|
7
8
|
/**
|
|
8
9
|
* 递归收集目录内全部条目。
|
|
9
10
|
* 目录遍历统一复用 core 的 traverseDirectory 单一事实源,
|
|
@@ -262,7 +263,7 @@ function tarHeader(path, size, mtimeSec, isDir, modeOrExec = false) {
|
|
|
262
263
|
prefix = cleanPath.slice(0, split);
|
|
263
264
|
name = cleanPath.slice(split + 1) + (trailingSlash ? "/" : "");
|
|
264
265
|
if (split <= 0 || Buffer.byteLength(prefix, "utf8") > 155 || Buffer.byteLength(name, "utf8") > 100) {
|
|
265
|
-
throw new
|
|
266
|
+
throw new BuilderError(`Archive path exceeds USTAR field capacity: ${path}`, "ARCHIVE_PATH_TOO_LONG");
|
|
266
267
|
}
|
|
267
268
|
}
|
|
268
269
|
const buf = Buffer.alloc(512);
|
package/dist/build.d.ts
CHANGED
|
@@ -1,10 +1,33 @@
|
|
|
1
1
|
export { copyPlanEntries } from "./stage-sources.js";
|
|
2
|
-
import type { BuildOptions, BuildResult } from "./types.js";
|
|
2
|
+
import type { BuildOptions, BuildProjectWithPlanOptions, BuildResult, SelectionPlan, SkillExporterOptions, SkillExportResult } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* 基于预先计算的 SelectionPlan 方案直接构建 Node.js 目录型交付产物。
|
|
5
|
+
*
|
|
6
|
+
* 核心构建管线涵盖可复现性检查、暂存区构建、清单与入口脚本生成、生产依赖物化及归档打包。
|
|
7
|
+
*
|
|
8
|
+
* 规划方案复用场景:
|
|
9
|
+
* - 当调用方已通过 SelectionPlanner.plan 计算出依赖与资产规划方案(例如技能导出 exportSkill、复合技能导出 exportCompositeSkill 或定制资产选择)时,直接消费 buildProjectWithPlan 核心管线,无需且严禁再次重复规划。
|
|
10
|
+
*
|
|
11
|
+
* 路径冲突仲裁策略:
|
|
12
|
+
* - 若 options.projectRoot 显式提供,则以 options.projectRoot 为准覆盖。
|
|
13
|
+
* - 若未提供,则兜底回退采用 plan.projectRoot。
|
|
14
|
+
*
|
|
15
|
+
* @param plan 已规划的依赖与资产选择方案
|
|
16
|
+
* @param options 构建执行选项
|
|
17
|
+
* @returns 构建产物结果描述
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildProjectWithPlan(plan: SelectionPlan, options?: BuildProjectWithPlanOptions): Promise<BuildResult>;
|
|
3
20
|
/**
|
|
4
21
|
* 构建 Node.js 目录型交付产物。
|
|
5
|
-
*
|
|
22
|
+
* 委托 SelectionPlanner 执行构建规划,随后直接消费核心构建管线 buildProjectWithPlan。
|
|
6
23
|
*
|
|
7
24
|
* @param options 构建选项
|
|
8
25
|
* @returns 构建产物结果描述
|
|
9
26
|
*/
|
|
10
27
|
export declare function buildProject(options: BuildOptions): Promise<BuildResult>;
|
|
28
|
+
/**
|
|
29
|
+
* 执行 Node 目录型 Skill 导出。
|
|
30
|
+
* 复用传入的 SelectionPlan 方案与 buildProjectWithPlan 核心构建管线,
|
|
31
|
+
* 产出可执行交付目录,并在目录内生成调用该入口的 SKILL.md 说明书。
|
|
32
|
+
*/
|
|
33
|
+
export declare function exportNodeSkill(root: string, targetSkillDir: string, plan: SelectionPlan, options: SkillExporterOptions, pkgSlug: string): Promise<SkillExportResult>;
|
package/dist/build.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync,
|
|
2
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
-
import { STANDALONE_ASYNC_UNSUPPORTED, } from "@actiondock/core";
|
|
5
4
|
import { getPackageSlug } from "@actiondock/core/project";
|
|
6
5
|
import { createZipArchiveAsync } from "./archive.js";
|
|
7
6
|
import { BuilderError } from "./errors.js";
|
|
@@ -9,7 +8,12 @@ import { collectRelativeFiles, getInternalDependencyVersion, replaceDirAtomic }
|
|
|
9
8
|
import { assertValidManifestActionIds, serializePlanManifest } from "./manifest.js";
|
|
10
9
|
import { SelectionPlanner } from "./planner.js";
|
|
11
10
|
import { copyPlanEntries } from "./stage-sources.js";
|
|
12
|
-
import {
|
|
11
|
+
import { generateNodeHostEntrySource, generateNodeSupervisorEntrySource } from "./entry-generators.js";
|
|
12
|
+
import { vendorDependencies } from "./vendor.js";
|
|
13
|
+
import { generateStandaloneSkillMd } from "./skill/templates.js";
|
|
14
|
+
import { writeRuntimeReferenceFile } from "./export-source.js";
|
|
15
|
+
import { buildConfigForTemplates, writeSkillMd, } from "./skill-md.js";
|
|
16
|
+
import { createArchive, resolveArchiveFormat } from "./export-archive.js";
|
|
13
17
|
export { copyPlanEntries } from "./stage-sources.js";
|
|
14
18
|
/**
|
|
15
19
|
* 检查外部依赖中声明的生命周期安装脚本。
|
|
@@ -42,8 +46,9 @@ function detectLifecycleScripts(root, dependencies) {
|
|
|
42
46
|
}
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
catch {
|
|
46
|
-
//
|
|
49
|
+
catch (err) {
|
|
50
|
+
// 防御与透明:依赖生命周期解析失败透传警告日志
|
|
51
|
+
console.warn(`[actiondock] Warning: Failed to inspect lifecycle scripts for dependency '${dep.name}': ${err instanceof Error ? err.message : String(err)}`);
|
|
47
52
|
}
|
|
48
53
|
break;
|
|
49
54
|
}
|
|
@@ -64,227 +69,6 @@ function calculateDirectoryDigest(dir) {
|
|
|
64
69
|
}
|
|
65
70
|
return hasher.digest("hex");
|
|
66
71
|
}
|
|
67
|
-
/**
|
|
68
|
-
* 生成 Host 子进程入口脚本源码(负责运行 ActionDockHost,通过 Node IPC 暴露 Target)。
|
|
69
|
-
* 与 serializePlanManifest / stageSources 的过滤条件保持一致:跨包外部 Action 不进入 import 与运行时注册,
|
|
70
|
-
* 避免入口 import 清单没有的源文件产生孤儿模块(外部源码未物化进本包目录,运行时必然加载失败)。
|
|
71
|
-
*/
|
|
72
|
-
function generateNodeHostEntrySource(plan) {
|
|
73
|
-
// 仅包自有 Action 参与 import 与注册,与产物清单保持同一事实源
|
|
74
|
-
const ownActions = plan.actions.filter((a) => isOwnAction(a));
|
|
75
|
-
const imports = ownActions
|
|
76
|
-
.map((act, idx) => `import action_${idx} from ${JSON.stringify(`./${act.entry.replace(/\\/g, "/")}`)};`)
|
|
77
|
-
.join("\n");
|
|
78
|
-
const actionsDict = {};
|
|
79
|
-
for (const a of ownActions) {
|
|
80
|
-
actionsDict[a.id] = {
|
|
81
|
-
entry: a.entry,
|
|
82
|
-
description: a.description || "",
|
|
83
|
-
inputSchema: a.inputSchema ?? null,
|
|
84
|
-
outputSchema: a.outputSchema ?? null,
|
|
85
|
-
uses: a.uses || [],
|
|
86
|
-
tags: a.tags || [],
|
|
87
|
-
annotations: a.annotations || {},
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
const actionItems = ownActions
|
|
91
|
-
.map((a, idx) => {
|
|
92
|
-
const entryObj = `{
|
|
93
|
-
...(typeof action_${idx} === "function" ? { run: action_${idx} } : action_${idx}),
|
|
94
|
-
id: action_${idx}?.id || ${JSON.stringify(a.id)},
|
|
95
|
-
description: action_${idx}?.description || ${JSON.stringify(a.description || "")},
|
|
96
|
-
inputSchema: action_${idx}?.inputSchema ?? ${JSON.stringify(a.inputSchema ?? null)},
|
|
97
|
-
outputSchema: action_${idx}?.outputSchema ?? ${JSON.stringify(a.outputSchema ?? null)},
|
|
98
|
-
uses: action_${idx}?.uses || ${JSON.stringify(a.uses || [])},
|
|
99
|
-
tags: action_${idx}?.tags || ${JSON.stringify(a.tags || [])},
|
|
100
|
-
annotations: action_${idx}?.annotations || ${JSON.stringify(a.annotations || {})},
|
|
101
|
-
}`;
|
|
102
|
-
return entryObj;
|
|
103
|
-
})
|
|
104
|
-
.join(",\n ");
|
|
105
|
-
return `#!/usr/bin/env node
|
|
106
|
-
// AUTO-GENERATED HOST ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
|
|
107
|
-
import {
|
|
108
|
-
createActionDock,
|
|
109
|
-
createNodePlatform,
|
|
110
|
-
} from "@actiondock/core";
|
|
111
|
-
import { serveParentIpc } from "@actiondock/core/server";
|
|
112
|
-
${imports}
|
|
113
|
-
|
|
114
|
-
let dataDir;
|
|
115
|
-
const configOverrides = {};
|
|
116
|
-
const args = process.argv.slice(2);
|
|
117
|
-
for (let i = 0; i < args.length; i++) {
|
|
118
|
-
if (args[i] === "--data-dir" && i + 1 < args.length) {
|
|
119
|
-
dataDir = args[++i];
|
|
120
|
-
} else if (args[i].startsWith("--data-dir=")) {
|
|
121
|
-
dataDir = args[i].slice(11);
|
|
122
|
-
} else if (args[i] === "--config" && i + 1 < args.length) {
|
|
123
|
-
const raw = args[++i];
|
|
124
|
-
const [k, ...v] = raw.split("=");
|
|
125
|
-
if (k) configOverrides[k] = v.join("=");
|
|
126
|
-
} else if (args[i].startsWith("--config=")) {
|
|
127
|
-
const raw = args[i].slice(9);
|
|
128
|
-
const [k, ...v] = raw.split("=");
|
|
129
|
-
if (k) configOverrides[k] = v.join("=");
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const service = await createActionDock({
|
|
134
|
-
dataDir,
|
|
135
|
-
packages: [
|
|
136
|
-
{
|
|
137
|
-
dataDir,
|
|
138
|
-
configOverrides,
|
|
139
|
-
projectConfig: {
|
|
140
|
-
id: ${JSON.stringify(plan.packageId)},
|
|
141
|
-
name: ${JSON.stringify(plan.packageName)},
|
|
142
|
-
version: ${JSON.stringify(plan.version)},
|
|
143
|
-
description: ${JSON.stringify(plan.description || "")},
|
|
144
|
-
config: ${JSON.stringify(plan.configDefs || {})},
|
|
145
|
-
actions: ${JSON.stringify(actionsDict)},
|
|
146
|
-
},
|
|
147
|
-
actions: [
|
|
148
|
-
${actionItems}
|
|
149
|
-
],
|
|
150
|
-
},
|
|
151
|
-
],
|
|
152
|
-
platform: createNodePlatform({ dataDir }),
|
|
153
|
-
autoLoadCurrentProject: false,
|
|
154
|
-
scanLinkedPackages: false,
|
|
155
|
-
});
|
|
156
|
-
await serveParentIpc(service);
|
|
157
|
-
`;
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
|
|
161
|
-
*/
|
|
162
|
-
function generateNodeSupervisorEntrySource(plan) {
|
|
163
|
-
return `#!/usr/bin/env node
|
|
164
|
-
// AUTO-GENERATED SUPERVISOR ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
|
|
165
|
-
import { spawn } from "node:child_process";
|
|
166
|
-
import { join } from "node:path";
|
|
167
|
-
import {
|
|
168
|
-
STANDALONE_ASYNC_UNSUPPORTED,
|
|
169
|
-
} from "@actiondock/core";
|
|
170
|
-
import {
|
|
171
|
-
ExitCode,
|
|
172
|
-
IpcActionDockService,
|
|
173
|
-
StandaloneDispatcher,
|
|
174
|
-
} from "@actiondock/core/server";
|
|
175
|
-
|
|
176
|
-
const METADATA = {
|
|
177
|
-
packageId: ${JSON.stringify(plan.packageId)},
|
|
178
|
-
version: ${JSON.stringify(plan.version)},
|
|
179
|
-
description: ${JSON.stringify(plan.description || "")},
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const argv = process.argv.slice(2);
|
|
183
|
-
|
|
184
|
-
// 1. 监督进程接管参数校验,明确拒绝 --async
|
|
185
|
-
if (argv.includes("--async")) {
|
|
186
|
-
const isJson = argv.includes("--json");
|
|
187
|
-
if (isJson) {
|
|
188
|
-
console.log(
|
|
189
|
-
JSON.stringify(
|
|
190
|
-
{
|
|
191
|
-
ok: false,
|
|
192
|
-
error: {
|
|
193
|
-
code: ${JSON.stringify(STANDALONE_ASYNC_UNSUPPORTED)},
|
|
194
|
-
message:
|
|
195
|
-
"Async execution is not supported in standalone single-execution binaries. Use 'ad serve' or remote target.",
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
null,
|
|
199
|
-
2
|
|
200
|
-
)
|
|
201
|
-
);
|
|
202
|
-
} else {
|
|
203
|
-
console.error(
|
|
204
|
-
"Error [" + STANDALONE_ASYNC_UNSUPPORTED + "]: Async execution is not supported in standalone single-execution binaries."
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
process.exit(ExitCode.FAILURE);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// 2. 静态元数据快速返回
|
|
211
|
-
if (argv.includes("-v") || argv.includes("-V") || argv.includes("--version") || argv[0] === "version") {
|
|
212
|
-
console.log(\`\${METADATA.packageId} v\${METADATA.version}\`);
|
|
213
|
-
process.exit(ExitCode.SUCCESS);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (argv.includes("-h") || argv.includes("--help") || argv[0] === "help") {
|
|
217
|
-
console.log(\`\${METADATA.packageId} (v\${METADATA.version})\`);
|
|
218
|
-
if (METADATA.description) console.log(METADATA.description + "\\n");
|
|
219
|
-
console.log("Usage:");
|
|
220
|
-
console.log(" <cmd> list [--json] List available actions");
|
|
221
|
-
console.log(" <cmd> describe <id> [--json] Show action details and schemas");
|
|
222
|
-
console.log(" <cmd> run <id> [--input '<json>'] Execute action with JSON input");
|
|
223
|
-
console.log(" <cmd> config list/get/set/delete Manage package configuration");
|
|
224
|
-
console.log(" <cmd> state list/get/set/delete Manage shared state store");
|
|
225
|
-
console.log("\\nGlobal options:");
|
|
226
|
-
console.log(" --data-dir <path> Custom runtime database directory");
|
|
227
|
-
console.log(" --config <KEY=val> Temporary config override");
|
|
228
|
-
process.exit(ExitCode.SUCCESS);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// 3. 建立物理隔离监督边界,启动运行 ActionDockHost 的独立子进程
|
|
232
|
-
const hostScript = join(import.meta.dirname, "entry-host.js");
|
|
233
|
-
const child = spawn(process.execPath, [hostScript, ...argv], {
|
|
234
|
-
cwd: process.cwd(),
|
|
235
|
-
env: process.env,
|
|
236
|
-
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
// 4. 标准输出通道物理隔离与受控限流排空
|
|
240
|
-
const service = new IpcActionDockService({
|
|
241
|
-
childProcess: child,
|
|
242
|
-
maxDiagnosticBytes: 512 * 1024,
|
|
243
|
-
maxDiagnosticRate: 64 * 1024,
|
|
244
|
-
diagnosticTarget: process.stderr,
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
let cleanedUp = false;
|
|
248
|
-
const cleanup = async () => {
|
|
249
|
-
if (cleanedUp) return;
|
|
250
|
-
cleanedUp = true;
|
|
251
|
-
try {
|
|
252
|
-
await service.close();
|
|
253
|
-
} catch {}
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
process.once("SIGINT", async () => {
|
|
257
|
-
await cleanup();
|
|
258
|
-
process.exit(ExitCode.SIGINT);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
process.once("SIGTERM", async () => {
|
|
262
|
-
await cleanup();
|
|
263
|
-
process.exit(143);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const dispatcher = new StandaloneDispatcher({
|
|
267
|
-
packageId: METADATA.packageId,
|
|
268
|
-
version: METADATA.version,
|
|
269
|
-
description: METADATA.description,
|
|
270
|
-
service,
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
const exitCode = await dispatcher.dispatch(argv);
|
|
275
|
-
await cleanup();
|
|
276
|
-
process.exit(exitCode);
|
|
277
|
-
} catch (err) {
|
|
278
|
-
if (err?.code === "HOST_PROCESS_EXITED") {
|
|
279
|
-
console.error("[Supervisor] Host process exited unexpectedly:", err.message);
|
|
280
|
-
} else {
|
|
281
|
-
console.error("[Supervisor] Error:", err?.message || err);
|
|
282
|
-
}
|
|
283
|
-
await cleanup();
|
|
284
|
-
process.exit(ExitCode.FAILURE);
|
|
285
|
-
}
|
|
286
|
-
`;
|
|
287
|
-
}
|
|
288
72
|
/**
|
|
289
73
|
* 拷贝 Action 源码、Playbook 规程与声明的代码文件/静态资产到暂存目录。
|
|
290
74
|
* 拷贝内核统一复用 stage-sources 的 copyPlanEntries,无差异化调优时直接全量拷贝。
|
|
@@ -358,96 +142,31 @@ function writeEntrypoints(stagingDir, plan) {
|
|
|
358
142
|
chmodSync(supervisorPath, 0o755);
|
|
359
143
|
chmodSync(forwarderPath, 0o755);
|
|
360
144
|
}
|
|
361
|
-
catch {
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
/**
|
|
366
|
-
* 物化锁定生产依赖到产物 node_modules 目录(vendorDeps 开启时)。
|
|
367
|
-
*/
|
|
368
|
-
function vendorDependencies(root, stagingDir, plan) {
|
|
369
|
-
const destNodeModules = join(stagingDir, "node_modules");
|
|
370
|
-
mkdirSync(destNodeModules, { recursive: true });
|
|
371
|
-
// 尝试复制本地已解析的生产依赖
|
|
372
|
-
const sourceCandidates = [
|
|
373
|
-
join(root, "node_modules"),
|
|
374
|
-
resolve(import.meta.dirname, "../../../node_modules"),
|
|
375
|
-
];
|
|
376
|
-
// 嵌套 node_modules 不再跳过:多版本共存时传递依赖必须完整物化,
|
|
377
|
-
// 否则产物运行时 Cannot find module 且构建阶段无任何警告;
|
|
378
|
-
// 保留 .git、.bin 等目录排除(.bin 为平台相关的命令行 shim,不参与运行时模块解析)
|
|
379
|
-
const copyVendorPackage = (srcDir, destDir) => {
|
|
380
|
-
if (!existsSync(srcDir))
|
|
381
|
-
return;
|
|
382
|
-
mkdirSync(destDir, { recursive: true });
|
|
383
|
-
const entries = readdirSync(srcDir);
|
|
384
|
-
for (const entry of entries) {
|
|
385
|
-
if (entry === ".git" ||
|
|
386
|
-
entry === ".actiondock" ||
|
|
387
|
-
entry === ".bin" ||
|
|
388
|
-
entry === "test" ||
|
|
389
|
-
entry === "tests") {
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
const srcPath = join(srcDir, entry);
|
|
393
|
-
const destPath = join(destDir, entry);
|
|
394
|
-
try {
|
|
395
|
-
const stat = statSync(srcPath);
|
|
396
|
-
if (stat.isDirectory()) {
|
|
397
|
-
copyVendorPackage(srcPath, destPath);
|
|
398
|
-
}
|
|
399
|
-
else if (stat.isFile()) {
|
|
400
|
-
copyFileSync(srcPath, destPath);
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
catch {
|
|
404
|
-
// 忽略无法读取的文件或死链接
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
};
|
|
408
|
-
for (const dep of plan.dependencies.external) {
|
|
409
|
-
if (dep.isDev)
|
|
410
|
-
continue;
|
|
411
|
-
for (const baseModules of sourceCandidates) {
|
|
412
|
-
const srcDep = join(baseModules, dep.name);
|
|
413
|
-
if (existsSync(srcDep)) {
|
|
414
|
-
const targetDep = join(destNodeModules, dep.name);
|
|
415
|
-
mkdirSync(dirname(targetDep), { recursive: true });
|
|
416
|
-
copyVendorPackage(srcDep, targetDep);
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
// 复制 ActionDock 内部依赖
|
|
422
|
-
for (const internalPkg of ["@actiondock/core", "@actiondock/sdk"]) {
|
|
423
|
-
for (const baseModules of sourceCandidates) {
|
|
424
|
-
const srcDep = join(baseModules, internalPkg);
|
|
425
|
-
if (existsSync(srcDep)) {
|
|
426
|
-
const targetDep = join(destNodeModules, internalPkg);
|
|
427
|
-
mkdirSync(dirname(targetDep), { recursive: true });
|
|
428
|
-
copyVendorPackage(srcDep, targetDep);
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
// Windows 平台不支持类 Unix 权限模式;非 Windows 环境下输出预警
|
|
147
|
+
if (process.platform !== "win32") {
|
|
148
|
+
console.warn(`[actiondock] Warning: Failed to set executable permissions on entry scripts: ${err instanceof Error ? err.message : String(err)}`);
|
|
431
149
|
}
|
|
432
150
|
}
|
|
433
151
|
}
|
|
434
152
|
/**
|
|
435
|
-
*
|
|
436
|
-
* 主流程仅做编排,各阶段职责由独立函数承担。
|
|
153
|
+
* 基于预先计算的 SelectionPlan 方案直接构建 Node.js 目录型交付产物。
|
|
437
154
|
*
|
|
438
|
-
*
|
|
155
|
+
* 核心构建管线涵盖可复现性检查、暂存区构建、清单与入口脚本生成、生产依赖物化及归档打包。
|
|
156
|
+
*
|
|
157
|
+
* 规划方案复用场景:
|
|
158
|
+
* - 当调用方已通过 SelectionPlanner.plan 计算出依赖与资产规划方案(例如技能导出 exportSkill、复合技能导出 exportCompositeSkill 或定制资产选择)时,直接消费 buildProjectWithPlan 核心管线,无需且严禁再次重复规划。
|
|
159
|
+
*
|
|
160
|
+
* 路径冲突仲裁策略:
|
|
161
|
+
* - 若 options.projectRoot 显式提供,则以 options.projectRoot 为准覆盖。
|
|
162
|
+
* - 若未提供,则兜底回退采用 plan.projectRoot。
|
|
163
|
+
*
|
|
164
|
+
* @param plan 已规划的依赖与资产选择方案
|
|
165
|
+
* @param options 构建执行选项
|
|
439
166
|
* @returns 构建产物结果描述
|
|
440
167
|
*/
|
|
441
|
-
export async function
|
|
442
|
-
const root = resolve(options.projectRoot);
|
|
443
|
-
const plan = SelectionPlanner.plan({
|
|
444
|
-
projectRoot: root,
|
|
445
|
-
config: options.config,
|
|
446
|
-
manifest: options.manifest,
|
|
447
|
-
actions: options.actions,
|
|
448
|
-
playbooks: options.playbooks,
|
|
449
|
-
skipDependencyValidation: options.skipDependencyValidation,
|
|
450
|
-
});
|
|
168
|
+
export async function buildProjectWithPlan(plan, options = {}) {
|
|
169
|
+
const root = resolve(options.projectRoot || plan.projectRoot);
|
|
451
170
|
if (plan.actions.length === 0) {
|
|
452
171
|
throw new BuilderError("No actions resolved for build");
|
|
453
172
|
}
|
|
@@ -551,3 +270,74 @@ export async function buildProject(options) {
|
|
|
551
270
|
sha256,
|
|
552
271
|
};
|
|
553
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* 构建 Node.js 目录型交付产物。
|
|
275
|
+
* 委托 SelectionPlanner 执行构建规划,随后直接消费核心构建管线 buildProjectWithPlan。
|
|
276
|
+
*
|
|
277
|
+
* @param options 构建选项
|
|
278
|
+
* @returns 构建产物结果描述
|
|
279
|
+
*/
|
|
280
|
+
export async function buildProject(options) {
|
|
281
|
+
const root = resolve(options.projectRoot);
|
|
282
|
+
const plan = SelectionPlanner.plan({
|
|
283
|
+
projectRoot: root,
|
|
284
|
+
config: options.config,
|
|
285
|
+
manifest: options.manifest,
|
|
286
|
+
actions: options.actions,
|
|
287
|
+
playbooks: options.playbooks,
|
|
288
|
+
skipDependencyValidation: options.skipDependencyValidation,
|
|
289
|
+
});
|
|
290
|
+
return buildProjectWithPlan(plan, {
|
|
291
|
+
projectRoot: root,
|
|
292
|
+
outDir: options.outDir,
|
|
293
|
+
outfile: options.outfile,
|
|
294
|
+
archive: options.archive,
|
|
295
|
+
vendorDeps: options.vendorDeps,
|
|
296
|
+
allowInstallScripts: options.allowInstallScripts,
|
|
297
|
+
requireReproducible: options.requireReproducible,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* 执行 Node 目录型 Skill 导出。
|
|
302
|
+
* 复用传入的 SelectionPlan 方案与 buildProjectWithPlan 核心构建管线,
|
|
303
|
+
* 产出可执行交付目录,并在目录内生成调用该入口的 SKILL.md 说明书。
|
|
304
|
+
*/
|
|
305
|
+
export async function exportNodeSkill(root, targetSkillDir, plan, options, pkgSlug) {
|
|
306
|
+
await buildProjectWithPlan(plan, {
|
|
307
|
+
projectRoot: root,
|
|
308
|
+
outDir: targetSkillDir,
|
|
309
|
+
vendorDeps: options.vendorDeps,
|
|
310
|
+
allowInstallScripts: options.allowInstallScripts,
|
|
311
|
+
requireReproducible: options.requireReproducible,
|
|
312
|
+
});
|
|
313
|
+
const usedExistingSkillMd = writeSkillMd(root, targetSkillDir, { skipSkillMd: options.skipSkillMd, skillMdPath: options.skillMdPath, pkgSlug }, () => {
|
|
314
|
+
const configForTemplates = buildConfigForTemplates(plan, plan.actionsDir || "actions", plan.playbooksDir || "playbooks");
|
|
315
|
+
return generateStandaloneSkillMd(configForTemplates, plan.actions, plan.playbooks, "node ./entry.mjs");
|
|
316
|
+
});
|
|
317
|
+
// 仅在使用自动模板时生成默认运行参考文件;Node 目录型使用自身入口,不要求全局安装 ad
|
|
318
|
+
if (usedExistingSkillMd === undefined && !options.skipSkillMd) {
|
|
319
|
+
writeRuntimeReferenceFile(targetSkillDir, {
|
|
320
|
+
dependencyStepLabel: "安装技能运行目录依赖",
|
|
321
|
+
relinkStepLabel: "完成安装后重新链接本技能",
|
|
322
|
+
invocationStyle: "node-entry",
|
|
323
|
+
entryRelPath: "node ./entry.mjs",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
let archivePath;
|
|
327
|
+
if (options.archive) {
|
|
328
|
+
archivePath = await createArchive(targetSkillDir, resolveArchiveFormat(options));
|
|
329
|
+
}
|
|
330
|
+
return {
|
|
331
|
+
packageId: plan.packageId,
|
|
332
|
+
version: plan.version,
|
|
333
|
+
mode: "node",
|
|
334
|
+
skillDir: targetSkillDir,
|
|
335
|
+
archivePath,
|
|
336
|
+
actionsCount: plan.actions.length,
|
|
337
|
+
playbooksCount: plan.playbooks.length,
|
|
338
|
+
actions: plan.actions.map((a) => a.id),
|
|
339
|
+
playbooks: plan.playbooks.map((p) => p.id),
|
|
340
|
+
files: collectRelativeFiles(targetSkillDir),
|
|
341
|
+
usedExistingSkillMd,
|
|
342
|
+
};
|
|
343
|
+
}
|
package/dist/dependency-check.js
CHANGED
|
@@ -175,9 +175,9 @@ export function assertRelativeDependenciesIntegrity(projectRoot, plan) {
|
|
|
175
175
|
const relToParent = relative(resolvedParent, resolvedTarget);
|
|
176
176
|
const isSiblingPackage = !isPathOutsideBoundary(relToParent);
|
|
177
177
|
const detail = isSiblingPackage
|
|
178
|
-
?
|
|
179
|
-
:
|
|
180
|
-
throw new BuilderError(`Action '${actionId}'
|
|
178
|
+
? `It is located in the parent directory of the project root, likely a neighboring package in a monorepo. If this dependency is required, please include it in this project or declare it as a package manager dependency; if it is a local helper module, please adjust the directory structure or declare it in 'files'`
|
|
179
|
+
: `It is completely outside the project. Exported Skill assets must not contain relative path dependencies outside the project. Please move the required module into the project and declare it in 'files'`;
|
|
180
|
+
throw new BuilderError(`Action '${actionId}' imports a module outside the project root via relative path '${specifier}' (resolved to '${resolvedTarget}'). ${detail}`, "EXTERNAL_LOCAL_DEPENDENCY");
|
|
181
181
|
}
|
|
182
182
|
// 检查目标文件是否在构建收集集合中
|
|
183
183
|
if (!collectedPaths.has(resolvedTarget)) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ExternalDependency } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 测试与类型声明文件后缀常量(供 fallback 清单过滤与 isIgnoredPath 共享同一口径)。
|
|
4
|
+
*/
|
|
5
|
+
export declare const TEST_FILE_SUFFIXES: string[];
|
|
6
|
+
/**
|
|
7
|
+
* 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
8
|
+
*/
|
|
9
|
+
export declare function isIgnoredPath(relPath: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* 收集项目根目录 package.json 中声明的外部 npm 依赖。
|
|
12
|
+
*
|
|
13
|
+
* 防御与透明原则:文件不存在返回空集合(合法的无依赖工程);
|
|
14
|
+
* 文件存在但不可读或 JSON 损坏时抛 PlannerError 并携带路径与原因,
|
|
15
|
+
* 严禁静默吞掉导致后续 build 的生产依赖、pack 的产物依赖与 vendorDeps 全部无声缺失。
|
|
16
|
+
*/
|
|
17
|
+
export declare function extractExternalDependencies(projectRoot: string): ExternalDependency[];
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { PlannerError } from "./errors.js";
|
|
4
|
+
/**
|
|
5
|
+
* 测试与类型声明文件后缀常量(供 fallback 清单过滤与 isIgnoredPath 共享同一口径)。
|
|
6
|
+
*/
|
|
7
|
+
export const TEST_FILE_SUFFIXES = [
|
|
8
|
+
".test.ts",
|
|
9
|
+
".test.js",
|
|
10
|
+
".test.tsx",
|
|
11
|
+
".test.jsx",
|
|
12
|
+
".spec.ts",
|
|
13
|
+
".spec.js",
|
|
14
|
+
".spec.tsx",
|
|
15
|
+
".spec.jsx",
|
|
16
|
+
".d.ts",
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
20
|
+
*/
|
|
21
|
+
export function isIgnoredPath(relPath) {
|
|
22
|
+
const normalized = relPath.replace(/\\/g, "/");
|
|
23
|
+
return (normalized.startsWith("node_modules/") ||
|
|
24
|
+
normalized.includes("/node_modules/") ||
|
|
25
|
+
normalized.startsWith(".git/") ||
|
|
26
|
+
normalized.startsWith("dist/") ||
|
|
27
|
+
normalized.startsWith(".actiondock/") ||
|
|
28
|
+
TEST_FILE_SUFFIXES.some((suffix) => normalized.endsWith(suffix)));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 收集项目根目录 package.json 中声明的外部 npm 依赖。
|
|
32
|
+
*
|
|
33
|
+
* 防御与透明原则:文件不存在返回空集合(合法的无依赖工程);
|
|
34
|
+
* 文件存在但不可读或 JSON 损坏时抛 PlannerError 并携带路径与原因,
|
|
35
|
+
* 严禁静默吞掉导致后续 build 的生产依赖、pack 的产物依赖与 vendorDeps 全部无声缺失。
|
|
36
|
+
*/
|
|
37
|
+
export function extractExternalDependencies(projectRoot) {
|
|
38
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
39
|
+
if (!existsSync(pkgPath))
|
|
40
|
+
return [];
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
const raw = readFileSync(pkgPath, "utf-8");
|
|
44
|
+
parsed = JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
throw new PlannerError(`Failed to read or parse package.json at ${pkgPath}: ${err?.message || String(err)}`, "EXTRACT_DEPS_ERROR");
|
|
48
|
+
}
|
|
49
|
+
const deps = [];
|
|
50
|
+
if (parsed.dependencies && typeof parsed.dependencies === "object") {
|
|
51
|
+
for (const [name, versionRange] of Object.entries(parsed.dependencies)) {
|
|
52
|
+
deps.push({
|
|
53
|
+
name,
|
|
54
|
+
versionRange: String(versionRange),
|
|
55
|
+
isDev: false,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (parsed.devDependencies && typeof parsed.devDependencies === "object") {
|
|
60
|
+
for (const [name, versionRange] of Object.entries(parsed.devDependencies)) {
|
|
61
|
+
deps.push({
|
|
62
|
+
name,
|
|
63
|
+
versionRange: String(versionRange),
|
|
64
|
+
isDev: true,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return deps;
|
|
69
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SelectionPlan } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 生成 Host 子进程入口脚本源码(负责运行 ActionDockHost,通过 Node IPC 暴露 Target)。
|
|
4
|
+
*
|
|
5
|
+
* 按需装配契约:
|
|
6
|
+
* - 不再静态导入全部包内 Action(消除发现路径的业务模块顶层副作用);
|
|
7
|
+
* - 仅携带清单元数据,执行时由现有执行服务通过清单 entry 与 NodeModuleLoader
|
|
8
|
+
* 按需加载业务实现;跨包外部 Action 不混入本包源码,依赖调用继续经 Host 路由;
|
|
9
|
+
* - 入口路径以产物包根目录为基准(import.meta.dirname),不依赖调用者当前工作目录。
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateNodeHostEntrySource(plan: SelectionPlan): string;
|
|
12
|
+
/**
|
|
13
|
+
* 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateNodeSupervisorEntrySource(plan: SelectionPlan): string;
|