@actiondock/builder 2.15.0 → 2.17.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 +37 -15
- package/dist/dependency-check.d.ts +1 -1
- package/dist/dependency-check.js +5 -0
- package/dist/entry-generators.d.ts +2 -9
- package/dist/entry-generators.js +21 -43
- package/dist/export-composite.d.ts +1 -1
- package/dist/export-composite.js +54 -125
- package/dist/export-source.d.ts +1 -1
- package/dist/export-source.js +8 -32
- package/dist/exporter.js +7 -36
- package/dist/planner.js +78 -74
- package/dist/skill/templates.d.ts +5 -7
- package/dist/skill/templates.js +67 -96
- package/dist/skill-md.d.ts +0 -8
- package/dist/skill-md.js +0 -46
- package/dist/types.d.ts +8 -9
- package/package.json +3 -3
- package/dist/derived-graph.d.ts +0 -4
- package/dist/derived-graph.js +0 -30
- package/dist/source-graph.d.ts +0 -4
- package/dist/source-graph.js +0 -33
package/README.md
CHANGED
|
@@ -40,21 +40,29 @@ ActionDock 2.x 构建规划、目录交付与技能导出包。
|
|
|
40
40
|
|
|
41
41
|
### SkillExporter 与技能导出
|
|
42
42
|
|
|
43
|
-
[SkillExporter](./src/exporter.ts)
|
|
43
|
+
[SkillExporter](./src/exporter.ts)、`exportSkill` 以及 `exportCompositeSkill` 导出函数将原子 Action 与操作规程 Playbook 打包导出为面向主流 AI 智能体生态的标准 Agent Skill 资产:
|
|
44
44
|
|
|
45
|
-
-
|
|
46
|
-
-
|
|
45
|
+
- 默认单包浅导出机制:默认采用单包源码浅闭包导出,仅复制当前包自身源码与规程,产物清单完整保留 `uses` 跨包逻辑契约,不物理拷贝外部包源码,单包结构纯粹透明,天然支持配合 `playbooks` 或 `actions` 执行规程驱动剪枝。
|
|
46
|
+
- 显式外部依赖物化:支持 `bundleDeps` 选项。当显式开启且依赖闭包跨越外部已链接包时,自顶向下递归解析依赖链并将外部依赖包物理物化进产物根目录的 `packages/*` 子目录,展开为自包含的微型工作区产物结构;展开为复合形态时与过滤参数严格互斥(抛出 `FILTERS_UNSUPPORTED_FOR_COMPOSITE` 错误)。
|
|
47
|
+
- 独立自包含 Node 目录型导出:支持 `standalone` 选项(等价于 `mode: "node"`),生成免全局 `ad` 命令行工具的自包含目录,内置独立运行脚本与说明书,配合 `vendorDeps` 可实现生产依赖完全内置。
|
|
47
48
|
- 本地依赖完整性强校验:严格断言 Action 源码中的相对路径导入。若引用了未在 `actions` 且未在 `files` 字段中声明的本地模块,直接中断构建并抛出 `BuilderError`(错误码 `UNMET_LOCAL_DEPENDENCY`),彻底杜绝缺失依赖的产物分发。
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
- 压缩归档:支持
|
|
49
|
+
- 生成 SKILL.md 指令:提取 Playbook 规程内容与工具元数据,合成符合智能体规范的操作指南文档与声明头部。
|
|
50
|
+
- 复合套件导出与说明书定制:支持 `exportCompositeSkill` 函数将工作区内的多个包统一导出为复合工作区技能套件;支持通过 `customMdPath` 注入插槽定制模板,通过 `skillMdOnly` 实现就地仅原位刷新说明书。
|
|
51
|
+
- 压缩归档:支持 `archive` 选项将导出的技能目录打包为 `.zip` 或 `.tar.gz` 格式。
|
|
51
52
|
|
|
52
53
|
---
|
|
53
54
|
|
|
54
55
|
## 编程调用示例
|
|
55
56
|
|
|
56
57
|
```ts
|
|
57
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
buildProject,
|
|
60
|
+
packProject,
|
|
61
|
+
SelectionPlanner,
|
|
62
|
+
SkillExporter,
|
|
63
|
+
exportSkill,
|
|
64
|
+
exportCompositeSkill,
|
|
65
|
+
} from "@actiondock/builder";
|
|
58
66
|
|
|
59
67
|
// 计算依赖规划闭包
|
|
60
68
|
const planner = new SelectionPlanner({ projectRoot: "/path/to/project" });
|
|
@@ -73,20 +81,34 @@ const packResult = await packProject({
|
|
|
73
81
|
outDir: "./dist/npm",
|
|
74
82
|
});
|
|
75
83
|
|
|
76
|
-
//
|
|
84
|
+
// 默认源码单包浅导出(支持规程定向裁剪)
|
|
77
85
|
const skillResult = await exportSkill({
|
|
78
86
|
projectRoot: "/path/to/project",
|
|
79
|
-
outDir: "./dist/skills",
|
|
87
|
+
outDir: "./dist/skills/review-pr",
|
|
80
88
|
playbooks: ["review-pr"],
|
|
81
|
-
mode: "source",
|
|
82
89
|
});
|
|
83
90
|
|
|
84
|
-
//
|
|
85
|
-
const
|
|
86
|
-
const customResult = await exporter.export({
|
|
91
|
+
// 显式物化外部依赖导出(展开为微型工作区)
|
|
92
|
+
const bundledResult = await exportSkill({
|
|
87
93
|
projectRoot: "/path/to/project",
|
|
88
|
-
outDir: "./dist/skills
|
|
89
|
-
|
|
94
|
+
outDir: "./dist/skills/bundled",
|
|
95
|
+
bundleDeps: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// 独立自包含 Node.js 目录型导出(免全局 ad 命令行,物化生产依赖)
|
|
99
|
+
const standaloneResult = await exportSkill({
|
|
100
|
+
projectRoot: "/path/to/project",
|
|
101
|
+
outDir: "./dist/skills/standalone",
|
|
102
|
+
standalone: true,
|
|
103
|
+
vendorDeps: true,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// 复合套件聚合导出与就地原位刷新说明书
|
|
107
|
+
const compositeResult = await exportCompositeSkill({
|
|
108
|
+
bundleName: "vip-agent-tools",
|
|
109
|
+
projectRoots: ["/path/to/pkg-a", "/path/to/pkg-b"],
|
|
110
|
+
outDir: "./dist/composite-skill",
|
|
111
|
+
customMdPath: "/path/to/SKILL.custom.md",
|
|
90
112
|
});
|
|
91
113
|
```
|
|
92
114
|
|
package/dist/dependency-check.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
|
2
2
|
import { dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { isPathOutsideBoundary } from "@actiondock/core/project";
|
|
4
4
|
import { BuilderError } from "./errors.js";
|
|
5
|
+
import { isOwnAction } from "./types.js";
|
|
5
6
|
/**
|
|
6
7
|
* 移除源码中的单行与多行注释,防止注释内的相对路径字符串误触发依赖解析。
|
|
7
8
|
*/
|
|
@@ -130,6 +131,8 @@ export function assertRelativeDependenciesIntegrity(projectRoot, plan) {
|
|
|
130
131
|
// 收集已包含在构建规划中的全部文件物理绝对路径
|
|
131
132
|
const collectedPaths = new Set();
|
|
132
133
|
for (const act of plan.actions) {
|
|
134
|
+
if (!isOwnAction(act))
|
|
135
|
+
continue;
|
|
133
136
|
if (act.resolvedPath) {
|
|
134
137
|
collectedPaths.add(toRealPath(act.resolvedPath));
|
|
135
138
|
}
|
|
@@ -142,6 +145,8 @@ export function assertRelativeDependenciesIntegrity(projectRoot, plan) {
|
|
|
142
145
|
// 待校验文件队列(包括全部 Action 及其收集的 TS/JS 模块)
|
|
143
146
|
const queue = [];
|
|
144
147
|
for (const act of plan.actions) {
|
|
148
|
+
if (!isOwnAction(act))
|
|
149
|
+
continue;
|
|
145
150
|
if (act.resolvedPath && existsSync(act.resolvedPath)) {
|
|
146
151
|
queue.push({ file: toRealPath(act.resolvedPath), actionId: act.id });
|
|
147
152
|
}
|
|
@@ -8,15 +8,8 @@ import type { SelectionPlan } from "./types.js";
|
|
|
8
8
|
* 按需加载业务实现;跨包外部 Action 不混入本包源码,依赖调用继续经 Host 路由;
|
|
9
9
|
* - 入口路径以产物包根目录为基准(import.meta.dirname),不依赖调用者当前工作目录。
|
|
10
10
|
*/
|
|
11
|
-
export declare function generateNodeHostEntrySource(plan: SelectionPlan
|
|
12
|
-
localPackages: Array<{
|
|
13
|
-
packageId: string;
|
|
14
|
-
relativeRoot: string;
|
|
15
|
-
}>;
|
|
16
|
-
}): string;
|
|
11
|
+
export declare function generateNodeHostEntrySource(plan: SelectionPlan): string;
|
|
17
12
|
/**
|
|
18
13
|
* 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
|
|
19
14
|
*/
|
|
20
|
-
export declare function generateNodeSupervisorEntrySource(plan:
|
|
21
|
-
multiPackage?: boolean;
|
|
22
|
-
}): string;
|
|
15
|
+
export declare function generateNodeSupervisorEntrySource(plan: SelectionPlan): string;
|
package/dist/entry-generators.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { STANDALONE_ASYNC_UNSUPPORTED } from "@actiondock/core";
|
|
2
|
-
import { BuilderError } from "./errors.js";
|
|
3
2
|
import { serializePlanManifest } from "./manifest.js";
|
|
4
3
|
/**
|
|
5
4
|
* 生成 Host 子进程入口脚本源码(负责运行 ActionDockHost,通过 Node IPC 暴露 Target)。
|
|
@@ -10,49 +9,24 @@ import { serializePlanManifest } from "./manifest.js";
|
|
|
10
9
|
* 按需加载业务实现;跨包外部 Action 不混入本包源码,依赖调用继续经 Host 路由;
|
|
11
10
|
* - 入口路径以产物包根目录为基准(import.meta.dirname),不依赖调用者当前工作目录。
|
|
12
11
|
*/
|
|
13
|
-
export function generateNodeHostEntrySource(plan
|
|
12
|
+
export function generateNodeHostEntrySource(plan) {
|
|
14
13
|
// 复用 SelectionPlan 的清单序列化单一事实源(含包内动作筛选与 Schema 字段映射),
|
|
15
14
|
// 不单独复制一份 Schema 字段映射
|
|
16
|
-
|
|
17
|
-
throw new BuilderError("A plan or explicit local packages are required for the Host entry");
|
|
18
|
-
const manifestActions = options ? undefined : serializePlanManifest(plan).actions;
|
|
19
|
-
const packageEntries = options
|
|
20
|
-
? options.localPackages.map((pkg) => `{
|
|
21
|
-
packageRoot: resolve(packageRoot, ${JSON.stringify(pkg.relativeRoot)}),
|
|
22
|
-
projectConfig: loadLocalConfig(${JSON.stringify(pkg.packageId)}, ${JSON.stringify(pkg.relativeRoot)}),
|
|
23
|
-
dataDir,
|
|
24
|
-
configOverrides,
|
|
25
|
-
}`).join(",\n")
|
|
26
|
-
: `{
|
|
27
|
-
packageRoot, dataDir, configOverrides,
|
|
28
|
-
projectConfig: ${JSON.stringify({
|
|
29
|
-
id: plan.packageId, name: plan.packageName, version: plan.version,
|
|
30
|
-
description: plan.description || "", config: plan.configDefs || {}, actions: manifestActions,
|
|
31
|
-
})},
|
|
32
|
-
}`;
|
|
15
|
+
const manifestActions = serializePlanManifest(plan).actions;
|
|
33
16
|
return `#!/usr/bin/env node
|
|
34
17
|
// AUTO-GENERATED HOST ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
|
|
35
|
-
import { resolve } from "node:path";
|
|
36
18
|
import {
|
|
37
|
-
loadProjectConfig,
|
|
38
19
|
createActionDock,
|
|
39
20
|
createNodePlatform,
|
|
40
|
-
validateLocalSkillPackages,
|
|
41
21
|
} from "@actiondock/core";
|
|
42
22
|
import { serveParentIpc } from "@actiondock/core/server";
|
|
43
23
|
|
|
44
24
|
// 产物包根目录:以入口文件自身位置为基准,不依赖调用者当前工作目录
|
|
45
25
|
const packageRoot = import.meta.dirname;
|
|
46
|
-
${options ? `function loadLocalConfig(expectedId, relativeRoot) {
|
|
47
|
-
const config = loadProjectConfig(resolve(packageRoot, relativeRoot));
|
|
48
|
-
if (config.id !== expectedId) throw new Error("Local package ID mismatch: expected " + expectedId + ", found " + config.id);
|
|
49
|
-
return config;
|
|
50
|
-
}` : ""}
|
|
51
26
|
|
|
52
27
|
let dataDir;
|
|
53
28
|
const configOverrides = {};
|
|
54
29
|
const args = process.argv.slice(2);
|
|
55
|
-
const commandArgs = [];
|
|
56
30
|
for (let i = 0; i < args.length; i++) {
|
|
57
31
|
if (args[i] === "--data-dir" && i + 1 < args.length) {
|
|
58
32
|
dataDir = args[++i];
|
|
@@ -66,28 +40,33 @@ for (let i = 0; i < args.length; i++) {
|
|
|
66
40
|
const raw = args[i].slice(9);
|
|
67
41
|
const [k, ...v] = raw.split("=");
|
|
68
42
|
if (k) configOverrides[k] = v.join("=");
|
|
69
|
-
} else {
|
|
70
|
-
commandArgs.push(args[i]);
|
|
71
43
|
}
|
|
72
44
|
}
|
|
73
45
|
|
|
74
46
|
// 复用既有命令分发结果确定访问性质:发现命令(list/describe/show)为旁观初始化,
|
|
75
47
|
// 不创建数据库、不取目录锁、不收割遗留运行记录;执行与写入命令为持有者初始化
|
|
76
|
-
const command =
|
|
48
|
+
const command = args.find((a) => !a.startsWith("-")) || "";
|
|
77
49
|
const DISCOVERY_COMMANDS = new Set(["list", "describe", "show"]);
|
|
78
50
|
const recoverOrphans = DISCOVERY_COMMANDS.has(command) ? false : undefined;
|
|
79
51
|
|
|
80
|
-
const localPackages = [${packageEntries}];
|
|
81
|
-
validateLocalSkillPackages(localPackages.map((p) => ({
|
|
82
|
-
packageId: p.projectConfig.id,
|
|
83
|
-
packageRoot: p.packageRoot,
|
|
84
|
-
projectConfig: p.projectConfig,
|
|
85
|
-
})));
|
|
86
|
-
|
|
87
52
|
const service = await createActionDock({
|
|
88
53
|
dataDir,
|
|
89
54
|
...(recoverOrphans === false ? { recoverOrphans: false } : {}),
|
|
90
|
-
packages:
|
|
55
|
+
packages: [
|
|
56
|
+
{
|
|
57
|
+
packageRoot,
|
|
58
|
+
dataDir,
|
|
59
|
+
configOverrides,
|
|
60
|
+
projectConfig: {
|
|
61
|
+
id: ${JSON.stringify(plan.packageId)},
|
|
62
|
+
name: ${JSON.stringify(plan.packageName)},
|
|
63
|
+
version: ${JSON.stringify(plan.version)},
|
|
64
|
+
description: ${JSON.stringify(plan.description || "")},
|
|
65
|
+
config: ${JSON.stringify(plan.configDefs || {})},
|
|
66
|
+
actions: ${JSON.stringify(manifestActions)},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
91
70
|
platform: createNodePlatform({ dataDir }),
|
|
92
71
|
autoLoadCurrentProject: false,
|
|
93
72
|
scanLinkedPackages: false,
|
|
@@ -98,7 +77,7 @@ await serveParentIpc(service);
|
|
|
98
77
|
/**
|
|
99
78
|
* 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
|
|
100
79
|
*/
|
|
101
|
-
export function generateNodeSupervisorEntrySource(plan
|
|
80
|
+
export function generateNodeSupervisorEntrySource(plan) {
|
|
102
81
|
return `#!/usr/bin/env node
|
|
103
82
|
// AUTO-GENERATED SUPERVISOR ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
|
|
104
83
|
import { spawn } from "node:child_process";
|
|
@@ -162,8 +141,8 @@ if (argv.includes("-h") || argv.includes("--help") || argv[0] === "help") {
|
|
|
162
141
|
console.log(" <cmd> run <id> [--input '<json>'] Execute action with JSON input");
|
|
163
142
|
console.log(" <cmd> run <id> [--stdin-field <field>] [-- <assignments...>]");
|
|
164
143
|
console.log(" Bind raw stdin text to an input field");
|
|
165
|
-
|
|
166
|
-
|
|
144
|
+
console.log(" <cmd> config list/get/set/delete Manage package configuration");
|
|
145
|
+
console.log(" <cmd> state list/get/set/delete Manage shared state store");
|
|
167
146
|
console.log("\\nGlobal options:");
|
|
168
147
|
console.log(" --data-dir <path> Custom runtime database directory");
|
|
169
148
|
console.log(" --config <KEY=val> Temporary config override");
|
|
@@ -212,7 +191,6 @@ const dispatcher = new StandaloneDispatcher({
|
|
|
212
191
|
version: METADATA.version,
|
|
213
192
|
description: METADATA.description,
|
|
214
193
|
service,
|
|
215
|
-
multiPackage: ${JSON.stringify(options?.multiPackage === true)},
|
|
216
194
|
});
|
|
217
195
|
|
|
218
196
|
try {
|
|
@@ -3,7 +3,7 @@ import type { CompositeSkillExportOptions, CompositeSkillExportResult, SkillExpo
|
|
|
3
3
|
* 复合套件导出装配层。
|
|
4
4
|
*
|
|
5
5
|
* 职责:将多个包聚合为一个统一的复合技能目录(mini-workspace 形态),
|
|
6
|
-
*
|
|
6
|
+
* 聚合各子包依赖生成复合根 package.json,并生成或复用复合 SKILL.md。
|
|
7
7
|
*/
|
|
8
8
|
/** 子包导出回调:由 SkillExporter 注入自身导出能力,避免模块间循环依赖 */
|
|
9
9
|
export type SubpackageExporter = (options: SkillExporterOptions) => Promise<SkillExportResult>;
|
package/dist/export-composite.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { loadProjectConfig, } from "@actiondock/core";
|
|
4
4
|
import { getPackageSlug, loadManifest, loadPlaybooks, } from "@actiondock/core/project";
|
|
@@ -6,10 +6,9 @@ import { generateCompositeSkillMd, RUNTIME_REFERENCE_REL_PATH } from "./skill/in
|
|
|
6
6
|
import { writeRuntimeReferenceFile } from "./export-source.js";
|
|
7
7
|
import { BuilderError } from "./errors.js";
|
|
8
8
|
import { collectRelativeFiles, getInternalDependencyVersion, replaceDirAtomic } from "./fs-utils.js";
|
|
9
|
-
import { allocatePackageDirName } from "./manifest.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { findExistingCompositeSkillMd, resolveCustomSkillDeclaration, appendLocalExecutionContract, } from "./skill-md.js";
|
|
9
|
+
import { allocatePackageDirName, sanitizeExportDependencies } from "./manifest.js";
|
|
10
|
+
import { SelectionPlanner } from "./planner.js";
|
|
11
|
+
import { findExistingCompositeSkillMd, resolveCustomSkillDeclaration, } from "./skill-md.js";
|
|
13
12
|
import { createArchive, resolveArchiveFormat } from "./export-archive.js";
|
|
14
13
|
/**
|
|
15
14
|
* 执行复合模式导出:将多个包聚合为一个统一的复合技能目录。
|
|
@@ -21,6 +20,9 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
21
20
|
if (!options.bundleName || !options.bundleName.trim()) {
|
|
22
21
|
throw new BuilderError("bundleName is required for composite skill export.");
|
|
23
22
|
}
|
|
23
|
+
if (options.standalone) {
|
|
24
|
+
throw new BuilderError("Composite Skill export currently only supports source mode. Standalone mode is not supported for composite bundles.", "UNSUPPORTED_BUILD_MODE");
|
|
25
|
+
}
|
|
24
26
|
const skillMdOnly = options.skillMdOnly === true;
|
|
25
27
|
const customDeclaration = resolveCustomSkillDeclaration(options);
|
|
26
28
|
const bundleSlug = getPackageSlug(options.bundleName);
|
|
@@ -39,36 +41,30 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
39
41
|
mkdirSync(packagesDestDir, { recursive: true });
|
|
40
42
|
}
|
|
41
43
|
const packageInfos = [];
|
|
42
|
-
const localPackages = [];
|
|
43
44
|
const packageSummaries = [];
|
|
44
45
|
let existingSkillPath;
|
|
45
46
|
try {
|
|
46
|
-
|
|
47
|
-
const effectiveRoots =
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const name = existsSync(pkgJsonPath) ? readWorkspacePackageName(pkgJsonPath) : undefined;
|
|
58
|
-
sourceNpmNames.set(projectRoot, name);
|
|
59
|
-
if (name) {
|
|
60
|
-
if (usedNpmNames.has(name)) {
|
|
61
|
-
throw new BuilderError(`Duplicate npm workspace package name '${name}'.`, "DUPLICATE_WORKSPACE_PACKAGE_NAME");
|
|
47
|
+
// 收集项目根目录;仅在显式指定 bundleDeps 时展开依赖闭包中的外部项目根目录
|
|
48
|
+
const effectiveRoots = new Set();
|
|
49
|
+
for (const r of options.projectRoots) {
|
|
50
|
+
effectiveRoots.add(resolve(r));
|
|
51
|
+
if (!skillMdOnly && options.bundleDeps === true) {
|
|
52
|
+
try {
|
|
53
|
+
const p = SelectionPlanner.plan({ projectRoot: r });
|
|
54
|
+
if (p.externalProjectRoots) {
|
|
55
|
+
for (const extRoot of p.externalProjectRoots) {
|
|
56
|
+
effectiveRoots.add(resolve(extRoot));
|
|
57
|
+
}
|
|
62
58
|
}
|
|
63
|
-
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// 忽略规划解析异常,由主循环处理
|
|
64
62
|
}
|
|
65
63
|
}
|
|
66
64
|
}
|
|
65
|
+
const usedDirNames = new Set();
|
|
67
66
|
for (const projectRoot of effectiveRoots) {
|
|
68
|
-
const
|
|
69
|
-
const config = graphManifest
|
|
70
|
-
? { ...graphManifest, name: graphManifest.name || graphManifest.id, version: graphManifest.version || "0.1.0" }
|
|
71
|
-
: loadProjectConfig(projectRoot);
|
|
67
|
+
const config = loadProjectConfig(projectRoot);
|
|
72
68
|
let pkgSlug;
|
|
73
69
|
if (skillMdOnly) {
|
|
74
70
|
// 就地生成:规程链接基于工作区实际子目录名
|
|
@@ -78,49 +74,34 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
78
74
|
// 目录名分配统一复用 manifest 的 allocatePackageDirName,回退后仍冲突会追加数字后缀
|
|
79
75
|
pkgSlug = allocatePackageDirName(usedDirNames, config.id);
|
|
80
76
|
}
|
|
81
|
-
let defaultPackageName;
|
|
82
|
-
if (!skillMdOnly && !sourceNpmNames.get(projectRoot)) {
|
|
83
|
-
const baseName = normalizeNpmPackageName(pkgSlug);
|
|
84
|
-
let candidate = baseName;
|
|
85
|
-
let suffix = 2;
|
|
86
|
-
while (usedNpmNames.has(candidate))
|
|
87
|
-
candidate = `${baseName}-${suffix++}`;
|
|
88
|
-
defaultPackageName = candidate;
|
|
89
|
-
usedNpmNames.add(candidate);
|
|
90
|
-
}
|
|
91
77
|
let actionIds;
|
|
92
|
-
let
|
|
78
|
+
let playbookIds;
|
|
93
79
|
let manifest;
|
|
94
80
|
if (skillMdOnly) {
|
|
95
81
|
// 仅生成 SKILL.md:直接读取清单获取 Action 契约描述,不拷贝子包产物
|
|
96
82
|
manifest = loadManifest(projectRoot);
|
|
97
83
|
actionIds = manifest?.actions ? Object.keys(manifest.actions) : [];
|
|
98
|
-
|
|
84
|
+
playbookIds = Array.from(loadPlaybooks(projectRoot, config.playbooksDir).keys());
|
|
99
85
|
}
|
|
100
86
|
else {
|
|
101
87
|
const destPkgDir = join(packagesDestDir, pkgSlug);
|
|
102
88
|
const singleExport = await exportSubpackage({
|
|
103
89
|
projectRoot,
|
|
104
|
-
packageGraph,
|
|
105
|
-
config,
|
|
106
|
-
manifest: graphManifest || undefined,
|
|
107
90
|
outDir: destPkgDir,
|
|
108
91
|
archive: false,
|
|
109
92
|
skipSkillMd: true,
|
|
110
93
|
mode: "source",
|
|
111
94
|
_isSubpackage: true,
|
|
112
|
-
defaultPackageName,
|
|
113
95
|
});
|
|
114
|
-
manifest = loadManifest(destPkgDir);
|
|
96
|
+
manifest = loadManifest(destPkgDir) || loadManifest(projectRoot);
|
|
115
97
|
actionIds = singleExport.actions;
|
|
116
|
-
|
|
98
|
+
playbookIds = singleExport.playbooks;
|
|
117
99
|
}
|
|
118
|
-
const
|
|
100
|
+
const playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
|
|
119
101
|
const actionEntries = actionIds.map((actId) => ({
|
|
120
102
|
id: actId,
|
|
121
103
|
description: manifest?.actions?.[actId]?.description,
|
|
122
104
|
}));
|
|
123
|
-
localPackages.push({ packageId: config.id, relativeRoot: `packages/${pkgSlug}` });
|
|
124
105
|
packageInfos.push({
|
|
125
106
|
config,
|
|
126
107
|
actions: actionEntries,
|
|
@@ -140,7 +121,6 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
140
121
|
const compositeSkillMd = generateCompositeSkillMd(options.bundleName, description, packageInfos, {
|
|
141
122
|
customSections: customDeclaration?.sections,
|
|
142
123
|
packagesBaseDir: skillMdOnly ? "." : "packages",
|
|
143
|
-
invocationStyle: skillMdOnly ? "global-ad" : "source-ad",
|
|
144
124
|
});
|
|
145
125
|
if (skillMdOnly) {
|
|
146
126
|
// 就地仅重生成 SKILL.md:始终重新生成(忽略已有 SKILL.md 复用逻辑),不产出完整套件目录
|
|
@@ -176,10 +156,9 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
176
156
|
}
|
|
177
157
|
if (existingSkillPath) {
|
|
178
158
|
const destSkillMdPath = join(stagingDir, "SKILL.md");
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
})));
|
|
159
|
+
if (resolve(existingSkillPath) !== resolve(destSkillMdPath)) {
|
|
160
|
+
copyFileSync(existingSkillPath, destSkillMdPath);
|
|
161
|
+
}
|
|
183
162
|
}
|
|
184
163
|
else {
|
|
185
164
|
writeFileSync(join(stagingDir, "SKILL.md"), compositeSkillMd, "utf-8");
|
|
@@ -188,19 +167,11 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
188
167
|
dependencyStepLabel: "安装复合技能聚合依赖",
|
|
189
168
|
relinkStepLabel: "完成安装后重新链接复合技能",
|
|
190
169
|
compositeLinkNote: true,
|
|
191
|
-
invocationStyle: "
|
|
192
|
-
entryRelPath: 'node "<skill_root>/entry.mjs"',
|
|
170
|
+
invocationStyle: "global-ad",
|
|
193
171
|
});
|
|
194
172
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
// npm workspaces 保留各包依赖约束,根目录仅声明入口运行依赖。
|
|
199
|
-
writeCompositePkgJson(stagingDir, bundleSlug, options);
|
|
200
|
-
const entryMetadata = { packageId: options.bundleName, version: "0.1.0", description };
|
|
201
|
-
writeFileSync(join(stagingDir, "entry.mjs"), generateNodeSupervisorEntrySource(entryMetadata, { multiPackage: true }), "utf-8");
|
|
202
|
-
// Host 生成仍复用单包生成器,但源码包元数据只从自身相对路径读取。
|
|
203
|
-
writeFileSync(join(stagingDir, "entry-host.js"), generateNodeHostEntrySource(undefined, { localPackages }), "utf-8");
|
|
173
|
+
// 聚合所有子包依赖生成复合根目录 package.json
|
|
174
|
+
writeCompositePkgJson(stagingDir, packagesDestDir, packageInfos, bundleSlug, options);
|
|
204
175
|
// 原子替换目标复合技能目录
|
|
205
176
|
await replaceDirAtomic(stagingDir, targetSkillDir);
|
|
206
177
|
}
|
|
@@ -227,77 +198,35 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
227
198
|
};
|
|
228
199
|
}
|
|
229
200
|
/**
|
|
230
|
-
*
|
|
201
|
+
* 复合套件聚合各子包依赖并生成复合根目录 package.json。
|
|
202
|
+
* 依赖清洗统一复用 manifest 的 sanitizeExportDependencies 单一入口,与单包导出口径一致。
|
|
231
203
|
*/
|
|
232
|
-
function writeCompositePkgJson(stagingDir, bundleSlug, options) {
|
|
233
|
-
|
|
234
|
-
const runtimeDeps = {
|
|
235
|
-
"@actiondock/core": getInternalDependencyVersion(),
|
|
204
|
+
function writeCompositePkgJson(stagingDir, packagesDestDir, packageInfos, bundleSlug, options) {
|
|
205
|
+
const aggregatedDeps = {
|
|
236
206
|
"@actiondock/sdk": getInternalDependencyVersion(),
|
|
237
207
|
};
|
|
208
|
+
for (const info of packageInfos) {
|
|
209
|
+
const pkgJsonPath = join(packagesDestDir, info.packageDir, "package.json");
|
|
210
|
+
if (existsSync(pkgJsonPath)) {
|
|
211
|
+
try {
|
|
212
|
+
const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
213
|
+
Object.assign(aggregatedDeps, sanitizeExportDependencies(join(packagesDestDir, info.packageDir), parsed.dependencies, "exported skill packages", false));
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
if (err instanceof BuilderError)
|
|
217
|
+
throw err;
|
|
218
|
+
// 复合导出缺依赖影响面大:读文件与解析失败必须显式报错,严禁静默吞掉导致聚合依赖缺失
|
|
219
|
+
throw new BuilderError(`Failed to read or parse package.json of exported subpackage '${info.packageDir}' (${pkgJsonPath}): ${err instanceof Error ? err.message : String(err)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
238
223
|
const compositePkg = {
|
|
239
224
|
name: `${bundleSlug}-composite-skill`,
|
|
240
225
|
version: "0.1.0",
|
|
241
226
|
description: options.description || `Composite Skill suite: ${options.bundleName}`,
|
|
242
227
|
type: "module",
|
|
243
228
|
workspaces: ["packages/*"],
|
|
244
|
-
dependencies:
|
|
229
|
+
dependencies: aggregatedDeps,
|
|
245
230
|
};
|
|
246
231
|
writeFileSync(join(stagingDir, "package.json"), JSON.stringify(compositePkg, null, 2) + "\n", "utf-8");
|
|
247
232
|
}
|
|
248
|
-
/**
|
|
249
|
-
* 规范化包目录标识为合法 npm 包名(小写字母、数字、连字符、下划线与点号)。
|
|
250
|
-
*/
|
|
251
|
-
function normalizeNpmPackageName(name) {
|
|
252
|
-
const normalized = name
|
|
253
|
-
.trim()
|
|
254
|
-
.toLowerCase()
|
|
255
|
-
.replace(/[^a-z0-9-_.]/g, "-")
|
|
256
|
-
.replace(/^[._]+/, "");
|
|
257
|
-
return normalized || "package";
|
|
258
|
-
}
|
|
259
|
-
/** 工作区清单读取失败必须透明报告,只有缺省 name 才允许自动分配。 */
|
|
260
|
-
function readWorkspacePackageName(path) {
|
|
261
|
-
const raw = readFileSync(path, "utf-8");
|
|
262
|
-
let parsed;
|
|
263
|
-
try {
|
|
264
|
-
parsed = JSON.parse(raw);
|
|
265
|
-
}
|
|
266
|
-
catch (err) {
|
|
267
|
-
throw new BuilderError(`Invalid package.json at '${path}': ${String(err)}`, "INVALID_PACKAGE_JSON");
|
|
268
|
-
}
|
|
269
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
270
|
-
throw new BuilderError(`Invalid package.json at '${path}': expected an object.`, "INVALID_PACKAGE_JSON");
|
|
271
|
-
}
|
|
272
|
-
const name = parsed.name;
|
|
273
|
-
if (name === undefined || name === "")
|
|
274
|
-
return undefined;
|
|
275
|
-
if (typeof name !== "string" || !name.trim() || name !== name.trim()) {
|
|
276
|
-
throw new BuilderError(`Invalid npm workspace package name at '${path}'.`, "INVALID_PACKAGE_JSON");
|
|
277
|
-
}
|
|
278
|
-
return name;
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* 校验复合暂存目录中所有子包导出的 package.json.name 唯一性。
|
|
282
|
-
* 若已有真实 npm 名确实冲突,在发布产物前明确抛出 BuilderError。
|
|
283
|
-
*/
|
|
284
|
-
function assertUniqueWorkspacePackageNames(packagesDir) {
|
|
285
|
-
const seenNames = new Map();
|
|
286
|
-
const entries = readdirSync(packagesDir, { withFileTypes: true });
|
|
287
|
-
for (const entry of entries) {
|
|
288
|
-
if (!entry.isDirectory())
|
|
289
|
-
continue;
|
|
290
|
-
const pkgJsonPath = join(packagesDir, entry.name, "package.json");
|
|
291
|
-
if (!existsSync(pkgJsonPath))
|
|
292
|
-
continue;
|
|
293
|
-
const name = readWorkspacePackageName(pkgJsonPath);
|
|
294
|
-
if (!name) {
|
|
295
|
-
throw new BuilderError(`Missing npm workspace package name at '${pkgJsonPath}'.`, "INVALID_PACKAGE_JSON");
|
|
296
|
-
}
|
|
297
|
-
const existing = seenNames.get(name);
|
|
298
|
-
if (existing) {
|
|
299
|
-
throw new BuilderError(`Duplicate npm workspace package name '${name}' found in packages/${existing} and packages/${entry.name}.`, "DUPLICATE_WORKSPACE_PACKAGE_NAME");
|
|
300
|
-
}
|
|
301
|
-
seenNames.set(name, entry.name);
|
|
302
|
-
}
|
|
303
|
-
}
|
package/dist/export-source.d.ts
CHANGED