@actiondock/builder 2.7.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 +4 -5
- package/dist/archive.d.ts +0 -16
- package/dist/archive.js +35 -202
- package/dist/build.d.ts +1 -0
- package/dist/build.js +26 -61
- package/dist/dependency-check.js +36 -89
- package/dist/export-batch.js +4 -6
- package/dist/export-composite.js +8 -22
- package/dist/export-node.js +1 -1
- package/dist/export-source.js +11 -61
- package/dist/exporter.js +3 -9
- package/dist/fs-utils.d.ts +3 -2
- package/dist/fs-utils.js +9 -66
- package/dist/index.d.ts +3 -10
- package/dist/index.js +5 -16
- package/dist/manifest.d.ts +26 -0
- package/dist/manifest.js +67 -2
- package/dist/pack.js +37 -47
- package/dist/planner.js +73 -102
- package/dist/skill/index.d.ts +1 -0
- package/dist/skill/index.js +1 -0
- package/dist/skill/templates.d.ts +60 -0
- package/dist/skill/templates.js +507 -0
- package/dist/skill-md.d.ts +2 -1
- package/dist/skill-md.js +2 -1
- package/dist/stage-sources.d.ts +25 -0
- package/dist/stage-sources.js +51 -0
- package/dist/types.d.ts +18 -23
- package/dist/types.js +10 -1
- package/package.json +3 -4
package/dist/manifest.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { ACTION_ID_REGEX } from "@actiondock/core";
|
|
3
|
+
import { ACTION_ID_REGEX, getPackageSlug } from "@actiondock/core/project";
|
|
4
4
|
import { BuilderError } from "./errors.js";
|
|
5
5
|
import { getInternalDependencyVersion } from "./fs-utils.js";
|
|
6
|
+
import { isOwnAction } from "./types.js";
|
|
6
7
|
/**
|
|
7
8
|
* 校验清单 actions 字典中的所有 Action ID 是否符合规范。
|
|
8
9
|
* 强制写入端契约自检,杜绝非法 Action ID 落盘。
|
|
@@ -57,7 +58,7 @@ export function serializePlanManifest(plan, options = {}) {
|
|
|
57
58
|
const manifestActions = {};
|
|
58
59
|
for (const act of plan.actions) {
|
|
59
60
|
// 仅序列化包自有 Action,跨包依赖不写入包清单
|
|
60
|
-
if (act
|
|
61
|
+
if (!isOwnAction(act)) {
|
|
61
62
|
continue;
|
|
62
63
|
}
|
|
63
64
|
const serialized = serializeManifestAction(act);
|
|
@@ -174,6 +175,70 @@ export function resolveWorkspaceDepVersion(root, depName, ver) {
|
|
|
174
175
|
}
|
|
175
176
|
throw new BuilderError(`Failed to resolve workspace dependency '${depName}' (${ver}) in '${root}'. Target package version could not be found.`);
|
|
176
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* 为导出子包分配互不冲突的目录名。
|
|
180
|
+
*
|
|
181
|
+
* 首选 Package ID 尾段(getPackageSlug);冲突时回退为 ID 全量的安全形式,
|
|
182
|
+
* 回退后仍冲突(如两个同尾段 ID 回退值相同)则追加数字后缀直至唯一,
|
|
183
|
+
* 严禁仅加入 used 集合而不二次消歧导致目录互相覆盖。
|
|
184
|
+
*
|
|
185
|
+
* @param used 已占用的目录名集合(函数内部会登记分配结果)
|
|
186
|
+
* @param configId 待分配目录名的 Package ID
|
|
187
|
+
*/
|
|
188
|
+
export function allocatePackageDirName(used, configId) {
|
|
189
|
+
let name = getPackageSlug(configId);
|
|
190
|
+
if (used.has(name)) {
|
|
191
|
+
name = configId.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
|
|
192
|
+
}
|
|
193
|
+
if (!name) {
|
|
194
|
+
name = getPackageSlug(configId) || "package";
|
|
195
|
+
}
|
|
196
|
+
if (used.has(name)) {
|
|
197
|
+
let suffix = 2;
|
|
198
|
+
while (used.has(`${name}-${suffix}`)) {
|
|
199
|
+
suffix++;
|
|
200
|
+
}
|
|
201
|
+
name = `${name}-${suffix}`;
|
|
202
|
+
}
|
|
203
|
+
used.add(name);
|
|
204
|
+
return name;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* 导出产物依赖清洗单一入口。
|
|
208
|
+
*
|
|
209
|
+
* 清洗规则(单包源码导出与复合套件聚合导出共用同一事实源):
|
|
210
|
+
* - file: 本地协议拒绝(经 assertNoFileProtocolDeps);
|
|
211
|
+
* - @actiondock/* 内部依赖覆写为内部版本规则;
|
|
212
|
+
* - workspace: 前缀解析为真实版本(经 resolveWorkspaceDepVersion);
|
|
213
|
+
* - 其余依赖原样保留。
|
|
214
|
+
*
|
|
215
|
+
* @param root 解析 workspace 依赖时的参照根目录
|
|
216
|
+
* @param deps 待清洗的依赖字典(可选)
|
|
217
|
+
* @param context 错误消息中的场景描述
|
|
218
|
+
* @param ensureSdk 是否保证 @actiondock/sdk 依赖存在(单包导出需要,复合根目录已预置)
|
|
219
|
+
*/
|
|
220
|
+
export function sanitizeExportDependencies(root, deps, context, ensureSdk = true) {
|
|
221
|
+
const result = {};
|
|
222
|
+
assertNoFileProtocolDeps(deps, context);
|
|
223
|
+
if (deps && typeof deps === "object") {
|
|
224
|
+
for (const [depName, depVer] of Object.entries(deps)) {
|
|
225
|
+
const verStr = String(depVer);
|
|
226
|
+
if (depName.startsWith("@actiondock/")) {
|
|
227
|
+
result[depName] = getInternalDependencyVersion();
|
|
228
|
+
}
|
|
229
|
+
else if (verStr.startsWith("workspace:")) {
|
|
230
|
+
result[depName] = resolveWorkspaceDepVersion(root, depName, verStr);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
result[depName] = verStr;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (ensureSdk && !result["@actiondock/sdk"]) {
|
|
238
|
+
result["@actiondock/sdk"] = getInternalDependencyVersion();
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
177
242
|
/**
|
|
178
243
|
* 读取并解析 package.json,解析失败抛出 BuilderError。
|
|
179
244
|
*/
|
package/dist/pack.js
CHANGED
|
@@ -3,12 +3,15 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
|
-
import { basename,
|
|
7
|
-
import {
|
|
6
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
7
|
+
import { loadProjectConfig } from "@actiondock/core";
|
|
8
|
+
import { getPackageSlug } from "@actiondock/core/project";
|
|
8
9
|
import { BuilderError } from "./errors.js";
|
|
9
10
|
import { assertNoFileProtocolDeps, assertValidManifestActionIds, normalizePkgExportsAndEngines, readPackageJson, serializePlanManifest, } from "./manifest.js";
|
|
10
11
|
import { collectRelativeFiles } from "./fs-utils.js";
|
|
11
12
|
import { SelectionPlanner } from "./planner.js";
|
|
13
|
+
import { isOwnAction } from "./types.js";
|
|
14
|
+
import { copyPlanEntries } from "./stage-sources.js";
|
|
12
15
|
/**
|
|
13
16
|
* 对 Action Package 执行打包并生成标准 npm Action 压缩包(.tgz)。
|
|
14
17
|
*
|
|
@@ -158,7 +161,7 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
|
|
|
158
161
|
const ts = await resolveTypeScriptCompiler(root);
|
|
159
162
|
const tsSourceFiles = new Set();
|
|
160
163
|
for (const act of plan.actions) {
|
|
161
|
-
if (act
|
|
164
|
+
if (!isOwnAction(act))
|
|
162
165
|
continue;
|
|
163
166
|
if (isTypeScriptSource(act.entry)) {
|
|
164
167
|
tsSourceFiles.add(act.resolvedPath);
|
|
@@ -187,27 +190,34 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
|
|
|
187
190
|
if (existsSync(tsconfigPath)) {
|
|
188
191
|
try {
|
|
189
192
|
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
// tsc 不会改写 import 说明符:paths 路径别名在产物中无法解析,必须显式拒绝而非静默编译出坏产物
|
|
193
|
-
if (parsedConfig.options.paths && Object.keys(parsedConfig.options.paths).length > 0) {
|
|
194
|
-
throw new BuilderError(`tsconfig.json 'paths' aliases are not supported in packed output: TypeScript does not rewrite import specifiers, so the packed artifact would contain unresolvable module specifiers. Replace path aliases with relative imports, or pre-build the project and pack the compiled output instead.`, "PATHS_ALIAS_UNSUPPORTED");
|
|
195
|
-
}
|
|
196
|
-
Object.assign(compilerOptions, parsedConfig.options, {
|
|
197
|
-
outDir: stagingPkgDir,
|
|
198
|
-
rootDir: root,
|
|
199
|
-
declaration: true,
|
|
200
|
-
emitDeclarationOnly: false,
|
|
201
|
-
rewriteRelativeImportExtensions: true,
|
|
202
|
-
noEmit: false,
|
|
203
|
-
});
|
|
193
|
+
if (configFile.error) {
|
|
194
|
+
throw new Error(ts.formatDiagnostic(configFile.error));
|
|
204
195
|
}
|
|
196
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, root);
|
|
197
|
+
if (parsedConfig.errors && parsedConfig.errors.length > 0) {
|
|
198
|
+
throw new Error(ts.formatDiagnostics(parsedConfig.errors));
|
|
199
|
+
}
|
|
200
|
+
// tsc 不会改写 import 说明符:paths 路径别名在产物中无法解析,必须显式拒绝而非静默编译出坏产物
|
|
201
|
+
if (parsedConfig.options.paths && Object.keys(parsedConfig.options.paths).length > 0) {
|
|
202
|
+
throw new BuilderError(`tsconfig.json 'paths' aliases are not supported in packed output: TypeScript does not rewrite import specifiers, so the packed artifact would contain unresolvable module specifiers. Replace path aliases with relative imports, or pre-build the project and pack the compiled output instead.`, "PATHS_ALIAS_UNSUPPORTED");
|
|
203
|
+
}
|
|
204
|
+
Object.assign(compilerOptions, parsedConfig.options, {
|
|
205
|
+
outDir: stagingPkgDir,
|
|
206
|
+
rootDir: root,
|
|
207
|
+
declaration: true,
|
|
208
|
+
emitDeclarationOnly: false,
|
|
209
|
+
rewriteRelativeImportExtensions: true,
|
|
210
|
+
noEmit: false,
|
|
211
|
+
});
|
|
205
212
|
}
|
|
206
213
|
catch (err) {
|
|
207
214
|
if (err instanceof BuilderError) {
|
|
208
215
|
throw err;
|
|
209
216
|
}
|
|
210
|
-
//
|
|
217
|
+
// tsconfig 解析失败回退默认 compilerOptions,但必须输出显著告警:
|
|
218
|
+
// 用户配置的 strict、target 等被丢弃可能编译出行为不同的产物,严禁无提示
|
|
219
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
220
|
+
console.warn(`[actiondock] Warning: Failed to parse tsconfig.json at ${tsconfigPath}; falling back to default compiler options for packed output. Root cause: ${reason}`);
|
|
211
221
|
}
|
|
212
222
|
}
|
|
213
223
|
const host = ts.createCompilerHost(compilerOptions);
|
|
@@ -228,36 +238,16 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
|
|
|
228
238
|
}
|
|
229
239
|
/**
|
|
230
240
|
* 拷贝声明的非 TypeScript 模块、静态资产、原生 JavaScript 入口、Playbook 与基础文档到暂存目录。
|
|
241
|
+
* 拷贝内核统一复用 stage-sources 的 copyPlanEntries,仅通过谓词分化编译排除差异。
|
|
231
242
|
*/
|
|
232
243
|
function stageSources(root, stagingPkgDir, plan) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const destPath = join(stagingPkgDir, dep.path);
|
|
241
|
-
mkdirSync(dirname(destPath), { recursive: true });
|
|
242
|
-
copyFileSync(dep.resolvedPath, destPath);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
// 拷贝原生 JavaScript Action 入口(若存在)
|
|
246
|
-
for (const act of plan.actions) {
|
|
247
|
-
if (!isTypeScriptSource(act.entry)) {
|
|
248
|
-
const destPath = join(stagingPkgDir, act.entry);
|
|
249
|
-
mkdirSync(dirname(destPath), { recursive: true });
|
|
250
|
-
copyFileSync(act.resolvedPath, destPath);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
// 拷贝 Playbook 规程文件
|
|
254
|
-
for (const pb of plan.playbooks) {
|
|
255
|
-
if (existsSync(pb.filePath)) {
|
|
256
|
-
const destPb = join(stagingPkgDir, relative(root, pb.filePath));
|
|
257
|
-
mkdirSync(dirname(destPb), { recursive: true });
|
|
258
|
-
copyFileSync(pb.filePath, destPb);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
244
|
+
copyPlanEntries(stagingPkgDir, plan, {
|
|
245
|
+
// TypeScript 入口已由编译阶段生成 .js 与 .d.ts,不重复物化源文件
|
|
246
|
+
copyAction: (act) => !isTypeScriptSource(act.entry),
|
|
247
|
+
copyModule: (dep) => !isTypeScriptSource(dep.path),
|
|
248
|
+
// pack 产物保留源工程相对路径(与编译输出的目录结构一致)
|
|
249
|
+
playbookRelPath: (pb) => relative(root, pb.filePath),
|
|
250
|
+
});
|
|
261
251
|
// 拷贝 README 等基础文档(若存在)
|
|
262
252
|
for (const doc of ["README.md", "readme.md", "LICENSE", "license"]) {
|
|
263
253
|
const srcDoc = join(root, doc);
|
|
@@ -285,7 +275,7 @@ function writeManifestAndPkgJson(stagingPkgDir, plan, sourcePkgJson) {
|
|
|
285
275
|
// 构建编译后的 Action 清单字典并严格校验入口扩展名
|
|
286
276
|
const compiledEntries = new Map();
|
|
287
277
|
for (const act of plan.actions) {
|
|
288
|
-
if (act
|
|
278
|
+
if (!isOwnAction(act))
|
|
289
279
|
continue;
|
|
290
280
|
const destEntry = compiledEntryFor(act);
|
|
291
281
|
if (!destEntry.endsWith(".js") && !destEntry.endsWith(".mjs")) {
|
package/dist/planner.js
CHANGED
|
@@ -1,64 +1,33 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync,
|
|
2
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
3
3
|
import { basename, join, relative, resolve } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { loadProjectConfig, } from "@actiondock/core";
|
|
5
|
+
import { assertPathWithinRoot, traverseDirectory, loadManifest, loadPlaybooks, validateManifest, } from "@actiondock/core/project";
|
|
6
|
+
import { DefaultActionCatalog, PackageGraphBuilder, resolveAction, } from "@actiondock/core/graph";
|
|
5
7
|
import { PlannerError } from "./errors.js";
|
|
6
8
|
import { assertRelativeDependenciesIntegrity } from "./dependency-check.js";
|
|
7
9
|
/**
|
|
8
10
|
* 递归扫描指定目录下的文件,返回绝对路径列表。
|
|
9
|
-
*
|
|
11
|
+
* 目录遍历统一复用 core 的 traverseDirectory 单一事实源,
|
|
12
|
+
* 自带软链接越界防护与循环拦截。
|
|
10
13
|
*/
|
|
11
|
-
function walkDirectory(dir
|
|
12
|
-
|
|
13
|
-
return [];
|
|
14
|
-
const results = [];
|
|
15
|
-
const entries = readdirSync(dir);
|
|
16
|
-
for (const entry of entries) {
|
|
17
|
-
const fullPath = join(dir, entry);
|
|
18
|
-
if (rootDir) {
|
|
19
|
-
try {
|
|
20
|
-
assertPathWithinRoot(rootDir, fullPath, "planner path");
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
let real;
|
|
27
|
-
try {
|
|
28
|
-
real = existsSync(fullPath) ? realpathSync(fullPath) : fullPath;
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
if (rootDir) {
|
|
34
|
-
try {
|
|
35
|
-
assertPathWithinRoot(rootDir, real, "planner path");
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
// 忽略并跳过指向项目根目录外部的软链接
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
if (visited.has(real)) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
visited.add(real);
|
|
46
|
-
try {
|
|
47
|
-
const stat = statSync(fullPath);
|
|
48
|
-
if (stat.isDirectory()) {
|
|
49
|
-
results.push(...walkDirectory(fullPath, rootDir, visited));
|
|
50
|
-
}
|
|
51
|
-
else if (stat.isFile()) {
|
|
52
|
-
results.push(fullPath);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
// 忽略无法访问或损坏的文件/符号链接
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return results;
|
|
14
|
+
function walkDirectory(dir) {
|
|
15
|
+
return traverseDirectory(dir).map((entry) => entry.fullPath);
|
|
61
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* 测试与类型声明文件后缀常量(供 fallback 清单过滤与 isIgnoredPath 共享同一口径)。
|
|
19
|
+
*/
|
|
20
|
+
const TEST_FILE_SUFFIXES = [
|
|
21
|
+
".test.ts",
|
|
22
|
+
".test.js",
|
|
23
|
+
".test.tsx",
|
|
24
|
+
".test.jsx",
|
|
25
|
+
".spec.ts",
|
|
26
|
+
".spec.js",
|
|
27
|
+
".spec.tsx",
|
|
28
|
+
".spec.jsx",
|
|
29
|
+
".d.ts",
|
|
30
|
+
];
|
|
62
31
|
/**
|
|
63
32
|
* 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
64
33
|
*/
|
|
@@ -69,15 +38,7 @@ function isIgnoredPath(relPath) {
|
|
|
69
38
|
normalized.startsWith(".git/") ||
|
|
70
39
|
normalized.startsWith("dist/") ||
|
|
71
40
|
normalized.startsWith(".actiondock/") ||
|
|
72
|
-
normalized.endsWith(
|
|
73
|
-
normalized.endsWith(".test.js") ||
|
|
74
|
-
normalized.endsWith(".test.tsx") ||
|
|
75
|
-
normalized.endsWith(".test.jsx") ||
|
|
76
|
-
normalized.endsWith(".spec.ts") ||
|
|
77
|
-
normalized.endsWith(".spec.js") ||
|
|
78
|
-
normalized.endsWith(".spec.tsx") ||
|
|
79
|
-
normalized.endsWith(".spec.jsx") ||
|
|
80
|
-
normalized.endsWith(".d.ts"));
|
|
41
|
+
TEST_FILE_SUFFIXES.some((suffix) => normalized.endsWith(suffix)));
|
|
81
42
|
}
|
|
82
43
|
/**
|
|
83
44
|
* 支持的包管理器锁文件候选列表。
|
|
@@ -122,8 +83,9 @@ function computeLockfileInfo(projectRoot, preferredLockfile) {
|
|
|
122
83
|
};
|
|
123
84
|
}
|
|
124
85
|
}
|
|
125
|
-
catch {
|
|
126
|
-
|
|
86
|
+
catch (err) {
|
|
87
|
+
// 存在但不可读:显式报错而非静默跳过,否则构建在无锁文件摘要状态下继续,可复现性校验形同虚设
|
|
88
|
+
throw new PlannerError(`Lockfile '${lockFileName}' exists but could not be read in ${projectRoot}: ${err?.message || String(err)}`, "LOCKFILE_READ_ERROR");
|
|
127
89
|
}
|
|
128
90
|
}
|
|
129
91
|
}
|
|
@@ -153,12 +115,13 @@ function generateFallbackManifest(projectRoot, actionsDir = "actions", config) {
|
|
|
153
115
|
}
|
|
154
116
|
}
|
|
155
117
|
}
|
|
156
|
-
// 若 actions
|
|
118
|
+
// 若 actions 目录存在,基于文件名建立默认映射(过滤规则与 isIgnoredPath 保持同一口径)
|
|
157
119
|
if (existsSync(dir)) {
|
|
158
|
-
const files = walkDirectory(dir
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
120
|
+
const files = walkDirectory(dir).filter((f) => {
|
|
121
|
+
const rel = relative(projectRoot, f).replace(/\\/g, "/");
|
|
122
|
+
return ((f.endsWith(".ts") || f.endsWith(".js")) &&
|
|
123
|
+
!isIgnoredPath(rel));
|
|
124
|
+
});
|
|
162
125
|
for (const file of files) {
|
|
163
126
|
const relPath = relative(projectRoot, file).replace(/\\/g, "/");
|
|
164
127
|
const filename = basename(file);
|
|
@@ -180,38 +143,43 @@ function generateFallbackManifest(projectRoot, actionsDir = "actions", config) {
|
|
|
180
143
|
}
|
|
181
144
|
/**
|
|
182
145
|
* 收集项目根目录 package.json 中声明的外部 npm 依赖。
|
|
146
|
+
*
|
|
147
|
+
* 防御与透明原则:文件不存在返回空集合(合法的无依赖工程);
|
|
148
|
+
* 文件存在但不可读或 JSON 损坏时抛 PlannerError 并携带路径与原因,
|
|
149
|
+
* 严禁静默吞掉导致后续 build 的生产依赖、pack 的产物依赖与 vendorDeps 全部无声缺失。
|
|
183
150
|
*/
|
|
184
151
|
function extractExternalDependencies(projectRoot) {
|
|
185
152
|
const pkgPath = join(projectRoot, "package.json");
|
|
186
153
|
if (!existsSync(pkgPath))
|
|
187
154
|
return [];
|
|
155
|
+
let parsed;
|
|
188
156
|
try {
|
|
189
157
|
const raw = readFileSync(pkgPath, "utf-8");
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
deps.push({
|
|
204
|
-
name,
|
|
205
|
-
versionRange: String(versionRange),
|
|
206
|
-
isDev: true,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
158
|
+
parsed = JSON.parse(raw);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
throw new PlannerError(`Failed to read or parse package.json at ${pkgPath}: ${err?.message || String(err)}`, "EXTRACT_DEPS_ERROR");
|
|
162
|
+
}
|
|
163
|
+
const deps = [];
|
|
164
|
+
if (parsed.dependencies && typeof parsed.dependencies === "object") {
|
|
165
|
+
for (const [name, versionRange] of Object.entries(parsed.dependencies)) {
|
|
166
|
+
deps.push({
|
|
167
|
+
name,
|
|
168
|
+
versionRange: String(versionRange),
|
|
169
|
+
isDev: false,
|
|
170
|
+
});
|
|
209
171
|
}
|
|
210
|
-
return deps;
|
|
211
172
|
}
|
|
212
|
-
|
|
213
|
-
|
|
173
|
+
if (parsed.devDependencies && typeof parsed.devDependencies === "object") {
|
|
174
|
+
for (const [name, versionRange] of Object.entries(parsed.devDependencies)) {
|
|
175
|
+
deps.push({
|
|
176
|
+
name,
|
|
177
|
+
versionRange: String(versionRange),
|
|
178
|
+
isDev: true,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
214
181
|
}
|
|
182
|
+
return deps;
|
|
215
183
|
}
|
|
216
184
|
/**
|
|
217
185
|
* 声明式选择集与构建规划器 (SelectionPlanner)。
|
|
@@ -335,6 +303,8 @@ export class SelectionPlanner {
|
|
|
335
303
|
const externalActionRoots = new Map();
|
|
336
304
|
const externalActionEntries = new Map();
|
|
337
305
|
const manifestActions = manifest.actions || {};
|
|
306
|
+
const graph = new PackageGraphBuilder({ root, allowDevLinks: true }).buildSync();
|
|
307
|
+
const catalog = new DefaultActionCatalog(graph);
|
|
338
308
|
while (queue.length > 0) {
|
|
339
309
|
const currentId = queue.shift();
|
|
340
310
|
queued.delete(currentId);
|
|
@@ -345,26 +315,27 @@ export class SelectionPlanner {
|
|
|
345
315
|
let entryRoot = root;
|
|
346
316
|
if (!entry) {
|
|
347
317
|
try {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
318
|
+
const resolved = resolveAction(currentId, { graph, catalog, caller: config?.id });
|
|
319
|
+
const extNode = graph.packages.get(resolved.package.id);
|
|
320
|
+
if (extNode && existsSync(extNode.root)) {
|
|
321
|
+
const extRoot = extNode.root;
|
|
351
322
|
let extConfig;
|
|
352
323
|
try {
|
|
353
324
|
extConfig = loadProjectConfig(extRoot);
|
|
354
325
|
}
|
|
355
326
|
catch {
|
|
356
327
|
extConfig = {
|
|
357
|
-
id:
|
|
358
|
-
name:
|
|
328
|
+
id: resolved.package.id,
|
|
329
|
+
name: resolved.package.id,
|
|
359
330
|
version: "0.1.0",
|
|
360
331
|
};
|
|
361
332
|
}
|
|
362
333
|
const externalManifest = loadManifest(extRoot);
|
|
363
|
-
let externalEntry = externalManifest?.actions?.[
|
|
364
|
-
extConfig?.actions?.[
|
|
334
|
+
let externalEntry = externalManifest?.actions?.[resolved.ref.actionId] ||
|
|
335
|
+
extConfig?.actions?.[resolved.ref.actionId];
|
|
365
336
|
if (!externalEntry) {
|
|
366
337
|
const staticManifest = generateFallbackManifest(extRoot, extConfig.actionsDir || "actions", extConfig);
|
|
367
|
-
externalEntry = staticManifest.actions?.[
|
|
338
|
+
externalEntry = staticManifest.actions?.[resolved.ref.actionId];
|
|
368
339
|
}
|
|
369
340
|
if (externalEntry) {
|
|
370
341
|
entry = externalEntry;
|
|
@@ -470,7 +441,7 @@ export class SelectionPlanner {
|
|
|
470
441
|
}
|
|
471
442
|
const stat = statSync(resolvedFile);
|
|
472
443
|
if (stat.isDirectory()) {
|
|
473
|
-
const walked = walkDirectory(resolvedFile
|
|
444
|
+
const walked = walkDirectory(resolvedFile);
|
|
474
445
|
for (const f of walked) {
|
|
475
446
|
const rel = relative(root, f).replace(/\\/g, "/");
|
|
476
447
|
if (!isIgnoredPath(rel) && !actionPathSet.has(f) && !filePathSet.has(rel)) {
|
|
@@ -517,7 +488,7 @@ export class SelectionPlanner {
|
|
|
517
488
|
}
|
|
518
489
|
const stat = statSync(resolvedAsset);
|
|
519
490
|
if (stat.isDirectory()) {
|
|
520
|
-
const walked = walkDirectory(resolvedAsset
|
|
491
|
+
const walked = walkDirectory(resolvedAsset);
|
|
521
492
|
for (const f of walked) {
|
|
522
493
|
const rel = relative(root, f).replace(/\\/g, "/");
|
|
523
494
|
if (!assetPathSet.has(rel)) {
|
|
@@ -545,7 +516,7 @@ export class SelectionPlanner {
|
|
|
545
516
|
// 默认 assets 目录扫描
|
|
546
517
|
const defaultAssetsDir = join(root, "assets");
|
|
547
518
|
if (existsSync(defaultAssetsDir)) {
|
|
548
|
-
const assetFiles = walkDirectory(defaultAssetsDir
|
|
519
|
+
const assetFiles = walkDirectory(defaultAssetsDir);
|
|
549
520
|
for (const file of assetFiles) {
|
|
550
521
|
const rel = relative(root, file).replace(/\\/g, "/");
|
|
551
522
|
if (!assetPathSet.has(rel)) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./templates.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./templates.js";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ActionSpec, ProjectConfig } from "@actiondock/core";
|
|
2
|
+
import type { PlaybookDefinition } from "@actiondock/core/project";
|
|
3
|
+
export type SkillActionItem = ActionSpec | {
|
|
4
|
+
id: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
inputSchema?: any;
|
|
7
|
+
outputSchema?: any;
|
|
8
|
+
annotations?: any;
|
|
9
|
+
};
|
|
10
|
+
export declare function generateSourceSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: PlaybookDefinition[]): string;
|
|
11
|
+
export declare function generateStandaloneSkillMd(config: ProjectConfig, actions: SkillActionItem[], playbooks: PlaybookDefinition[], binaryRelPath?: string): string;
|
|
12
|
+
export interface CompositeSkillPackageInfo {
|
|
13
|
+
config: ProjectConfig;
|
|
14
|
+
actions: Array<{
|
|
15
|
+
id: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
}>;
|
|
18
|
+
playbooks: Array<{
|
|
19
|
+
id: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
filePath: string;
|
|
23
|
+
}>;
|
|
24
|
+
packageDir: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 复合技能自定义说明书(SKILL.custom.md)的槽位,
|
|
28
|
+
* 决定自定义段落插入到官方模板生成结果中的位置。
|
|
29
|
+
*/
|
|
30
|
+
export type CompositeCustomSlot = "intro" | "after-init" | "after-describe" | "after-actions" | "after-playbooks" | "after-invoke" | "append";
|
|
31
|
+
export declare const COMPOSITE_CUSTOM_SLOTS: readonly CompositeCustomSlot[];
|
|
32
|
+
/** 工作区内约定俗成的复合技能自定义说明书文件名。 */
|
|
33
|
+
export declare const COMPOSITE_CUSTOM_DECLARATION_FILE = "SKILL.custom.md";
|
|
34
|
+
export interface CompositeCustomSection {
|
|
35
|
+
slot: CompositeCustomSlot;
|
|
36
|
+
content: string;
|
|
37
|
+
}
|
|
38
|
+
export interface CompositeCustomDeclaration {
|
|
39
|
+
/** 自定义说明书 frontmatter 中声明的 description 覆盖值 */
|
|
40
|
+
description?: string;
|
|
41
|
+
sections: CompositeCustomSection[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 解析自定义说明书正文:以 `<!-- actiondock:slot <name> -->` 标记行分段。
|
|
45
|
+
* 首个标记之前的内容归入 `append` 槽位;未知槽位名直接抛错,避免拼写错误被静默吞掉。
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseCustomSections(source: string): CompositeCustomSection[];
|
|
48
|
+
/**
|
|
49
|
+
* 解析复合技能自定义说明书文件:支持 YAML frontmatter 中的 `description` 覆盖,
|
|
50
|
+
* 正文按槽位标记解析为自定义段落。
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseCustomSkillDeclaration(source: string): CompositeCustomDeclaration;
|
|
53
|
+
/**
|
|
54
|
+
* 生成多包聚合的复合模式 SKILL.md 文档。
|
|
55
|
+
*/
|
|
56
|
+
export declare function generateCompositeSkillMd(bundleName: string, description: string, packages: CompositeSkillPackageInfo[], options?: {
|
|
57
|
+
customSections?: CompositeCustomSection[];
|
|
58
|
+
/** 规程链接的子包目录前缀:导出布局为 "packages"(默认);就地生成时传 "." 使用工作区实际目录 */
|
|
59
|
+
packagesBaseDir?: string;
|
|
60
|
+
}): string;
|