@actiondock/builder 2.0.4
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 +74 -0
- package/package.json +41 -0
- package/src/archive.ts +248 -0
- package/src/compiler.ts +303 -0
- package/src/errors.ts +67 -0
- package/src/exporter.ts +407 -0
- package/src/index.ts +5 -0
- package/src/planner.ts +453 -0
- package/src/types.ts +262 -0
package/src/errors.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builder 模块基础错误类。
|
|
3
|
+
*/
|
|
4
|
+
export class BuilderError extends Error {
|
|
5
|
+
constructor(message: string) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "BuilderError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 构建规划阶段抛出的错误。
|
|
13
|
+
*/
|
|
14
|
+
export class PlannerError extends BuilderError {
|
|
15
|
+
public readonly code: string;
|
|
16
|
+
|
|
17
|
+
constructor(message: string, code = "PLANNER_ERROR") {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "PlannerError";
|
|
20
|
+
this.code = code;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 编译器参数与目标平台校验错误。
|
|
26
|
+
*/
|
|
27
|
+
export class CompilerValidationError extends BuilderError {
|
|
28
|
+
public readonly code: string;
|
|
29
|
+
public readonly target?: string;
|
|
30
|
+
|
|
31
|
+
constructor(message: string, options: { code?: string; target?: string } = {}) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "CompilerValidationError";
|
|
34
|
+
this.code = options.code || "COMPILER_VALIDATION_ERROR";
|
|
35
|
+
this.target = options.target;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 编译器调用执行失败错误。包含标准化错误码与详情。
|
|
41
|
+
*/
|
|
42
|
+
export class CompilerError extends BuilderError {
|
|
43
|
+
public readonly code: string;
|
|
44
|
+
public readonly exitCode: number;
|
|
45
|
+
public readonly rawError: string;
|
|
46
|
+
public readonly details: string[];
|
|
47
|
+
public readonly target?: string;
|
|
48
|
+
|
|
49
|
+
constructor(
|
|
50
|
+
message: string,
|
|
51
|
+
options: {
|
|
52
|
+
code: string;
|
|
53
|
+
exitCode: number;
|
|
54
|
+
rawError: string;
|
|
55
|
+
details?: string[];
|
|
56
|
+
target?: string;
|
|
57
|
+
}
|
|
58
|
+
) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "CompilerError";
|
|
61
|
+
this.code = options.code;
|
|
62
|
+
this.exitCode = options.exitCode;
|
|
63
|
+
this.rawError = options.rawError;
|
|
64
|
+
this.details = options.details || [];
|
|
65
|
+
this.target = options.target;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/src/exporter.ts
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
generateSkillJson,
|
|
5
|
+
generateSourceSkillMd,
|
|
6
|
+
generateStandaloneEntrypoint,
|
|
7
|
+
generateStandaloneSkillMd,
|
|
8
|
+
getPackageSlug,
|
|
9
|
+
type ProjectConfig,
|
|
10
|
+
} from "@actiondock/core";
|
|
11
|
+
import { createTarGzArchive, createZipArchive } from "./archive";
|
|
12
|
+
import { BunCompiler } from "./compiler";
|
|
13
|
+
import { BuilderError } from "./errors";
|
|
14
|
+
import { BuildPlanner } from "./planner";
|
|
15
|
+
import type { ArchiveFormat, SkillExporterOptions, SkillExportResult } from "./types";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 递归扫描生成目录中的所有相对文件路径。
|
|
19
|
+
*/
|
|
20
|
+
function scanRelativeFiles(dir: string, baseDir = dir): string[] {
|
|
21
|
+
if (!existsSync(dir)) return [];
|
|
22
|
+
const results: string[] = [];
|
|
23
|
+
const entries = readdirSync(dir);
|
|
24
|
+
for (const entry of entries) {
|
|
25
|
+
const fullPath = join(dir, entry);
|
|
26
|
+
const stat = statSync(fullPath);
|
|
27
|
+
if (stat.isDirectory()) {
|
|
28
|
+
results.push(...scanRelativeFiles(fullPath, baseDir));
|
|
29
|
+
} else if (stat.isFile()) {
|
|
30
|
+
results.push(relative(baseDir, fullPath));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return results;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 执行归档压缩操作(支持 .zip 与 .tar.gz)。
|
|
38
|
+
*
|
|
39
|
+
* 归档在进程内完成(见 archive.ts),不依赖宿主机的 zip / tar 命令行工具,
|
|
40
|
+
* 保证 Windows 与最小化 Linux 环境下行为一致。
|
|
41
|
+
*/
|
|
42
|
+
function createArchive(
|
|
43
|
+
skillDir: string,
|
|
44
|
+
format: ArchiveFormat
|
|
45
|
+
): string {
|
|
46
|
+
const parentDir = dirname(skillDir);
|
|
47
|
+
const folderName = basename(skillDir);
|
|
48
|
+
const archiveName = `${folderName}.${format === "tar.gz" ? "tar.gz" : "zip"}`;
|
|
49
|
+
const archivePath = join(parentDir, archiveName);
|
|
50
|
+
|
|
51
|
+
if (existsSync(archivePath)) {
|
|
52
|
+
rmSync(archivePath, { force: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
if (format === "tar.gz") {
|
|
57
|
+
createTarGzArchive(skillDir, archivePath);
|
|
58
|
+
} else {
|
|
59
|
+
createZipArchive(skillDir, archivePath);
|
|
60
|
+
}
|
|
61
|
+
} catch (err: any) {
|
|
62
|
+
throw new BuilderError(`Failed to create ${format} archive: ${err?.message || String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return archivePath;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Agent Skill 导出器。
|
|
70
|
+
* 负责源码型 Skill 与独立二进制型 Skill 的构建、打包与归档分发。
|
|
71
|
+
*/
|
|
72
|
+
export class SkillExporter {
|
|
73
|
+
/**
|
|
74
|
+
* 导出 Skill 产物包。
|
|
75
|
+
*
|
|
76
|
+
* @param options 导出配置
|
|
77
|
+
* @returns 导出产物详细描述
|
|
78
|
+
*/
|
|
79
|
+
public async export(options: SkillExporterOptions): Promise<SkillExportResult> {
|
|
80
|
+
const root = resolve(options.projectRoot);
|
|
81
|
+
const mode: "source" | "standalone" =
|
|
82
|
+
options.standalone || options.mode === "standalone" ? "standalone" : "source";
|
|
83
|
+
const target = options.target ? String(options.target) : "host";
|
|
84
|
+
|
|
85
|
+
// 1. 调用 BuildPlanner 执行纯声明式构建规划与依赖闭包裁剪
|
|
86
|
+
const planner = new BuildPlanner({ projectRoot: root });
|
|
87
|
+
const plan = planner.plan({
|
|
88
|
+
projectRoot: root,
|
|
89
|
+
config: options.config,
|
|
90
|
+
manifest: options.manifest,
|
|
91
|
+
actions: options.actions,
|
|
92
|
+
playbooks: options.playbooks,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const pkgSlug = getPackageSlug(plan.packageId);
|
|
96
|
+
const targetSuffix = mode === "standalone" && target !== "host" && target !== "bun" ? `-${target}` : "";
|
|
97
|
+
const defaultFolderName = `${pkgSlug}-skill${targetSuffix}`;
|
|
98
|
+
const defaultSkillDir = join(root, "dist", defaultFolderName);
|
|
99
|
+
const skillDir = resolve(options.outDir || defaultSkillDir);
|
|
100
|
+
|
|
101
|
+
mkdirSync(skillDir, { recursive: true });
|
|
102
|
+
|
|
103
|
+
const playbooksDestDir = join(skillDir, "playbooks");
|
|
104
|
+
if (plan.playbooks.length > 0) {
|
|
105
|
+
mkdirSync(playbooksDestDir, { recursive: true });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const configForTemplates: ProjectConfig = {
|
|
109
|
+
id: plan.packageId,
|
|
110
|
+
name: plan.packageName,
|
|
111
|
+
version: plan.version,
|
|
112
|
+
description: plan.description,
|
|
113
|
+
actionsDir: "actions",
|
|
114
|
+
playbooksDir: "playbooks",
|
|
115
|
+
config: plan.configDefs as any,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (mode === "source") {
|
|
119
|
+
// ----------------------------------------------------
|
|
120
|
+
// 源码 Skill 导出 (Source Skill Export)
|
|
121
|
+
// 包含: SKILL.md, actiondock.skill.json, actiondock.manifest.json, actiondock.json, package.json, 源码与资产
|
|
122
|
+
// ----------------------------------------------------
|
|
123
|
+
|
|
124
|
+
// 1. 生成 SKILL.md
|
|
125
|
+
const skillMd = generateSourceSkillMd(
|
|
126
|
+
configForTemplates,
|
|
127
|
+
plan.actions as any,
|
|
128
|
+
plan.playbooks as any
|
|
129
|
+
);
|
|
130
|
+
writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
131
|
+
|
|
132
|
+
// 2. 生成 actiondock.skill.json 工具清单
|
|
133
|
+
const skillJsonContent = {
|
|
134
|
+
schemaVersion: "2.0.0",
|
|
135
|
+
packageId: plan.packageId,
|
|
136
|
+
name: plan.packageName,
|
|
137
|
+
version: plan.version,
|
|
138
|
+
description: plan.description,
|
|
139
|
+
mode: "source",
|
|
140
|
+
actions: plan.actions.map((a) => ({
|
|
141
|
+
id: a.id,
|
|
142
|
+
entry: a.entry,
|
|
143
|
+
description: a.description,
|
|
144
|
+
inputSchema: a.inputSchema,
|
|
145
|
+
outputSchema: a.outputSchema,
|
|
146
|
+
uses: a.uses,
|
|
147
|
+
tags: a.tags,
|
|
148
|
+
})),
|
|
149
|
+
playbooks: plan.playbooks.map((p) => ({
|
|
150
|
+
id: p.id,
|
|
151
|
+
description: p.description,
|
|
152
|
+
entry: `playbooks/${basename(p.filePath)}`,
|
|
153
|
+
})),
|
|
154
|
+
exportedAt: new Date().toISOString(),
|
|
155
|
+
};
|
|
156
|
+
writeFileSync(
|
|
157
|
+
join(skillDir, "actiondock.skill.json"),
|
|
158
|
+
JSON.stringify(skillJsonContent, null, 2) + "\n",
|
|
159
|
+
"utf-8"
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// 3. 导出精简后的 actiondock.manifest.json 清单
|
|
163
|
+
const manifestActions: Record<string, unknown> = {};
|
|
164
|
+
for (const a of plan.actions) {
|
|
165
|
+
manifestActions[a.id] = {
|
|
166
|
+
entry: a.entry,
|
|
167
|
+
description: a.description,
|
|
168
|
+
inputSchema: a.inputSchema,
|
|
169
|
+
outputSchema: a.outputSchema,
|
|
170
|
+
uses: a.uses,
|
|
171
|
+
tags: a.tags,
|
|
172
|
+
annotations: a.annotations,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const exportedManifest = {
|
|
176
|
+
schemaVersion: 1,
|
|
177
|
+
actions: manifestActions,
|
|
178
|
+
assets: plan.assets,
|
|
179
|
+
};
|
|
180
|
+
writeFileSync(
|
|
181
|
+
join(skillDir, "actiondock.manifest.json"),
|
|
182
|
+
JSON.stringify(exportedManifest, null, 2) + "\n",
|
|
183
|
+
"utf-8"
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// 4. 导出精简后的 actiondock.json 项目配置
|
|
187
|
+
const exportedConfig = {
|
|
188
|
+
id: plan.packageId,
|
|
189
|
+
name: plan.packageName,
|
|
190
|
+
version: plan.version,
|
|
191
|
+
description: plan.description,
|
|
192
|
+
actionsDir: "actions",
|
|
193
|
+
playbooksDir: "playbooks",
|
|
194
|
+
config: plan.configDefs,
|
|
195
|
+
};
|
|
196
|
+
writeFileSync(
|
|
197
|
+
join(skillDir, "actiondock.json"),
|
|
198
|
+
JSON.stringify(exportedConfig, null, 2) + "\n",
|
|
199
|
+
"utf-8"
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
// 5. 导出 package.json
|
|
203
|
+
const projectPkgPath = join(root, "package.json");
|
|
204
|
+
let exportedPkg: any;
|
|
205
|
+
if (existsSync(projectPkgPath)) {
|
|
206
|
+
try {
|
|
207
|
+
const raw = readFileSync(projectPkgPath, "utf-8");
|
|
208
|
+
const parsed = JSON.parse(raw);
|
|
209
|
+
exportedPkg = {
|
|
210
|
+
name: parsed.name || pkgSlug,
|
|
211
|
+
version: plan.version || parsed.version || "0.1.0",
|
|
212
|
+
description: plan.description || parsed.description,
|
|
213
|
+
type: "module",
|
|
214
|
+
dependencies: parsed.dependencies || {
|
|
215
|
+
"@actiondock/sdk": "^2.0.0",
|
|
216
|
+
},
|
|
217
|
+
devDependencies: parsed.devDependencies,
|
|
218
|
+
};
|
|
219
|
+
} catch {
|
|
220
|
+
exportedPkg = {
|
|
221
|
+
name: pkgSlug,
|
|
222
|
+
version: plan.version,
|
|
223
|
+
description: plan.description,
|
|
224
|
+
type: "module",
|
|
225
|
+
dependencies: { "@actiondock/sdk": "^2.0.0" },
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
} else {
|
|
229
|
+
exportedPkg = {
|
|
230
|
+
name: pkgSlug,
|
|
231
|
+
version: plan.version,
|
|
232
|
+
description: plan.description,
|
|
233
|
+
type: "module",
|
|
234
|
+
dependencies: { "@actiondock/sdk": "^2.0.0" },
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
writeFileSync(
|
|
238
|
+
join(skillDir, "package.json"),
|
|
239
|
+
JSON.stringify(exportedPkg, null, 2) + "\n",
|
|
240
|
+
"utf-8"
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// 6. 拷贝 tsconfig.json(若存在)
|
|
244
|
+
const tsconfigPath = join(root, "tsconfig.json");
|
|
245
|
+
if (existsSync(tsconfigPath)) {
|
|
246
|
+
copyFileSync(tsconfigPath, join(skillDir, "tsconfig.json"));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// 7. 拷贝 Action 源码文件,完整保留相对路径
|
|
250
|
+
for (const act of plan.actions) {
|
|
251
|
+
if (existsSync(act.resolvedPath)) {
|
|
252
|
+
const destFile = join(skillDir, act.entry);
|
|
253
|
+
mkdirSync(dirname(destFile), { recursive: true });
|
|
254
|
+
copyFileSync(act.resolvedPath, destFile);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 8. 拷贝静态资产文件,完整保留相对路径
|
|
259
|
+
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
260
|
+
if (dep.type === "asset" && existsSync(dep.resolvedPath)) {
|
|
261
|
+
const destAsset = join(skillDir, dep.path);
|
|
262
|
+
mkdirSync(dirname(destAsset), { recursive: true });
|
|
263
|
+
copyFileSync(dep.resolvedPath, destAsset);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 9. 拷贝 Playbook 文件
|
|
268
|
+
for (const pb of plan.playbooks) {
|
|
269
|
+
if (existsSync(pb.filePath)) {
|
|
270
|
+
const destPb = join(playbooksDestDir, basename(pb.filePath));
|
|
271
|
+
copyFileSync(pb.filePath, destPb);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
// ----------------------------------------------------
|
|
276
|
+
// 独立二进制 Skill 导出 (Standalone Binary Skill Export)
|
|
277
|
+
// 包含: SKILL.md, actiondock.skill.json, bin/<binary>, playbooks/*, assets/*
|
|
278
|
+
// ----------------------------------------------------
|
|
279
|
+
const binDir = join(skillDir, "bin");
|
|
280
|
+
mkdirSync(binDir, { recursive: true });
|
|
281
|
+
|
|
282
|
+
const binaryName = pkgSlug;
|
|
283
|
+
const binaryPath = join(binDir, binaryName);
|
|
284
|
+
|
|
285
|
+
// 动态构造 Standalone 编译入口文件
|
|
286
|
+
const buildDir = join(root, ".actiondock", ".build");
|
|
287
|
+
mkdirSync(buildDir, { recursive: true });
|
|
288
|
+
|
|
289
|
+
const entryCode = generateStandaloneEntrypoint(
|
|
290
|
+
plan.packageId,
|
|
291
|
+
plan.version,
|
|
292
|
+
plan.description,
|
|
293
|
+
plan.actions.map((a) => ({ id: a.id, filePath: a.resolvedPath })),
|
|
294
|
+
plan.configDefs
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const entryFileName = `entry-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.ts`;
|
|
298
|
+
const entryPath = join(buildDir, entryFileName);
|
|
299
|
+
writeFileSync(entryPath, entryCode, "utf-8");
|
|
300
|
+
|
|
301
|
+
let compileRes;
|
|
302
|
+
try {
|
|
303
|
+
compileRes = await BunCompiler.compile({
|
|
304
|
+
entrypoint: entryPath,
|
|
305
|
+
outfile: binaryPath,
|
|
306
|
+
target: options.target,
|
|
307
|
+
minify: options.minify,
|
|
308
|
+
bytecode: options.bytecode,
|
|
309
|
+
cwd: root,
|
|
310
|
+
packageId: plan.packageId,
|
|
311
|
+
version: plan.version,
|
|
312
|
+
actions: plan.actions.map((a) => a.id),
|
|
313
|
+
});
|
|
314
|
+
} finally {
|
|
315
|
+
if (existsSync(entryPath)) {
|
|
316
|
+
rmSync(entryPath, { force: true });
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const actualBinaryName = basename(compileRes.executablePath);
|
|
321
|
+
|
|
322
|
+
// 1. 生成独立模式 SKILL.md
|
|
323
|
+
const skillMd = generateStandaloneSkillMd(
|
|
324
|
+
configForTemplates,
|
|
325
|
+
plan.actions as any,
|
|
326
|
+
plan.playbooks as any,
|
|
327
|
+
`./bin/${actualBinaryName}`
|
|
328
|
+
);
|
|
329
|
+
writeFileSync(join(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
330
|
+
|
|
331
|
+
// 2. 生成 actiondock.skill.json
|
|
332
|
+
const rawSkillJson = generateSkillJson(
|
|
333
|
+
configForTemplates,
|
|
334
|
+
plan.actions as any,
|
|
335
|
+
actualBinaryName,
|
|
336
|
+
target
|
|
337
|
+
);
|
|
338
|
+
const parsedSkillJson = JSON.parse(rawSkillJson);
|
|
339
|
+
parsedSkillJson.mode = "standalone";
|
|
340
|
+
writeFileSync(
|
|
341
|
+
join(skillDir, "actiondock.skill.json"),
|
|
342
|
+
JSON.stringify(parsedSkillJson, null, 2) + "\n",
|
|
343
|
+
"utf-8"
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
// 3. 拷贝 Playbook
|
|
347
|
+
for (const pb of plan.playbooks) {
|
|
348
|
+
if (existsSync(pb.filePath)) {
|
|
349
|
+
const destPb = join(playbooksDestDir, basename(pb.filePath));
|
|
350
|
+
copyFileSync(pb.filePath, destPb);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 4. 拷贝静态资产
|
|
355
|
+
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
356
|
+
if (dep.type === "asset" && existsSync(dep.resolvedPath)) {
|
|
357
|
+
const destAsset = join(skillDir, dep.path);
|
|
358
|
+
mkdirSync(dirname(destAsset), { recursive: true });
|
|
359
|
+
copyFileSync(dep.resolvedPath, destAsset);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 归档压缩处理
|
|
365
|
+
let archivePath: string | undefined;
|
|
366
|
+
if (options.archive) {
|
|
367
|
+
let format: ArchiveFormat = "zip";
|
|
368
|
+
if (options.archiveFormat === "tar.gz" || options.archive === "tar.gz") {
|
|
369
|
+
format = "tar.gz";
|
|
370
|
+
} else if (options.archiveFormat === "zip" || options.archive === "zip") {
|
|
371
|
+
format = "zip";
|
|
372
|
+
}
|
|
373
|
+
archivePath = createArchive(skillDir, format);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const files = scanRelativeFiles(skillDir);
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
packageId: plan.packageId,
|
|
380
|
+
version: plan.version,
|
|
381
|
+
mode,
|
|
382
|
+
target,
|
|
383
|
+
skillDir,
|
|
384
|
+
archivePath,
|
|
385
|
+
actionsCount: plan.actions.length,
|
|
386
|
+
playbooksCount: plan.playbooks.length,
|
|
387
|
+
actions: plan.actions.map((a) => a.id),
|
|
388
|
+
playbooks: plan.playbooks.map((p) => p.id),
|
|
389
|
+
files,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 静态辅助调用方法。
|
|
395
|
+
*/
|
|
396
|
+
public static async export(options: SkillExporterOptions): Promise<SkillExportResult> {
|
|
397
|
+
const exporter = new SkillExporter();
|
|
398
|
+
return exporter.export(options);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* 快捷导出 Skill 产物函数。
|
|
404
|
+
*/
|
|
405
|
+
export async function exportSkill(options: SkillExporterOptions): Promise<SkillExportResult> {
|
|
406
|
+
return SkillExporter.export(options);
|
|
407
|
+
}
|