@actiondock/builder 2.6.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 CHANGED
@@ -1,12 +1,12 @@
1
1
  # @actiondock/builder
2
2
 
3
- ActionDock 2.0 构建规划、目录交付与技能导出包。
3
+ ActionDock 2.x 构建规划、目录交付与技能导出包。
4
4
 
5
5
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D24.12.0-green?logo=node.js)](https://nodejs.org/)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?logo=typescript)](https://www.typescriptlang.org/)
7
7
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
8
8
 
9
- `@actiondock/builder` 负责静态分析依赖关系、构建 Node.js 目录交付产物、打包标准 npm 压缩包以及将工具和规程导出为可分发的 Agent Skill 资产。
9
+ `@actiondock/builder` 负责静态分析依赖关系、构建 Node.js 目录交付产物、打包标准 npm 压缩包以及将工具和规程导出为可分发的 Agent Skill 资产。在 ActionDock 2.x 中,Skill 模板生成与规程渲染能力已统一归位并入本包作为内置支持。
10
10
 
11
11
  ---
12
12
 
@@ -29,7 +29,6 @@ ActionDock 2.0 构建规划、目录交付与技能导出包。
29
29
  - 预置生产依赖:支持 `--vendor-deps` 选项,将锁定的生产依赖物化至产物目录内,实现离线自包含运行。
30
30
  - 可重现构建检验:支持 `--require-reproducible` 选项,当存在不受控的生命周期安装脚本时严格报错阻断,保证构建确定性。
31
31
  - 标准归档打包:支持 `--archive` 选项,自动将构建目录打包为标准 `.zip` 压缩归档文件。
32
- - 原单文件二进制编译选项(`--target` 与 `--bytecode`)已彻底删除,统一采用 Node.js 目录交付格式。
33
32
 
34
33
  ### packProject npm 包打包
35
34
 
@@ -39,9 +38,9 @@ ActionDock 2.0 构建规划、目录交付与技能导出包。
39
38
  - 预检模式:支持 `--dry-run` 选项,完整校验打包清单、动作列表与文件总数,无需实际写入磁盘。
40
39
  - 完整性校验:自动计算并输出打包产物的字节大小与 SHA-256 校验和。
41
40
 
42
- ### SkillExporter 技能导出器
41
+ ### SkillExporter 与技能导出
43
42
 
44
- [SkillExporter](./src/exporter.ts) 将原子 Action 与操作规程 Playbook 打包导出为面向主流 AI 智能体生态的标准 Agent Skill 资产:
43
+ [SkillExporter](./src/exporter.ts) 与 `exportSkill` 导出函数将原子 Action 与操作规程 Playbook 打包导出为面向主流 AI 智能体生态的标准 Agent Skill 资产:
45
44
 
46
45
  - 生成 SKILL.md 指令:提取 Playbook 规程内容与工具元数据,合成符合智能体规范的操作指南文档与声明头部。
47
46
  - 源码模式技能导出:使用 `--mode source`(默认模式),保留 TypeScript 源码与依赖声明,适合具备 Node.js 运行环境的智能体容器直接运行。
package/dist/archive.d.ts CHANGED
@@ -4,14 +4,6 @@ export declare function dosDateTime(ms: number): {
4
4
  date: number;
5
5
  time: number;
6
6
  };
7
- /**
8
- * 将目录打包为标准 zip 归档(PKZIP APPNOTE 结构)。
9
- * 直接流式分块写入磁盘,避免在内存中积压全量归档内容。
10
- *
11
- * @param dir 待打包目录(目录名即归档内根文件夹名)
12
- * @param outPath 输出 zip 文件路径
13
- */
14
- export declare function createZipArchive(dir: string, outPath: string): void;
15
7
  /**
16
8
  * 向可写流安全写入数据块,严格处理背压与流异常。
17
9
  * 当 stream.write 返回 false 时挂起等待 drain 事件触发;
@@ -23,14 +15,6 @@ export declare function writeToStream(stream: Writable, chunk: Buffer): Promise<
23
15
  * 逐文件流式读取与增量压缩写入磁盘,避免在内存中积压全量原文件与压缩内容。
24
16
  */
25
17
  export declare function createZipArchiveAsync(dir: string, outPath: string): Promise<void>;
26
- /**
27
- * 将目录打包为标准 tar.gz 归档(USTAR + gzip)。
28
- *
29
- * @param dir 待打包目录(目录名即归档内根文件夹名)
30
- * @param outPath 输出 tar.gz 文件路径
31
- */
32
- export declare function createTarGzArchive(dir: string, outPath: string): void;
33
- /** tar.gz 异步流式归档配置选项 */
34
18
  export interface TarGzArchiveOptions {
35
19
  /** 自定义可读流创建器,默认使用 node:fs 的 createReadStream */
36
20
  createReadStream?: (path: string) => Readable;
package/dist/archive.js CHANGED
@@ -1,71 +1,39 @@
1
- import { closeSync, createReadStream, createWriteStream, existsSync, openSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, writeSync } from "node:fs";
2
- import { createDeflateRaw, deflateRawSync, gzipSync, createGzip } from "node:zlib";
3
- import { basename, join, relative, sep } from "node:path";
1
+ import { createReadStream, createWriteStream, existsSync, statSync } from "node:fs";
2
+ import { createDeflateRaw, createGzip } from "node:zlib";
3
+ import { basename, join } from "node:path";
4
4
  import { Transform, Writable } from "node:stream";
5
5
  import { pipeline } from "node:stream/promises";
6
- import { assertPathWithinRoot } from "@actiondock/core";
7
- /** 递归收集目录内全部条目,执行安全路径边界校验与符号链接循环拦截 */
8
- function collectEntries(dir, baseDir = dir, visited = new Set()) {
6
+ import { traverseDirectory } from "@actiondock/core/project";
7
+ /**
8
+ * 递归收集目录内全部条目。
9
+ * 目录遍历统一复用 core 的 traverseDirectory 单一事实源,
10
+ * 自带安全路径边界校验、软链接越界跳过与循环拦截。
11
+ */
12
+ function collectEntries(dir) {
9
13
  if (!existsSync(dir))
10
14
  return [];
11
- if (visited.size === 0) {
12
- try {
13
- visited.add(existsSync(baseDir) ? realpathSync(baseDir) : baseDir);
14
- }
15
- catch {
16
- // 忽略
17
- }
18
- }
19
- const entries = [];
20
- let names;
21
- try {
22
- names = readdirSync(dir).sort();
23
- }
24
- catch {
25
- return [];
26
- }
27
- for (const name of names) {
28
- const fullPath = join(dir, name);
29
- try {
30
- assertPathWithinRoot(baseDir, fullPath, "archive path");
31
- }
32
- catch {
33
- continue;
34
- }
35
- let real;
36
- try {
37
- real = existsSync(fullPath) ? realpathSync(fullPath) : fullPath;
38
- }
39
- catch {
40
- continue;
41
- }
42
- try {
43
- assertPathWithinRoot(baseDir, real, "archive path");
44
- }
45
- catch {
46
- // 忽略并跳过指向 baseDir 外部的软链接
47
- continue;
48
- }
49
- if (visited.has(real)) {
50
- continue;
51
- }
52
- visited.add(real);
53
- try {
54
- const stat = statSync(fullPath);
55
- if (stat.isDirectory()) {
56
- entries.push({ relPath: relative(baseDir, fullPath).split(sep).join("/"), isDir: true });
57
- entries.push(...collectEntries(fullPath, baseDir, visited));
58
- }
59
- else if (stat.isFile()) {
60
- entries.push({ relPath: relative(baseDir, fullPath).split(sep).join("/"), isDir: false });
61
- }
62
- }
63
- catch {
64
- // 忽略无法访问或损坏的文件/符号链接
65
- continue;
66
- }
15
+ return traverseDirectory(dir, { includeDirs: true }).map((entry) => ({
16
+ relPath: entry.relPath,
17
+ isDir: entry.isDir,
18
+ }));
19
+ }
20
+ /**
21
+ * 归档条目可执行位启发式:bin/ 路径前缀约定为可执行产物。
22
+ * 与磁盘 mode 权限位任一命中即按 0755 落归档元数据。
23
+ */
24
+ const EXECUTABLE_PATH_HINT = "bin/";
25
+ /**
26
+ * 计算归档条目的 Unix 权限位:目录固定 0755;文件按「磁盘 mode 含执行位
27
+ * 或相对路径命中 bin/ 启发式」判定为 0755,其余 0644。
28
+ */
29
+ function computeUnixMode(relPath, mode, isDir) {
30
+ if (isDir) {
31
+ return 0o40755;
67
32
  }
68
- return entries;
33
+ const isExec = Boolean(mode & 0o111) ||
34
+ relPath.includes(EXECUTABLE_PATH_HINT) ||
35
+ relPath.startsWith(EXECUTABLE_PATH_HINT);
36
+ return isExec ? 0o100755 : 0o100644;
69
37
  }
70
38
  /* ------------------------------------------------------------------ */
71
39
  /* zip 格式写入 */
@@ -88,9 +56,6 @@ function updateCrc32(crc, buf) {
88
56
  }
89
57
  return crc;
90
58
  }
91
- function crc32(buf) {
92
- return (updateCrc32(0xffffffff, buf) ^ 0xffffffff) >>> 0;
93
- }
94
59
  /** Unix 毫秒时间戳转 DOS 时间格式(date/time 各 16 位) */
95
60
  export function dosDateTime(ms) {
96
61
  const d = new Date(ms);
@@ -104,104 +69,6 @@ export function dosDateTime(ms) {
104
69
  const date = ((year - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate();
105
70
  return { date, time };
106
71
  }
107
- /**
108
- * 将目录打包为标准 zip 归档(PKZIP APPNOTE 结构)。
109
- * 直接流式分块写入磁盘,避免在内存中积压全量归档内容。
110
- *
111
- * @param dir 待打包目录(目录名即归档内根文件夹名)
112
- * @param outPath 输出 zip 文件路径
113
- */
114
- export function createZipArchive(dir, outPath) {
115
- const rootName = basename(dir);
116
- const entries = collectEntries(dir);
117
- const fd = openSync(outPath, "w");
118
- const centralRecords = [];
119
- let offset = 0;
120
- try {
121
- for (const entry of entries) {
122
- // 归档内路径带根目录前缀;目录条目名以 / 结尾
123
- const name = `${rootName}/${entry.relPath}${entry.isDir ? "/" : ""}`;
124
- const nameBuf = Buffer.from(name, "utf8");
125
- const stat = statSync(join(dir, entry.relPath));
126
- const { date, time } = dosDateTime(stat.mtimeMs);
127
- const content = entry.isDir ? Buffer.alloc(0) : readFileSync(join(dir, entry.relPath));
128
- let method = 0;
129
- let payload = content;
130
- if (!entry.isDir && content.length > 0) {
131
- const deflated = deflateRawSync(content, { level: 9 });
132
- if (deflated.length < content.length) {
133
- method = 8;
134
- payload = deflated;
135
- }
136
- }
137
- const crc = crc32(content);
138
- // Local File Header(30 字节 + 文件名)
139
- const local = Buffer.alloc(30);
140
- local.writeUInt32LE(0x04034b50, 0);
141
- local.writeUInt16LE(20, 4);
142
- local.writeUInt16LE(0x0800, 6); // UTF-8 文件名标志
143
- local.writeUInt16LE(method, 8);
144
- local.writeUInt16LE(time, 10);
145
- local.writeUInt16LE(date, 12);
146
- local.writeUInt32LE(crc, 14);
147
- local.writeUInt32LE(payload.length, 18);
148
- local.writeUInt32LE(content.length, 22);
149
- local.writeUInt16LE(nameBuf.length, 26);
150
- local.writeUInt16LE(0, 28);
151
- writeSync(fd, local);
152
- writeSync(fd, nameBuf);
153
- if (payload.length > 0) {
154
- writeSync(fd, payload);
155
- }
156
- // Central Directory Header(46 字节 + 文件名)
157
- const central = Buffer.alloc(46);
158
- central.writeUInt32LE(0x02014b50, 0);
159
- central.writeUInt16LE((3 << 8) | 20, 4); // version made by: Unix + ZIP 2.0
160
- central.writeUInt16LE(20, 6); // version needed
161
- central.writeUInt16LE(0x0800, 8);
162
- central.writeUInt16LE(method, 10);
163
- central.writeUInt16LE(time, 12);
164
- central.writeUInt16LE(date, 14);
165
- central.writeUInt32LE(crc, 16);
166
- central.writeUInt32LE(payload.length, 20);
167
- central.writeUInt32LE(content.length, 24);
168
- central.writeUInt16LE(nameBuf.length, 28);
169
- central.writeUInt16LE(0, 30); // extra len
170
- central.writeUInt16LE(0, 32); // comment len
171
- central.writeUInt16LE(0, 34); // disk start
172
- central.writeUInt16LE(0, 36); // internal attrs
173
- // 外部属性:Unix 权限左移 16 位,目录附加 MS-DOS 目录位
174
- const isExec = !entry.isDir &&
175
- (Boolean(stat.mode & 0o111) || entry.relPath.includes("bin/") || entry.relPath.startsWith("bin/"));
176
- const fileMode = isExec ? 0o100755 : 0o100644;
177
- const extAttrs = ((entry.isDir ? 0o40755 : fileMode) << 16) | (entry.isDir ? 0x10 : 0);
178
- central.writeUInt32LE(extAttrs >>> 0, 38);
179
- central.writeUInt32LE(offset, 42);
180
- centralRecords.push({ header: Buffer.concat([central, nameBuf]), localOffset: offset });
181
- offset += local.length + nameBuf.length + payload.length;
182
- }
183
- const centralDirOffset = offset;
184
- let centralDirSize = 0;
185
- for (const record of centralRecords) {
186
- writeSync(fd, record.header);
187
- centralDirSize += record.header.length;
188
- }
189
- // End of Central Directory Record(22 字节)
190
- const eocd = Buffer.alloc(22);
191
- eocd.writeUInt32LE(0x06054b50, 0);
192
- eocd.writeUInt16LE(0, 4);
193
- eocd.writeUInt16LE(0, 6);
194
- eocd.writeUInt16LE(centralRecords.length, 8);
195
- eocd.writeUInt16LE(centralRecords.length, 10);
196
- eocd.writeUInt32LE(centralDirSize, 12);
197
- eocd.writeUInt32LE(centralDirOffset, 16);
198
- eocd.writeUInt16LE(0, 20); // comment len
199
- writeSync(fd, eocd);
200
- }
201
- finally {
202
- closeSync(fd);
203
- }
204
- }
205
72
  /**
206
73
  * 向可写流安全写入数据块,严格处理背压与流异常。
207
74
  * 当 stream.write 返回 false 时挂起等待 drain 事件触发;
@@ -267,10 +134,8 @@ export async function createZipArchiveAsync(dir, outPath) {
267
134
  const stat = statSync(fullPath);
268
135
  const { date, time } = dosDateTime(stat.mtimeMs);
269
136
  // 外部属性:Unix 权限左移 16 位,目录附加 MS-DOS 目录位
270
- const isExec = !entry.isDir &&
271
- (Boolean(stat.mode & 0o111) || entry.relPath.includes("bin/") || entry.relPath.startsWith("bin/"));
272
- const fileMode = isExec ? 0o100755 : 0o100644;
273
- const extAttrs = ((entry.isDir ? 0o40755 : fileMode) << 16) | (entry.isDir ? 0x10 : 0);
137
+ const fileMode = computeUnixMode(entry.relPath, stat.mode, entry.isDir);
138
+ const extAttrs = (fileMode << 16) | (entry.isDir ? 0x10 : 0);
274
139
  const localOffset = offset;
275
140
  const isDeflatedFile = !entry.isDir && stat.size > 0;
276
141
  const method = isDeflatedFile ? 8 : 0;
@@ -429,37 +294,6 @@ function tarHeader(path, size, mtimeSec, isDir, modeOrExec = false) {
429
294
  buf.write(" ", 155, 1, "ascii");
430
295
  return buf;
431
296
  }
432
- /** 将字节数补齐到 512 的整数倍 */
433
- function pad512(length) {
434
- const rem = length % 512;
435
- return rem === 0 ? Buffer.alloc(0) : Buffer.alloc(512 - rem);
436
- }
437
- /**
438
- * 将目录打包为标准 tar.gz 归档(USTAR + gzip)。
439
- *
440
- * @param dir 待打包目录(目录名即归档内根文件夹名)
441
- * @param outPath 输出 tar.gz 文件路径
442
- */
443
- export function createTarGzArchive(dir, outPath) {
444
- const rootName = basename(dir);
445
- const entries = collectEntries(dir);
446
- const chunks = [];
447
- for (const entry of entries) {
448
- const path = `${rootName}/${entry.relPath}${entry.isDir ? "/" : ""}`;
449
- const fullPath = join(dir, entry.relPath);
450
- const stat = statSync(fullPath);
451
- const content = entry.isDir ? Buffer.alloc(0) : readFileSync(fullPath);
452
- const isExec = !entry.isDir &&
453
- (Boolean(stat.mode & 0o111) || entry.relPath.includes("bin/") || entry.relPath.startsWith("bin/"));
454
- chunks.push(tarHeader(path, content.length, Math.floor(stat.mtimeMs / 1000), entry.isDir, isExec));
455
- if (!entry.isDir) {
456
- chunks.push(content, pad512(content.length));
457
- }
458
- }
459
- // 归档结尾:两个全零块
460
- chunks.push(Buffer.alloc(1024));
461
- writeFileSync(outPath, gzipSync(Buffer.concat(chunks), { level: 9 }));
462
- }
463
297
  /**
464
298
  * 将目录异步流式打包为标准 tar.gz 归档(USTAR 与 gzip 流),避免在内存中积压全量原文件与压缩内容。
465
299
  * 严格基于 Node.js 异步流背压机制与错误传播链路实现。
@@ -482,9 +316,8 @@ export async function createTarGzArchiveAsync(dir, outPath, options) {
482
316
  const path = `${rootName}/${entry.relPath}${entry.isDir ? "/" : ""}`;
483
317
  const fullPath = join(dir, entry.relPath);
484
318
  const stat = statSync(fullPath);
485
- const isExec = !entry.isDir &&
486
- (Boolean(stat.mode & 0o111) || entry.relPath.includes("bin/") || entry.relPath.startsWith("bin/"));
487
- const header = tarHeader(path, entry.isDir ? 0 : stat.size, Math.floor(stat.mtimeMs / 1000), entry.isDir, isExec);
319
+ const mode = computeUnixMode(entry.relPath, stat.mode, entry.isDir);
320
+ const header = tarHeader(path, entry.isDir ? 0 : stat.size, Math.floor(stat.mtimeMs / 1000), entry.isDir, entry.isDir ? "0000755" : mode === 0o100755);
488
321
  await writeToStream(gzip, header);
489
322
  if (!entry.isDir && stat.size > 0) {
490
323
  const fileStream = getReadStream(fullPath);
package/dist/build.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { copyPlanEntries } from "./stage-sources.js";
1
2
  import type { BuildOptions, BuildResult } from "./types.js";
2
3
  /**
3
4
  * 构建 Node.js 目录型交付产物。
package/dist/build.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
3
  import { basename, dirname, join, resolve } from "node:path";
4
- import { STANDALONE_ASYNC_UNSUPPORTED, getPackageSlug, } from "@actiondock/core";
4
+ import { STANDALONE_ASYNC_UNSUPPORTED, } from "@actiondock/core";
5
+ import { getPackageSlug } from "@actiondock/core/project";
5
6
  import { createZipArchiveAsync } from "./archive.js";
6
7
  import { BuilderError } from "./errors.js";
7
8
  import { collectRelativeFiles, getInternalDependencyVersion, replaceDirAtomic } from "./fs-utils.js";
8
9
  import { assertValidManifestActionIds, serializePlanManifest } from "./manifest.js";
9
10
  import { SelectionPlanner } from "./planner.js";
11
+ import { copyPlanEntries } from "./stage-sources.js";
12
+ import { isOwnAction } from "./types.js";
13
+ export { copyPlanEntries } from "./stage-sources.js";
10
14
  /**
11
15
  * 检查外部依赖中声明的生命周期安装脚本。
12
16
  */
@@ -67,7 +71,7 @@ function calculateDirectoryDigest(dir) {
67
71
  */
68
72
  function generateNodeHostEntrySource(plan) {
69
73
  // 仅包自有 Action 参与 import 与注册,与产物清单保持同一事实源
70
- const ownActions = plan.actions.filter((a) => !a.isExternal && !a.id.includes("/"));
74
+ const ownActions = plan.actions.filter((a) => isOwnAction(a));
71
75
  const imports = ownActions
72
76
  .map((act, idx) => `import action_${idx} from ${JSON.stringify(`./${act.entry.replace(/\\/g, "/")}`)};`)
73
77
  .join("\n");
@@ -101,11 +105,10 @@ function generateNodeHostEntrySource(plan) {
101
105
  return `#!/usr/bin/env node
102
106
  // AUTO-GENERATED HOST ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
103
107
  import {
104
- createActionDockHost,
105
- createActionDockTarget,
106
- serveParentIpc,
108
+ createActionDock,
109
+ createNodePlatform,
107
110
  } from "@actiondock/core";
108
- import { createNodePlatform } from "@actiondock/runtime-node";
111
+ import { serveParentIpc } from "@actiondock/core/server";
109
112
  ${imports}
110
113
 
111
114
  let dataDir;
@@ -127,7 +130,7 @@ for (let i = 0; i < args.length; i++) {
127
130
  }
128
131
  }
129
132
 
130
- const host = await createActionDockHost({
133
+ const service = await createActionDock({
131
134
  dataDir,
132
135
  packages: [
133
136
  {
@@ -147,10 +150,10 @@ const host = await createActionDockHost({
147
150
  },
148
151
  ],
149
152
  platform: createNodePlatform({ dataDir }),
153
+ autoLoadCurrentProject: false,
154
+ scanLinkedPackages: false,
150
155
  });
151
-
152
- const target = await createActionDockTarget({ type: "local", host });
153
- await serveParentIpc(target);
156
+ await serveParentIpc(service);
154
157
  `;
155
158
  }
156
159
  /**
@@ -162,11 +165,13 @@ function generateNodeSupervisorEntrySource(plan) {
162
165
  import { spawn } from "node:child_process";
163
166
  import { join } from "node:path";
164
167
  import {
165
- IpcActionDockTarget,
166
- StandaloneDispatcher,
167
- ExitCode,
168
168
  STANDALONE_ASYNC_UNSUPPORTED,
169
169
  } from "@actiondock/core";
170
+ import {
171
+ ExitCode,
172
+ IpcActionDockService,
173
+ StandaloneDispatcher,
174
+ } from "@actiondock/core/server";
170
175
 
171
176
  const METADATA = {
172
177
  packageId: ${JSON.stringify(plan.packageId)},
@@ -232,7 +237,7 @@ const child = spawn(process.execPath, [hostScript, ...argv], {
232
237
  });
233
238
 
234
239
  // 4. 标准输出通道物理隔离与受控限流排空
235
- const target = new IpcActionDockTarget({
240
+ const service = new IpcActionDockService({
236
241
  childProcess: child,
237
242
  maxDiagnosticBytes: 512 * 1024,
238
243
  maxDiagnosticRate: 64 * 1024,
@@ -244,7 +249,7 @@ const cleanup = async () => {
244
249
  if (cleanedUp) return;
245
250
  cleanedUp = true;
246
251
  try {
247
- await target.close();
252
+ await service.close();
248
253
  } catch {}
249
254
  };
250
255
 
@@ -262,7 +267,7 @@ const dispatcher = new StandaloneDispatcher({
262
267
  packageId: METADATA.packageId,
263
268
  version: METADATA.version,
264
269
  description: METADATA.description,
265
- target,
270
+ service,
266
271
  });
267
272
 
268
273
  try {
@@ -282,45 +287,10 @@ try {
282
287
  }
283
288
  /**
284
289
  * 拷贝 Action 源码、Playbook 规程与声明的代码文件/静态资产到暂存目录。
285
- * 与 serializePlanManifest 的过滤条件保持一致:跨包外部 Action 不物化进本包目录,
286
- * 避免产物混入外部源文件与本包入口发生非确定性覆盖。
290
+ * 拷贝内核统一复用 stage-sources 的 copyPlanEntries,无差异化调优时直接全量拷贝。
287
291
  */
288
- function stageSources(root, stagingDir, plan) {
289
- // 拷贝 Action 源码文件,保留相对路径(仅拷贝包自有 Action,跨包外部依赖不物化进本包目录)
290
- const relativeActionImports = [];
291
- for (const act of plan.actions) {
292
- if (act.isExternal || act.id.includes("/")) {
293
- continue;
294
- }
295
- if (existsSync(act.resolvedPath)) {
296
- const destFile = join(stagingDir, act.entry);
297
- mkdirSync(dirname(destFile), { recursive: true });
298
- copyFileSync(act.resolvedPath, destFile);
299
- relativeActionImports.push(`./${act.entry.replace(/\\/g, "/")}`);
300
- }
301
- }
302
- // 拷贝 Playbook 规程文件
303
- if (plan.playbooks.length > 0) {
304
- const playbooksDir = plan.playbooksDir || "playbooks";
305
- const playbooksDestDir = join(stagingDir, playbooksDir);
306
- mkdirSync(playbooksDestDir, { recursive: true });
307
- for (const pb of plan.playbooks) {
308
- if (existsSync(pb.filePath)) {
309
- const destPb = join(playbooksDestDir, basename(pb.filePath));
310
- copyFileSync(pb.filePath, destPb);
311
- }
312
- }
313
- }
314
- // 拷贝声明的代码文件与静态资产
315
- for (const dep of plan.dependencies.modulesAndAssets) {
316
- if ((dep.type === "asset" || dep.type === "module" || dep.type === "file") &&
317
- existsSync(dep.resolvedPath)) {
318
- const destPath = join(stagingDir, dep.path);
319
- mkdirSync(dirname(destPath), { recursive: true });
320
- copyFileSync(dep.resolvedPath, destPath);
321
- }
322
- }
323
- return relativeActionImports;
292
+ function stageSources(stagingDir, plan) {
293
+ return copyPlanEntries(stagingDir, plan);
324
294
  }
325
295
  /**
326
296
  * 生成裁剪后的 actiondock.json(项目元数据与清单的单一事实源)。
@@ -339,7 +309,6 @@ function writePkgJsonAndLockfiles(root, stagingDir, plan, pkgSlug) {
339
309
  // 准备锁定的生产依赖
340
310
  const productionDependencies = {
341
311
  "@actiondock/core": getInternalDependencyVersion(),
342
- "@actiondock/runtime-node": getInternalDependencyVersion(),
343
312
  "@actiondock/sdk": getInternalDependencyVersion(),
344
313
  };
345
314
  for (const ext of plan.dependencies.external) {
@@ -450,7 +419,7 @@ function vendorDependencies(root, stagingDir, plan) {
450
419
  }
451
420
  }
452
421
  // 复制 ActionDock 内部依赖
453
- for (const internalPkg of ["@actiondock/core", "@actiondock/sdk", "@actiondock/runtime-node"]) {
422
+ for (const internalPkg of ["@actiondock/core", "@actiondock/sdk"]) {
454
423
  for (const baseModules of sourceCandidates) {
455
424
  const srcDep = join(baseModules, internalPkg);
456
425
  if (existsSync(srcDep)) {
@@ -470,10 +439,6 @@ function vendorDependencies(root, stagingDir, plan) {
470
439
  * @returns 构建产物结果描述
471
440
  */
472
441
  export async function buildProject(options) {
473
- // 彻底删除原有单文件二进制输出语义,明确拒绝并报错
474
- if (options.target !== undefined || options.bytecode !== undefined) {
475
- throw new BuilderError("Unsupported build mode: '--target' and '--bytecode' standalone single-file binary compilation have been removed in ActionDock 2.0. Directory-based Node.js builds are now the standard distribution format.", "UNSUPPORTED_BUILD_MODE");
476
- }
477
442
  const root = resolve(options.projectRoot);
478
443
  const plan = SelectionPlanner.plan({
479
444
  projectRoot: root,
@@ -512,7 +477,7 @@ export async function buildProject(options) {
512
477
  mkdirSync(stagingDir, { recursive: true });
513
478
  let platformInfo;
514
479
  try {
515
- stageSources(root, stagingDir, plan);
480
+ stageSources(stagingDir, plan);
516
481
  writeManifest(stagingDir, plan);
517
482
  writePkgJsonAndLockfiles(root, stagingDir, plan, pkgSlug);
518
483
  writeEntrypoints(stagingDir, plan);