@actiondock/builder 2.20.0 → 2.21.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.
@@ -15,10 +15,40 @@ export const TEST_FILE_SUFFIXES = [
15
15
  ".spec.jsx",
16
16
  ".d.ts",
17
17
  ];
18
+ /**
19
+ * ActionDock 配置加密主密钥与身份标记的规范文件名(位于密钥目录 keys 内)。
20
+ *
21
+ * 命名事实源为 core/security/config-key.ts 的 resolveKeyPaths:
22
+ * 密钥文件 config.key、身份标记 config.key.id。
23
+ */
24
+ const CONFIG_KEY_FILE_NAMES = ["config.key", "config.key.id"];
25
+ /**
26
+ * 判断文件名是否为 ActionDock 配置加密密钥资产。
27
+ *
28
+ * 密钥发布采用原子硬链接,临时文件名为 .{原名}.tmp.{pid}.{uuid}
29
+ * (core/security/config-key.ts 的 publishAtomically);失败清理为尽力而为,
30
+ * 残留临时名同样可能出现在密钥目录内,须一并识别。
31
+ */
32
+ function isConfigKeyArtifactName(fileName) {
33
+ for (const name of CONFIG_KEY_FILE_NAMES) {
34
+ if (fileName === name || fileName.startsWith(`.${name}.tmp.`)) {
35
+ return true;
36
+ }
37
+ }
38
+ return false;
39
+ }
18
40
  /** 运行环境配置不得进入分发产物;普通业务 bindings.json 不受影响。 */
19
41
  export function isRuntimeConfigFile(relPath) {
20
42
  const segments = relPath.replace(/\\/g, "/").split("/");
21
- return segments.includes(".actiondock") || segments.at(-1) === "profiles.json";
43
+ const fileName = segments.at(-1) ?? "";
44
+ if (segments.includes(".actiondock") || fileName === "profiles.json") {
45
+ return true;
46
+ }
47
+ // 配置加密主密钥仅落在密钥目录 keys 内:dataDir 定位为 <dataDir>/keys,
48
+ // 主目录定位为 .actiondock/keys(后者已被上方 .actiondock 分支覆盖)。
49
+ // 仅在 keys 目录上下文下精确排除,普通业务 keys 目录内的其他文件
50
+ // (如 keys/data.json、keys/api.key)与密钥目录外的同名业务资产不受影响。
51
+ return segments.at(-2) === "keys" && isConfigKeyArtifactName(fileName);
22
52
  }
23
53
  /**
24
54
  * 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
@@ -1,6 +1,7 @@
1
+ import { type RunPackageManagerOptions } from "@actiondock/core/project";
1
2
  /**
2
3
  * 在暂存目录调用带有 --ignore-scripts 的 npm pack 生成标准 npm 压缩包(.tgz)。
3
- * 优先解析 --json 结构化输出,失败时回退到尾部行文件名解析。
4
- * Windows 兼容:npm 是 .cmd 脚本,无 shell 直接 spawn 会 ENOENT/EINVAL。
4
+ * 使用 --json=false 确保标准输出仅输出单行压缩包文件名(显式覆盖 npm_config_json 等环境配置)。
5
+ * 复用统一包管理器启动解析与驱动底座,以 shell: false 执行,完整保留路径参数与特殊字符边界。
5
6
  */
6
- export declare function runNpmPack(stagingPkgDir: string, packDestination: string, fallbackName: string): Promise<string>;
7
+ export declare function runNpmPack(stagingPkgDir: string, packDestination: string, _fallbackName?: string, options?: RunPackageManagerOptions): Promise<string>;
package/dist/npm-pack.js CHANGED
@@ -1,69 +1,47 @@
1
- import { spawn } from "node:child_process";
2
1
  import { existsSync } from "node:fs";
3
2
  import { join } from "node:path";
3
+ import { runPackageManager } from "@actiondock/core/project";
4
4
  import { BuilderError } from "./errors.js";
5
5
  /**
6
6
  * 在暂存目录调用带有 --ignore-scripts 的 npm pack 生成标准 npm 压缩包(.tgz)。
7
- * 优先解析 --json 结构化输出,失败时回退到尾部行文件名解析。
8
- * Windows 兼容:npm 是 .cmd 脚本,无 shell 直接 spawn 会 ENOENT/EINVAL。
7
+ * 使用 --json=false 确保标准输出仅输出单行压缩包文件名(显式覆盖 npm_config_json 等环境配置)。
8
+ * 复用统一包管理器启动解析与驱动底座,以 shell: false 执行,完整保留路径参数与特殊字符边界。
9
9
  */
10
- export async function runNpmPack(stagingPkgDir, packDestination, fallbackName) {
11
- const { stdout } = await new Promise((resolvePromise, rejectPromise) => {
12
- const child = spawn("npm", ["pack", "--ignore-scripts", "--json", "--pack-destination", packDestination], {
10
+ export async function runNpmPack(stagingPkgDir, packDestination, _fallbackName, options) {
11
+ const args = [
12
+ "pack",
13
+ "--ignore-scripts",
14
+ "--json=false",
15
+ "--pack-destination",
16
+ packDestination,
17
+ ];
18
+ let procResult;
19
+ try {
20
+ procResult = await runPackageManager("npm", args, {
21
+ ...options,
13
22
  cwd: stagingPkgDir,
14
- shell: process.platform === "win32",
15
23
  });
16
- let stdoutBuf = "";
17
- let stderrBuf = "";
18
- child.stdout.on("data", (chunk) => (stdoutBuf += chunk));
19
- child.stderr.on("data", (chunk) => (stderrBuf += chunk));
20
- child.on("error", rejectPromise);
21
- child.on("close", (code) => {
22
- if (code === 0) {
23
- resolvePromise({ stdout: stdoutBuf, stderr: stderrBuf });
24
- }
25
- else {
26
- rejectPromise(new BuilderError(`npm pack exited with code ${code}. Output:\n${stdoutBuf}\n${stderrBuf}`));
27
- }
28
- });
29
- });
30
- // 优先解析 --json 结构化输出中的压缩包文件名
31
- let generatedName;
32
- const trimmed = stdout.trim();
33
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
34
- try {
35
- const parsed = JSON.parse(trimmed);
36
- let items;
37
- if (Array.isArray(parsed)) {
38
- items = parsed;
39
- }
40
- else if (typeof parsed === "object" && parsed !== null) {
41
- // npm >= 11 的 pack --json 输出为按包名索引的对象,取全部包含 filename 的条目
42
- items = Object.values(parsed).filter((entry) => typeof entry === "object" && entry !== null && "filename" in entry);
43
- }
44
- else {
45
- items = [];
46
- }
47
- // 多包输出时取最后一个 filename(npm 按依赖顺序逐包输出,末尾为当前项目产物)
48
- const filenames = items
49
- .map((item) => item.filename)
50
- .filter((name) => typeof name === "string" && name.length > 0);
51
- const filename = filenames[filenames.length - 1];
52
- if (filename !== undefined) {
53
- generatedName = filename;
54
- }
55
- }
56
- catch {
57
- // 回退到尾部行解析
58
- }
59
24
  }
60
- if (!generatedName) {
61
- const packLines = trimmed
62
- .split("\n")
63
- .map((line) => line.trim())
64
- .filter((line) => line.length > 0);
65
- const tgzLine = packLines.slice().reverse().find((line) => line.endsWith(".tgz"));
66
- generatedName = tgzLine || packLines.pop() || fallbackName;
25
+ catch (err) {
26
+ throw new BuilderError(`npm pack failed to execute: ${err?.message || String(err)}`);
27
+ }
28
+ if (procResult.status !== 0) {
29
+ throw new BuilderError(`npm pack exited with code ${procResult.status}. Output:\n${procResult.stdout}\n${procResult.stderr}`);
30
+ }
31
+ const stdout = procResult.stdout;
32
+ const lines = stdout
33
+ .split(/\r?\n/)
34
+ .map((line) => line.trim())
35
+ .filter((line) => line.length > 0);
36
+ if (lines.length === 0) {
37
+ throw new BuilderError(`npm pack produced empty stdout; expected tarball filename. Output:\n${stdout}\nStderr:\n${procResult.stderr}`);
38
+ }
39
+ if (lines.length > 1) {
40
+ throw new BuilderError(`npm pack produced unexpected multi-line output; expected single tarball filename. Output:\n${stdout}`);
41
+ }
42
+ const generatedName = lines[0];
43
+ if (!generatedName.endsWith(".tgz")) {
44
+ throw new BuilderError(`npm pack produced unexpected output line '${generatedName}'; expected .tgz filename. Output:\n${stdout}`);
67
45
  }
68
46
  const generatedTarball = join(packDestination, generatedName);
69
47
  if (!existsSync(generatedTarball)) {
@@ -220,19 +220,24 @@ ${desc}`;
220
220
 
221
221
  本技能为 ActionDock 源码型技能包。智能体可直接通过宿主环境中已安装的 ActionDock 命令行工具 \`ad\` 执行其中的 Action。
222
222
 
223
- ### 安装依赖与注册链接
223
+ ### 注册链接至全局路由表
224
224
 
225
- 本技能面向全局挂载使用。在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root>\`。标准流程为先安装生产依赖,再执行注册链接,之后可在任意工作目录通过完全限定 ID 全局调用:
225
+ 本技能面向全局挂载使用。在初次调用或初始化时,将包含本 \`SKILL.md\` 的目录解析为 \`<skill_root>\`。标准流程为执行注册链接,将其登记至全局路由表:
226
226
 
227
227
  \`\`\`bash
228
- # 安装生产依赖
229
- cd "<skill_root>" && npm install --omit=dev
230
-
231
228
  # 注册并链接到 ActionDock
232
229
  ad link "<skill_root>"
233
230
  \`\`\`
234
231
 
235
- > \`ad link\` 天然具备幂等性,同一 Package 多次执行会直接更新路径,可安全重复调用。若后续依赖发生变更,可重新执行上述安装依赖与链接命令更新环境。`;
232
+ > 登记后,在任意工作目录通过完全限定 ID 本地调用 \`ad run\` 时,主动作与宿主快照中已有的本地子动作均会在实际调用前按需准备依赖。\`ad link\` 天然具备幂等性,可安全重复调用。
233
+
234
+ ### 显式手动预装依赖(可选)
235
+
236
+ 使用者明确要求预先准备环境或修复已有依赖时,按项目实际使用的包管理器手动安装生产依赖。若本次调用指定了 \`--no-install\` 或 \`ACTIONDOCK_AUTO_INSTALL=false\`,整条调用链均保持禁装策略并报告依赖未就绪,智能体不能擅自执行安装命令绕过策略。以下为 npm 项目的预装示例:
237
+
238
+ \`\`\`bash
239
+ cd "<skill_root>" && npm install --omit=dev
240
+ \`\`\``;
236
241
  const sDescribe = `### 动作参数契约按需调阅
237
242
 
238
243
  在调用未知参数的 Action 前,可在终端执行命令查阅该 Action 的输入输出模式与详细说明:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@actiondock/builder",
3
- "version": "2.20.0",
3
+ "version": "2.21.0",
4
4
  "description": "ActionDock Build Planner, distribution packaging, and Skill exporter",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,8 +29,8 @@
29
29
  "test": "node ../../scripts/run-tests.ts"
30
30
  },
31
31
  "dependencies": {
32
- "@actiondock/core": "^2.20.0",
33
- "@actiondock/sdk": "^2.20.0"
32
+ "@actiondock/core": "^2.21.0",
33
+ "@actiondock/sdk": "^2.21.0"
34
34
  },
35
35
  "keywords": [
36
36
  "actiondock",