@actiondock/builder 2.13.0 → 2.15.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/derived-graph.d.ts +4 -0
- package/dist/derived-graph.js +30 -0
- package/dist/entry-generators.d.ts +9 -2
- package/dist/entry-generators.js +44 -25
- package/dist/export-composite.d.ts +1 -1
- package/dist/export-composite.js +125 -51
- package/dist/export-source.js +32 -7
- package/dist/exporter.js +33 -3
- package/dist/planner.js +74 -78
- package/dist/skill/templates.d.ts +7 -5
- package/dist/skill/templates.js +96 -67
- package/dist/skill-md.d.ts +8 -0
- package/dist/skill-md.js +46 -0
- package/dist/source-graph.d.ts +4 -0
- package/dist/source-graph.js +33 -0
- package/dist/types.d.ts +9 -0
- package/package.json +3 -3
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ActionDockManifest, type PlaybookSourceManifest } from "@actiondock/core/project";
|
|
2
|
+
import { type PackageGraph } from "@actiondock/core/graph";
|
|
3
|
+
/** 仅从共享包集合派生有效清单图,不修改调用方图或重新发现外部包。 */
|
|
4
|
+
export declare function deriveEffectivePackageGraph(baseGraph: PackageGraph, root: string, effectiveManifest?: ActionDockManifest | PlaybookSourceManifest): PackageGraph;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { computeManifestDigest } from "@actiondock/core/project";
|
|
3
|
+
import { PackageGraphBuilder } from "@actiondock/core/graph";
|
|
4
|
+
import { BuilderError } from "./errors.js";
|
|
5
|
+
/** 仅从共享包集合派生有效清单图,不修改调用方图或重新发现外部包。 */
|
|
6
|
+
export function deriveEffectivePackageGraph(baseGraph, root, effectiveManifest) {
|
|
7
|
+
const packages = Array.from(baseGraph.packages.values());
|
|
8
|
+
const target = packages.find((pkg) => resolve(pkg.root) === resolve(root));
|
|
9
|
+
if (!target) {
|
|
10
|
+
throw new BuilderError(`Project root '${root}' not found in provided packageGraph.`, "PACKAGE_NOT_FOUND");
|
|
11
|
+
}
|
|
12
|
+
if (!effectiveManifest)
|
|
13
|
+
return baseGraph;
|
|
14
|
+
if (effectiveManifest.id !== target.packageId) {
|
|
15
|
+
throw new BuilderError(`Cannot change package identity from '${target.packageId}' to '${effectiveManifest.id}' in shared package graph.`, "CANNOT_CHANGE_PACKAGE_IDENTITY");
|
|
16
|
+
}
|
|
17
|
+
// 合并配置可能仅创建等价对象;相同声明继续复用共享图。
|
|
18
|
+
if (computeManifestDigest(effectiveManifest) === target.manifestDigest)
|
|
19
|
+
return baseGraph;
|
|
20
|
+
return new PackageGraphBuilder({
|
|
21
|
+
packages: packages.map((pkg) => ({
|
|
22
|
+
id: pkg.packageId,
|
|
23
|
+
root: pkg.root,
|
|
24
|
+
manifest: pkg === target ? effectiveManifest : pkg.manifest,
|
|
25
|
+
isCurrentProject: pkg === target,
|
|
26
|
+
})),
|
|
27
|
+
root: target.root,
|
|
28
|
+
validateDependencies: true,
|
|
29
|
+
}).buildSync();
|
|
30
|
+
}
|
|
@@ -8,8 +8,15 @@ import type { SelectionPlan } from "./types.js";
|
|
|
8
8
|
* 按需加载业务实现;跨包外部 Action 不混入本包源码,依赖调用继续经 Host 路由;
|
|
9
9
|
* - 入口路径以产物包根目录为基准(import.meta.dirname),不依赖调用者当前工作目录。
|
|
10
10
|
*/
|
|
11
|
-
export declare function generateNodeHostEntrySource(plan: SelectionPlan
|
|
11
|
+
export declare function generateNodeHostEntrySource(plan: SelectionPlan | undefined, options?: {
|
|
12
|
+
localPackages: Array<{
|
|
13
|
+
packageId: string;
|
|
14
|
+
relativeRoot: string;
|
|
15
|
+
}>;
|
|
16
|
+
}): string;
|
|
12
17
|
/**
|
|
13
18
|
* 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
|
|
14
19
|
*/
|
|
15
|
-
export declare function generateNodeSupervisorEntrySource(plan: SelectionPlan
|
|
20
|
+
export declare function generateNodeSupervisorEntrySource(plan: Pick<SelectionPlan, "packageId" | "version" | "description">, options?: {
|
|
21
|
+
multiPackage?: boolean;
|
|
22
|
+
}): string;
|
package/dist/entry-generators.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { STANDALONE_ASYNC_UNSUPPORTED } from "@actiondock/core";
|
|
2
|
+
import { BuilderError } from "./errors.js";
|
|
2
3
|
import { serializePlanManifest } from "./manifest.js";
|
|
3
4
|
/**
|
|
4
5
|
* 生成 Host 子进程入口脚本源码(负责运行 ActionDockHost,通过 Node IPC 暴露 Target)。
|
|
@@ -9,25 +10,49 @@ import { serializePlanManifest } from "./manifest.js";
|
|
|
9
10
|
* 按需加载业务实现;跨包外部 Action 不混入本包源码,依赖调用继续经 Host 路由;
|
|
10
11
|
* - 入口路径以产物包根目录为基准(import.meta.dirname),不依赖调用者当前工作目录。
|
|
11
12
|
*/
|
|
12
|
-
export function generateNodeHostEntrySource(plan) {
|
|
13
|
+
export function generateNodeHostEntrySource(plan, options) {
|
|
13
14
|
// 复用 SelectionPlan 的清单序列化单一事实源(含包内动作筛选与 Schema 字段映射),
|
|
14
15
|
// 不单独复制一份 Schema 字段映射
|
|
15
|
-
|
|
16
|
+
if (!options && !plan)
|
|
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
|
+
}`;
|
|
16
33
|
return `#!/usr/bin/env node
|
|
17
34
|
// AUTO-GENERATED HOST ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
|
|
18
|
-
import "
|
|
35
|
+
import { resolve } from "node:path";
|
|
19
36
|
import {
|
|
37
|
+
loadProjectConfig,
|
|
20
38
|
createActionDock,
|
|
21
39
|
createNodePlatform,
|
|
40
|
+
validateLocalSkillPackages,
|
|
22
41
|
} from "@actiondock/core";
|
|
23
42
|
import { serveParentIpc } from "@actiondock/core/server";
|
|
24
43
|
|
|
25
44
|
// 产物包根目录:以入口文件自身位置为基准,不依赖调用者当前工作目录
|
|
26
45
|
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
|
+
}` : ""}
|
|
27
51
|
|
|
28
52
|
let dataDir;
|
|
29
53
|
const configOverrides = {};
|
|
30
54
|
const args = process.argv.slice(2);
|
|
55
|
+
const commandArgs = [];
|
|
31
56
|
for (let i = 0; i < args.length; i++) {
|
|
32
57
|
if (args[i] === "--data-dir" && i + 1 < args.length) {
|
|
33
58
|
dataDir = args[++i];
|
|
@@ -41,33 +66,28 @@ for (let i = 0; i < args.length; i++) {
|
|
|
41
66
|
const raw = args[i].slice(9);
|
|
42
67
|
const [k, ...v] = raw.split("=");
|
|
43
68
|
if (k) configOverrides[k] = v.join("=");
|
|
69
|
+
} else {
|
|
70
|
+
commandArgs.push(args[i]);
|
|
44
71
|
}
|
|
45
72
|
}
|
|
46
73
|
|
|
47
74
|
// 复用既有命令分发结果确定访问性质:发现命令(list/describe/show)为旁观初始化,
|
|
48
75
|
// 不创建数据库、不取目录锁、不收割遗留运行记录;执行与写入命令为持有者初始化
|
|
49
|
-
const command =
|
|
76
|
+
const command = commandArgs.find((a) => !a.startsWith("-")) || "";
|
|
50
77
|
const DISCOVERY_COMMANDS = new Set(["list", "describe", "show"]);
|
|
51
78
|
const recoverOrphans = DISCOVERY_COMMANDS.has(command) ? false : undefined;
|
|
52
79
|
|
|
80
|
+
const localPackages = [${packageEntries}];
|
|
81
|
+
validateLocalSkillPackages(localPackages.map((p) => ({
|
|
82
|
+
packageId: p.projectConfig.id,
|
|
83
|
+
packageRoot: p.packageRoot,
|
|
84
|
+
projectConfig: p.projectConfig,
|
|
85
|
+
})));
|
|
86
|
+
|
|
53
87
|
const service = await createActionDock({
|
|
54
88
|
dataDir,
|
|
55
89
|
...(recoverOrphans === false ? { recoverOrphans: false } : {}),
|
|
56
|
-
packages:
|
|
57
|
-
{
|
|
58
|
-
packageRoot,
|
|
59
|
-
dataDir,
|
|
60
|
-
configOverrides,
|
|
61
|
-
projectConfig: {
|
|
62
|
-
id: ${JSON.stringify(plan.packageId)},
|
|
63
|
-
name: ${JSON.stringify(plan.packageName)},
|
|
64
|
-
version: ${JSON.stringify(plan.version)},
|
|
65
|
-
description: ${JSON.stringify(plan.description || "")},
|
|
66
|
-
config: ${JSON.stringify(plan.configDefs || {})},
|
|
67
|
-
actions: ${JSON.stringify(manifestActions)},
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
],
|
|
90
|
+
packages: localPackages,
|
|
71
91
|
platform: createNodePlatform({ dataDir }),
|
|
72
92
|
autoLoadCurrentProject: false,
|
|
73
93
|
scanLinkedPackages: false,
|
|
@@ -78,10 +98,9 @@ await serveParentIpc(service);
|
|
|
78
98
|
/**
|
|
79
99
|
* 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
|
|
80
100
|
*/
|
|
81
|
-
export function generateNodeSupervisorEntrySource(plan) {
|
|
101
|
+
export function generateNodeSupervisorEntrySource(plan, options) {
|
|
82
102
|
return `#!/usr/bin/env node
|
|
83
103
|
// AUTO-GENERATED SUPERVISOR ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
|
|
84
|
-
import "@actiondock/core/warning";
|
|
85
104
|
import { spawn } from "node:child_process";
|
|
86
105
|
import { join } from "node:path";
|
|
87
106
|
import {
|
|
@@ -143,8 +162,8 @@ if (argv.includes("-h") || argv.includes("--help") || argv[0] === "help") {
|
|
|
143
162
|
console.log(" <cmd> run <id> [--input '<json>'] Execute action with JSON input");
|
|
144
163
|
console.log(" <cmd> run <id> [--stdin-field <field>] [-- <assignments...>]");
|
|
145
164
|
console.log(" Bind raw stdin text to an input field");
|
|
146
|
-
console.log("
|
|
147
|
-
console.log(" <cmd> state list/get/set/delete Manage shared state store");
|
|
165
|
+
${options?.multiPackage ? 'console.log(" Multi-package config/state management is unavailable; use --config overrides.");'
|
|
166
|
+
: 'console.log(" <cmd> config list/get/set/delete Manage package configuration");\n console.log(" <cmd> state list/get/set/delete Manage shared state store");'}
|
|
148
167
|
console.log("\\nGlobal options:");
|
|
149
168
|
console.log(" --data-dir <path> Custom runtime database directory");
|
|
150
169
|
console.log(" --config <KEY=val> Temporary config override");
|
|
@@ -153,8 +172,7 @@ if (argv.includes("-h") || argv.includes("--help") || argv[0] === "help") {
|
|
|
153
172
|
|
|
154
173
|
// 3. 建立物理隔离监督边界,启动运行 ActionDockHost 的独立子进程
|
|
155
174
|
const hostScript = join(import.meta.dirname, "entry-host.js");
|
|
156
|
-
const
|
|
157
|
-
const child = spawn(process.execPath, [...warningFlags, hostScript, ...argv], {
|
|
175
|
+
const child = spawn(process.execPath, [hostScript, ...argv], {
|
|
158
176
|
cwd: process.cwd(),
|
|
159
177
|
env: process.env,
|
|
160
178
|
stdio: ["pipe", "pipe", "pipe", "ipc"],
|
|
@@ -194,6 +212,7 @@ const dispatcher = new StandaloneDispatcher({
|
|
|
194
212
|
version: METADATA.version,
|
|
195
213
|
description: METADATA.description,
|
|
196
214
|
service,
|
|
215
|
+
multiPackage: ${JSON.stringify(options?.multiPackage === true)},
|
|
197
216
|
});
|
|
198
217
|
|
|
199
218
|
try {
|
|
@@ -3,7 +3,7 @@ import type { CompositeSkillExportOptions, CompositeSkillExportResult, SkillExpo
|
|
|
3
3
|
* 复合套件导出装配层。
|
|
4
4
|
*
|
|
5
5
|
* 职责:将多个包聚合为一个统一的复合技能目录(mini-workspace 形态),
|
|
6
|
-
*
|
|
6
|
+
* 保留各子包 npm 依赖并生成复合根 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 {
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, 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,9 +6,10 @@ 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
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
9
|
+
import { allocatePackageDirName } from "./manifest.js";
|
|
10
|
+
import { createSourcePackageGraph } from "./source-graph.js";
|
|
11
|
+
import { generateNodeHostEntrySource, generateNodeSupervisorEntrySource } from "./entry-generators.js";
|
|
12
|
+
import { findExistingCompositeSkillMd, resolveCustomSkillDeclaration, appendLocalExecutionContract, } from "./skill-md.js";
|
|
12
13
|
import { createArchive, resolveArchiveFormat } from "./export-archive.js";
|
|
13
14
|
/**
|
|
14
15
|
* 执行复合模式导出:将多个包聚合为一个统一的复合技能目录。
|
|
@@ -38,30 +39,36 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
38
39
|
mkdirSync(packagesDestDir, { recursive: true });
|
|
39
40
|
}
|
|
40
41
|
const packageInfos = [];
|
|
42
|
+
const localPackages = [];
|
|
41
43
|
const packageSummaries = [];
|
|
42
44
|
let existingSkillPath;
|
|
43
45
|
try {
|
|
44
|
-
|
|
45
|
-
const effectiveRoots =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
const packageGraph = skillMdOnly ? undefined : options.packageGraph ?? createSourcePackageGraph(options.projectRoots);
|
|
47
|
+
const effectiveRoots = skillMdOnly
|
|
48
|
+
? new Set(options.projectRoots.map((root) => resolve(root)))
|
|
49
|
+
: new Set(Array.from(packageGraph.packages.values(), (pkg) => pkg.root));
|
|
50
|
+
const usedDirNames = new Set();
|
|
51
|
+
const usedNpmNames = new Set();
|
|
52
|
+
const sourceNpmNames = new Map();
|
|
53
|
+
if (!skillMdOnly) {
|
|
54
|
+
// 先保留全部真实 npm 名,缺省名不得覆盖尚未导出的兄弟包身份。
|
|
55
|
+
for (const projectRoot of effectiveRoots) {
|
|
56
|
+
const pkgJsonPath = join(projectRoot, "package.json");
|
|
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");
|
|
55
62
|
}
|
|
56
|
-
|
|
57
|
-
catch {
|
|
58
|
-
// 忽略规划解析异常,由主循环处理
|
|
63
|
+
usedNpmNames.add(name);
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
}
|
|
62
|
-
const usedDirNames = new Set();
|
|
63
67
|
for (const projectRoot of effectiveRoots) {
|
|
64
|
-
const
|
|
68
|
+
const graphManifest = packageGraph && Array.from(packageGraph.packages.values()).find((p) => resolve(p.root) === resolve(projectRoot))?.manifest;
|
|
69
|
+
const config = graphManifest
|
|
70
|
+
? { ...graphManifest, name: graphManifest.name || graphManifest.id, version: graphManifest.version || "0.1.0" }
|
|
71
|
+
: loadProjectConfig(projectRoot);
|
|
65
72
|
let pkgSlug;
|
|
66
73
|
if (skillMdOnly) {
|
|
67
74
|
// 就地生成:规程链接基于工作区实际子目录名
|
|
@@ -71,34 +78,49 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
71
78
|
// 目录名分配统一复用 manifest 的 allocatePackageDirName,回退后仍冲突会追加数字后缀
|
|
72
79
|
pkgSlug = allocatePackageDirName(usedDirNames, config.id);
|
|
73
80
|
}
|
|
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
|
+
}
|
|
74
91
|
let actionIds;
|
|
75
|
-
let
|
|
92
|
+
let playbooks;
|
|
76
93
|
let manifest;
|
|
77
94
|
if (skillMdOnly) {
|
|
78
95
|
// 仅生成 SKILL.md:直接读取清单获取 Action 契约描述,不拷贝子包产物
|
|
79
96
|
manifest = loadManifest(projectRoot);
|
|
80
97
|
actionIds = manifest?.actions ? Object.keys(manifest.actions) : [];
|
|
81
|
-
|
|
98
|
+
playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
|
|
82
99
|
}
|
|
83
100
|
else {
|
|
84
101
|
const destPkgDir = join(packagesDestDir, pkgSlug);
|
|
85
102
|
const singleExport = await exportSubpackage({
|
|
86
103
|
projectRoot,
|
|
104
|
+
packageGraph,
|
|
105
|
+
config,
|
|
106
|
+
manifest: graphManifest || undefined,
|
|
87
107
|
outDir: destPkgDir,
|
|
88
108
|
archive: false,
|
|
89
109
|
skipSkillMd: true,
|
|
90
110
|
mode: "source",
|
|
91
111
|
_isSubpackage: true,
|
|
112
|
+
defaultPackageName,
|
|
92
113
|
});
|
|
93
|
-
manifest = loadManifest(destPkgDir)
|
|
114
|
+
manifest = loadManifest(destPkgDir);
|
|
94
115
|
actionIds = singleExport.actions;
|
|
95
|
-
|
|
116
|
+
playbooks = loadPlaybooks(destPkgDir, manifest?.playbooksDir, manifest);
|
|
96
117
|
}
|
|
97
|
-
const
|
|
118
|
+
const playbookIds = Array.from(playbooks.keys());
|
|
98
119
|
const actionEntries = actionIds.map((actId) => ({
|
|
99
120
|
id: actId,
|
|
100
121
|
description: manifest?.actions?.[actId]?.description,
|
|
101
122
|
}));
|
|
123
|
+
localPackages.push({ packageId: config.id, relativeRoot: `packages/${pkgSlug}` });
|
|
102
124
|
packageInfos.push({
|
|
103
125
|
config,
|
|
104
126
|
actions: actionEntries,
|
|
@@ -118,6 +140,7 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
118
140
|
const compositeSkillMd = generateCompositeSkillMd(options.bundleName, description, packageInfos, {
|
|
119
141
|
customSections: customDeclaration?.sections,
|
|
120
142
|
packagesBaseDir: skillMdOnly ? "." : "packages",
|
|
143
|
+
invocationStyle: skillMdOnly ? "global-ad" : "source-ad",
|
|
121
144
|
});
|
|
122
145
|
if (skillMdOnly) {
|
|
123
146
|
// 就地仅重生成 SKILL.md:始终重新生成(忽略已有 SKILL.md 复用逻辑),不产出完整套件目录
|
|
@@ -153,9 +176,10 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
153
176
|
}
|
|
154
177
|
if (existingSkillPath) {
|
|
155
178
|
const destSkillMdPath = join(stagingDir, "SKILL.md");
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
179
|
+
writeFileSync(destSkillMdPath, readFileSync(existingSkillPath, "utf-8"), "utf-8");
|
|
180
|
+
appendLocalExecutionContract(destSkillMdPath, packageInfos.map((p) => ({
|
|
181
|
+
packageId: p.config.id, actions: p.actions,
|
|
182
|
+
})));
|
|
159
183
|
}
|
|
160
184
|
else {
|
|
161
185
|
writeFileSync(join(stagingDir, "SKILL.md"), compositeSkillMd, "utf-8");
|
|
@@ -164,11 +188,19 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
164
188
|
dependencyStepLabel: "安装复合技能聚合依赖",
|
|
165
189
|
relinkStepLabel: "完成安装后重新链接复合技能",
|
|
166
190
|
compositeLinkNote: true,
|
|
167
|
-
invocationStyle: "
|
|
191
|
+
invocationStyle: "source-ad",
|
|
192
|
+
entryRelPath: 'node "<skill_root>/entry.mjs"',
|
|
168
193
|
});
|
|
169
194
|
}
|
|
170
|
-
|
|
171
|
-
|
|
195
|
+
if (packagesDestDir) {
|
|
196
|
+
assertUniqueWorkspacePackageNames(packagesDestDir);
|
|
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");
|
|
172
204
|
// 原子替换目标复合技能目录
|
|
173
205
|
await replaceDirAtomic(stagingDir, targetSkillDir);
|
|
174
206
|
}
|
|
@@ -195,35 +227,77 @@ export async function exportCompositeImpl(options, exportSubpackage) {
|
|
|
195
227
|
};
|
|
196
228
|
}
|
|
197
229
|
/**
|
|
198
|
-
*
|
|
199
|
-
* 依赖清洗统一复用 manifest 的 sanitizeExportDependencies 单一入口,与单包导出口径一致。
|
|
230
|
+
* 复合根目录声明入口运行依赖,业务依赖保留在各工作区包中,由 npm 安装解析。
|
|
200
231
|
*/
|
|
201
|
-
function writeCompositePkgJson(stagingDir,
|
|
202
|
-
|
|
232
|
+
function writeCompositePkgJson(stagingDir, bundleSlug, options) {
|
|
233
|
+
// 子包保留各自依赖约束,由 npm workspaces 正常安装与多版本解析,不聚合覆盖。
|
|
234
|
+
const runtimeDeps = {
|
|
235
|
+
"@actiondock/core": getInternalDependencyVersion(),
|
|
203
236
|
"@actiondock/sdk": getInternalDependencyVersion(),
|
|
204
237
|
};
|
|
205
|
-
for (const info of packageInfos) {
|
|
206
|
-
const pkgJsonPath = join(packagesDestDir, info.packageDir, "package.json");
|
|
207
|
-
if (existsSync(pkgJsonPath)) {
|
|
208
|
-
try {
|
|
209
|
-
const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
210
|
-
Object.assign(aggregatedDeps, sanitizeExportDependencies(join(packagesDestDir, info.packageDir), parsed.dependencies, "exported skill packages", false));
|
|
211
|
-
}
|
|
212
|
-
catch (err) {
|
|
213
|
-
if (err instanceof BuilderError)
|
|
214
|
-
throw err;
|
|
215
|
-
// 复合导出缺依赖影响面大:读文件与解析失败必须显式报错,严禁静默吞掉导致聚合依赖缺失
|
|
216
|
-
throw new BuilderError(`Failed to read or parse package.json of exported subpackage '${info.packageDir}' (${pkgJsonPath}): ${err instanceof Error ? err.message : String(err)}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
238
|
const compositePkg = {
|
|
221
239
|
name: `${bundleSlug}-composite-skill`,
|
|
222
240
|
version: "0.1.0",
|
|
223
241
|
description: options.description || `Composite Skill suite: ${options.bundleName}`,
|
|
224
242
|
type: "module",
|
|
225
243
|
workspaces: ["packages/*"],
|
|
226
|
-
dependencies:
|
|
244
|
+
dependencies: runtimeDeps,
|
|
227
245
|
};
|
|
228
246
|
writeFileSync(join(stagingDir, "package.json"), JSON.stringify(compositePkg, null, 2) + "\n", "utf-8");
|
|
229
247
|
}
|
|
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.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, readFileSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
3
|
import { mkdirSync } from "node:fs";
|
|
4
|
+
import { generateNodeHostEntrySource, generateNodeSupervisorEntrySource } from "./entry-generators.js";
|
|
5
|
+
import { appendLocalExecutionContract } from "./skill-md.js";
|
|
4
6
|
import { generateSourceSkillMd } from "./skill/index.js";
|
|
5
7
|
import { RUNTIME_REFERENCE_REL_PATH, renderRuntimeReferenceContent, } from "./skill/templates.js";
|
|
8
|
+
import { loadManifest } from "@actiondock/core/project";
|
|
6
9
|
import { BuilderError } from "./errors.js";
|
|
7
10
|
import { getInternalDependencyVersion } from "./fs-utils.js";
|
|
8
11
|
import { assertValidManifestActionIds, sanitizeExportDependencies, serializePlanManifest, } from "./manifest.js";
|
|
@@ -21,18 +24,24 @@ export function stageSourceSkill(root, skillDir, plan, options, pkgSlug) {
|
|
|
21
24
|
const actionsDir = plan.actionsDir || "actions";
|
|
22
25
|
const playbooksDir = plan.playbooksDir || "playbooks";
|
|
23
26
|
const configForTemplates = buildConfigForTemplates(plan, actionsDir, playbooksDir);
|
|
24
|
-
const usedExistingSkillMd = writeSkillMd(root, skillDir, { skipSkillMd: options.skipSkillMd, skillMdPath: options.skillMdPath, pkgSlug }, () => generateSourceSkillMd(configForTemplates, plan.actions, plan.playbooks));
|
|
27
|
+
const usedExistingSkillMd = writeSkillMd(root, skillDir, { skipSkillMd: options.skipSkillMd, skillMdPath: options.skillMdPath, pkgSlug }, () => generateSourceSkillMd(configForTemplates, plan.actions.filter((a) => !a.isExternal), plan.playbooks));
|
|
25
28
|
// 导出精简后的 actiondock.json 项目配置(项目元数据与清单的单一事实源)
|
|
26
29
|
const exportedConfig = serializePlanManifest(plan, {
|
|
27
30
|
omitEmptyConfig: true,
|
|
28
31
|
includeDirs: { actionsDir, playbooksDir },
|
|
29
32
|
});
|
|
33
|
+
const originalManifest = options.manifest || loadManifest(root);
|
|
34
|
+
if (originalManifest?.dependencies)
|
|
35
|
+
exportedConfig.dependencies = originalManifest.dependencies;
|
|
36
|
+
const packageUses = originalManifest?.uses;
|
|
37
|
+
if (packageUses)
|
|
38
|
+
exportedConfig.uses = packageUses;
|
|
30
39
|
if (exportedConfig.actions && typeof exportedConfig.actions === "object") {
|
|
31
40
|
assertValidManifestActionIds(exportedConfig.actions);
|
|
32
41
|
}
|
|
33
42
|
writeFileSync(join(skillDir, "actiondock.json"), JSON.stringify(exportedConfig, null, 2) + "\n", "utf-8");
|
|
34
43
|
// 导出 package.json
|
|
35
|
-
writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug);
|
|
44
|
+
writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug, options);
|
|
36
45
|
// 拷贝 tsconfig.json(若存在)
|
|
37
46
|
const tsconfigPath = join(root, "tsconfig.json");
|
|
38
47
|
if (existsSync(tsconfigPath)) {
|
|
@@ -42,13 +51,27 @@ export function stageSourceSkill(root, skillDir, plan, options, pkgSlug) {
|
|
|
42
51
|
copyPlanEntries(skillDir, plan, {
|
|
43
52
|
playbookRelPath: (pb) => join(playbooksDir, basename(pb.filePath)),
|
|
44
53
|
});
|
|
54
|
+
if (!options._isSubpackage) {
|
|
55
|
+
for (const name of ["entry.mjs", "entry-host.js"]) {
|
|
56
|
+
if (existsSync(join(skillDir, name))) {
|
|
57
|
+
throw new BuilderError(`Source Skill entry conflict: '${name}' is reserved for the local runtime entry; refusing to overwrite a declared file.`, "SOURCE_ENTRY_CONFLICT");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
writeFileSync(join(skillDir, "entry.mjs"), generateNodeSupervisorEntrySource(plan), "utf-8");
|
|
61
|
+
writeFileSync(join(skillDir, "entry-host.js"), generateNodeHostEntrySource(undefined, {
|
|
62
|
+
localPackages: [{ packageId: plan.packageId, relativeRoot: "." }],
|
|
63
|
+
}), "utf-8");
|
|
64
|
+
if (usedExistingSkillMd)
|
|
65
|
+
appendLocalExecutionContract(join(skillDir, "SKILL.md"), [plan]);
|
|
66
|
+
}
|
|
45
67
|
// 声明资产拷贝完成后再物化默认运行参考文件:仅在使用自动模板时生成;
|
|
46
68
|
// 与项目声明资产同路径冲突时报错不覆盖(拷贝在前,冲突检测能真实命中)
|
|
47
69
|
if (usedExistingSkillMd === undefined && !options.skipSkillMd) {
|
|
48
70
|
writeRuntimeReferenceFile(skillDir, {
|
|
49
71
|
dependencyStepLabel: "安装技能源码依赖",
|
|
50
72
|
relinkStepLabel: "完成安装后重新链接本技能",
|
|
51
|
-
invocationStyle: "
|
|
73
|
+
invocationStyle: "source-ad",
|
|
74
|
+
entryRelPath: 'node "<skill_root>/entry.mjs"',
|
|
52
75
|
});
|
|
53
76
|
}
|
|
54
77
|
return usedExistingSkillMd;
|
|
@@ -65,15 +88,16 @@ export function writeRuntimeReferenceFile(destDir, options) {
|
|
|
65
88
|
mkdirSync(dirname(referencePath), { recursive: true });
|
|
66
89
|
writeFileSync(referencePath, renderRuntimeReferenceContent(options), "utf-8");
|
|
67
90
|
}
|
|
68
|
-
function writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug) {
|
|
91
|
+
function writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug, options) {
|
|
69
92
|
let exportedPkg;
|
|
93
|
+
const fallbackName = options?.defaultPackageName || pkgSlug;
|
|
70
94
|
const projectPkgPath = join(root, "package.json");
|
|
71
95
|
if (existsSync(projectPkgPath)) {
|
|
72
96
|
try {
|
|
73
97
|
const raw = readFileSync(projectPkgPath, "utf-8");
|
|
74
98
|
const parsed = JSON.parse(raw);
|
|
75
99
|
exportedPkg = {
|
|
76
|
-
name: parsed.name ||
|
|
100
|
+
name: parsed.name || fallbackName,
|
|
77
101
|
version: plan.version || parsed.version || "0.1.0",
|
|
78
102
|
description: plan.description || parsed.description,
|
|
79
103
|
type: "module",
|
|
@@ -84,12 +108,13 @@ function writeSourceSkillPkgJson(root, skillDir, plan, pkgSlug) {
|
|
|
84
108
|
if (err instanceof BuilderError) {
|
|
85
109
|
throw err;
|
|
86
110
|
}
|
|
87
|
-
exportedPkg = fallbackSkillPkg(plan,
|
|
111
|
+
exportedPkg = fallbackSkillPkg(plan, fallbackName);
|
|
88
112
|
}
|
|
89
113
|
}
|
|
90
114
|
else {
|
|
91
|
-
exportedPkg = fallbackSkillPkg(plan,
|
|
115
|
+
exportedPkg = fallbackSkillPkg(plan, fallbackName);
|
|
92
116
|
}
|
|
117
|
+
exportedPkg.dependencies["@actiondock/core"] ??= getInternalDependencyVersion();
|
|
93
118
|
writeFileSync(join(skillDir, "package.json"), JSON.stringify(exportedPkg, null, 2) + "\n", "utf-8");
|
|
94
119
|
}
|
|
95
120
|
/** 无 package.json 或解析失败时的兜底导出描述 */
|
package/dist/exporter.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
-
import { getPackageSlug } from "@actiondock/core/project";
|
|
3
|
+
import { getPackageSlug, loadManifest } from "@actiondock/core/project";
|
|
4
4
|
import { BuilderError } from "./errors.js";
|
|
5
5
|
import { replaceDirAtomic } from "./fs-utils.js";
|
|
6
6
|
import { collectRelativeFiles } from "./fs-utils.js";
|
|
7
|
+
import { createSourcePackageGraph } from "./source-graph.js";
|
|
8
|
+
import { deriveEffectivePackageGraph } from "./derived-graph.js";
|
|
7
9
|
import { SelectionPlanner } from "./planner.js";
|
|
8
10
|
import { isOwnAction } from "./types.js";
|
|
9
11
|
import { createArchive, resolveArchiveFormat } from "./export-archive.js";
|
|
@@ -26,8 +28,35 @@ export class SkillExporter {
|
|
|
26
28
|
async export(options) {
|
|
27
29
|
const root = resolve(options.projectRoot);
|
|
28
30
|
const mode = options.mode === "node" ? "node" : "source";
|
|
31
|
+
// 共享图、规划与源码清单使用同一有效声明;显式 manifest 不从磁盘继承依赖。
|
|
32
|
+
if (mode === "source" && (options.config || options.manifest)) {
|
|
33
|
+
const graphNode = options.packageGraph && Array.from(options.packageGraph.packages.values()).find((p) => resolve(p.root) === root);
|
|
34
|
+
const baseManifest = options.manifest || graphNode?.manifest || loadManifest(root) || {};
|
|
35
|
+
const manifest = {
|
|
36
|
+
...baseManifest,
|
|
37
|
+
...options.config,
|
|
38
|
+
...options.manifest,
|
|
39
|
+
// 保持既有 config 元数据优先级;动作、dependencies、uses 使用有效清单声明。
|
|
40
|
+
...Object.fromEntries([
|
|
41
|
+
"id", "name", "version", "description", "config", "actionsDir", "playbooksDir",
|
|
42
|
+
].map((key) => [key, options.config?.[key]]).filter(([, value]) => value !== undefined)),
|
|
43
|
+
};
|
|
44
|
+
options = {
|
|
45
|
+
...options,
|
|
46
|
+
manifest,
|
|
47
|
+
config: { ...manifest, name: manifest.name || manifest.id, version: manifest.version || "0.1.0" },
|
|
48
|
+
};
|
|
49
|
+
}
|
|
29
50
|
// 调用 SelectionPlanner 执行声明式构建规划
|
|
51
|
+
const packageGraph = mode === "source" && options.packageGraph
|
|
52
|
+
? deriveEffectivePackageGraph(options.packageGraph, root, options.manifest)
|
|
53
|
+
: options.packageGraph ?? (mode === "source" ? createSourcePackageGraph([root], options.manifest) : undefined);
|
|
54
|
+
options = {
|
|
55
|
+
...options,
|
|
56
|
+
packageGraph,
|
|
57
|
+
};
|
|
30
58
|
const plan = SelectionPlanner.plan({
|
|
59
|
+
packageGraph,
|
|
31
60
|
projectRoot: root,
|
|
32
61
|
config: options.config,
|
|
33
62
|
manifest: options.manifest,
|
|
@@ -39,7 +68,7 @@ export class SkillExporter {
|
|
|
39
68
|
const defaultFolderName = `${pkgSlug}-skill`;
|
|
40
69
|
const defaultSkillDir = join(root, "dist", defaultFolderName);
|
|
41
70
|
const targetSkillDir = resolve(options.outDir || defaultSkillDir);
|
|
42
|
-
const hasExternalDeps = Boolean(plan.externalProjectRoots && plan.externalProjectRoots.length > 0);
|
|
71
|
+
const hasExternalDeps = Boolean((packageGraph && packageGraph.packages.size > 1) || (plan.externalProjectRoots && plan.externalProjectRoots.length > 0));
|
|
43
72
|
// 单包源码导出若依赖闭包含外部包,导成 mini-workspace 形态(packages/ 下带齐依赖包),和 bundle 统一成一个交付形状
|
|
44
73
|
// 该复合分支会展开全部依赖包,无法保留调用方传入的 actions/playbooks 过滤选项;
|
|
45
74
|
// 静默丢弃过滤会导致产物全量导出,必须显式拒绝并给出调整指引(CLI 侧已有 roots>1 守卫,此处补齐 composite 分支守卫)
|
|
@@ -50,8 +79,9 @@ export class SkillExporter {
|
|
|
50
79
|
throw new BuilderError("Filtering options (--actions, --playbook) cannot be combined with a source export whose dependency closure spans external linked packages (composite mini-workspace output). Remove the filtering options to export the full closure, or adjust the 'uses' declarations so the closure stays within a single package.", "FILTERS_UNSUPPORTED_FOR_COMPOSITE");
|
|
51
80
|
}
|
|
52
81
|
if (willUseComposite) {
|
|
53
|
-
const allProjectRoots = Array.from(new Set([root, ...
|
|
82
|
+
const allProjectRoots = Array.from(new Set([root, ...Array.from(packageGraph.packages.values(), (p) => p.root)]));
|
|
54
83
|
const compositeRes = await this.exportComposite({
|
|
84
|
+
packageGraph,
|
|
55
85
|
bundleName: plan.packageName || plan.packageId,
|
|
56
86
|
projectRoots: allProjectRoots,
|
|
57
87
|
outDir: targetSkillDir,
|