@actiondock/builder 2.10.0 → 2.12.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/archive.js +2 -1
- package/dist/build.d.ts +25 -2
- package/dist/build.js +99 -309
- package/dist/dependency-check.js +3 -3
- package/dist/dependency-resolver.d.ts +17 -0
- package/dist/dependency-resolver.js +69 -0
- package/dist/entry-generators.d.ts +15 -0
- package/dist/entry-generators.js +211 -0
- package/dist/export-composite.js +19 -1
- package/dist/export-source.d.ts +6 -0
- package/dist/export-source.js +24 -4
- package/dist/exporter.d.ts +1 -10
- package/dist/exporter.js +1 -16
- package/dist/fs-utils.js +2 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +3 -2
- package/dist/lockfile-resolver.d.ts +9 -0
- package/dist/lockfile-resolver.js +56 -0
- package/dist/npm-pack.d.ts +6 -0
- package/dist/npm-pack.js +73 -0
- package/dist/pack.d.ts +2 -2
- package/dist/pack.js +8 -212
- package/dist/planner.js +40 -162
- package/dist/skill/custom-parser.d.ts +28 -0
- package/dist/skill/custom-parser.js +61 -0
- package/dist/skill/index.d.ts +1 -0
- package/dist/skill/index.js +1 -0
- package/dist/skill/templates.d.ts +28 -29
- package/dist/skill/templates.js +104 -191
- package/dist/skill-md.d.ts +3 -14
- package/dist/skill-md.js +5 -11
- package/dist/types.d.ts +19 -20
- package/dist/vendor.d.ts +5 -0
- package/dist/vendor.js +76 -0
- package/package.json +3 -3
- package/dist/export-batch.d.ts +0 -13
- package/dist/export-batch.js +0 -37
- package/dist/export-node.d.ts +0 -11
- package/dist/export-node.js +0 -48
package/dist/pack.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { runNpmPack } from "./npm-pack.js";
|
|
3
3
|
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
4
|
import { tmpdir } from "node:os";
|
|
6
5
|
import { basename, join, relative, resolve } from "node:path";
|
|
7
6
|
import { loadProjectConfig } from "@actiondock/core";
|
|
@@ -16,8 +15,8 @@ import { copyPlanEntries } from "./stage-sources.js";
|
|
|
16
15
|
* 对 Action Package 执行打包并生成标准 npm Action 压缩包(.tgz)。
|
|
17
16
|
*
|
|
18
17
|
* 职责:
|
|
19
|
-
* -
|
|
20
|
-
* - 严格校验导出的 Manifest
|
|
18
|
+
* - 依托 Node 24 原生类型擦除特性,直接按规范收集并打包源码(.ts / .js 等)及资产文件到临时暂存目录。
|
|
19
|
+
* - 严格校验导出的 Manifest(不得包含 actionsDir 或 playbooksDir 等废弃目录字段)。
|
|
21
20
|
* - 在暂存目录调用带有 --ignore-scripts 的 npm pack 生成标准 npm 压缩包(.tgz),严禁使用正则替换与自建 tar 压缩。
|
|
22
21
|
*
|
|
23
22
|
* @param options 打包参数选项
|
|
@@ -47,7 +46,6 @@ export async function packProject(options) {
|
|
|
47
46
|
const stagingPkgDir = join(tempBase, "package");
|
|
48
47
|
mkdirSync(stagingPkgDir, { recursive: true });
|
|
49
48
|
try {
|
|
50
|
-
await compileTypeScript(root, stagingPkgDir, plan);
|
|
51
49
|
stageSources(root, stagingPkgDir, plan);
|
|
52
50
|
writeManifestAndPkgJson(stagingPkgDir, plan, sourcePkgJson);
|
|
53
51
|
// 收集打包文件相对清单与摘要
|
|
@@ -123,129 +121,12 @@ export async function packProject(options) {
|
|
|
123
121
|
}
|
|
124
122
|
}
|
|
125
123
|
/**
|
|
126
|
-
*
|
|
127
|
-
|
|
128
|
-
function isTypeScriptSource(path) {
|
|
129
|
-
return path.endsWith(".ts") || path.endsWith(".mts");
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* 解析项目本地 TypeScript 编译器(回退到打包工具自身依赖)。
|
|
133
|
-
*/
|
|
134
|
-
async function resolveTypeScriptCompiler(root) {
|
|
135
|
-
try {
|
|
136
|
-
const req = createRequire(join(root, "package.json"));
|
|
137
|
-
const tsPath = req.resolve("typescript");
|
|
138
|
-
const imported = await import(tsPath);
|
|
139
|
-
return imported.default || imported;
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
try {
|
|
143
|
-
const imported = await import("typescript");
|
|
144
|
-
return imported.default || imported;
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
throw new BuilderError(`TypeScript is required to pack TypeScript Action packages, but 'typescript' could not be resolved from ${root}.`);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* 在暂存目录内执行 TypeScript 编译,产出 .js 与 .d.ts。
|
|
153
|
-
* 仅在存在 TypeScript 源码时执行。
|
|
154
|
-
*/
|
|
155
|
-
async function compileTypeScript(root, stagingPkgDir, plan) {
|
|
156
|
-
const hasTypeScript = plan.actions.some((a) => isTypeScriptSource(a.entry)) ||
|
|
157
|
-
plan.dependencies.modulesAndAssets.some((m) => (m.type === "module" || m.type === "file") && isTypeScriptSource(m.path));
|
|
158
|
-
if (!hasTypeScript) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const ts = await resolveTypeScriptCompiler(root);
|
|
162
|
-
const tsSourceFiles = new Set();
|
|
163
|
-
for (const act of plan.actions) {
|
|
164
|
-
if (!isOwnAction(act))
|
|
165
|
-
continue;
|
|
166
|
-
if (isTypeScriptSource(act.entry)) {
|
|
167
|
-
tsSourceFiles.add(act.resolvedPath);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
for (const mod of plan.dependencies.modulesAndAssets) {
|
|
171
|
-
if ((mod.type === "module" || mod.type === "file") && isTypeScriptSource(mod.path)) {
|
|
172
|
-
tsSourceFiles.add(mod.resolvedPath);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const compilerOptions = {
|
|
176
|
-
target: ts.ScriptTarget.ES2022,
|
|
177
|
-
module: ts.ModuleKind.NodeNext,
|
|
178
|
-
moduleResolution: ts.ModuleResolutionKind.NodeNext,
|
|
179
|
-
declaration: true,
|
|
180
|
-
emitDeclarationOnly: false,
|
|
181
|
-
rewriteRelativeImportExtensions: true,
|
|
182
|
-
rootDir: root,
|
|
183
|
-
outDir: stagingPkgDir,
|
|
184
|
-
skipLibCheck: true,
|
|
185
|
-
esModuleInterop: true,
|
|
186
|
-
allowSyntheticDefaultImports: true,
|
|
187
|
-
allowJs: true,
|
|
188
|
-
};
|
|
189
|
-
const tsconfigPath = join(root, "tsconfig.json");
|
|
190
|
-
if (existsSync(tsconfigPath)) {
|
|
191
|
-
try {
|
|
192
|
-
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
193
|
-
if (configFile.error) {
|
|
194
|
-
throw new Error(ts.formatDiagnostic(configFile.error));
|
|
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
|
-
});
|
|
212
|
-
}
|
|
213
|
-
catch (err) {
|
|
214
|
-
if (err instanceof BuilderError) {
|
|
215
|
-
throw err;
|
|
216
|
-
}
|
|
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}`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
const host = ts.createCompilerHost(compilerOptions);
|
|
224
|
-
const program = ts.createProgram(Array.from(tsSourceFiles), compilerOptions, host);
|
|
225
|
-
const emitResult = program.emit();
|
|
226
|
-
const preEmitDiagnostics = ts.getPreEmitDiagnostics(program);
|
|
227
|
-
const allDiagnostics = [...preEmitDiagnostics, ...emitResult.diagnostics];
|
|
228
|
-
const errors = allDiagnostics.filter((d) => d.category === ts.DiagnosticCategory.Error);
|
|
229
|
-
if (errors.length > 0 || emitResult.emitSkipped) {
|
|
230
|
-
const diagnosticsToFormat = errors.length > 0 ? errors : allDiagnostics;
|
|
231
|
-
const formatted = ts.formatDiagnosticsWithColorAndContext(diagnosticsToFormat, {
|
|
232
|
-
getCanonicalFileName: (f) => f,
|
|
233
|
-
getCurrentDirectory: () => root,
|
|
234
|
-
getNewLine: () => "\n",
|
|
235
|
-
});
|
|
236
|
-
throw new BuilderError(`TypeScript compilation failed during pack:\n${formatted}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* 拷贝声明的非 TypeScript 模块、静态资产、原生 JavaScript 入口、Playbook 与基础文档到暂存目录。
|
|
241
|
-
* 拷贝内核统一复用 stage-sources 的 copyPlanEntries,仅通过谓词分化编译排除差异。
|
|
124
|
+
* 拷贝声明的 Action 源码、模块、静态资产、Playbook 与基础文档到暂存目录。
|
|
125
|
+
* 依托 Node 24 原生类型擦除,完整保留 TypeScript 源码与声明资产。
|
|
242
126
|
*/
|
|
243
127
|
function stageSources(root, stagingPkgDir, plan) {
|
|
244
128
|
copyPlanEntries(stagingPkgDir, plan, {
|
|
245
|
-
//
|
|
246
|
-
copyAction: (act) => !isTypeScriptSource(act.entry),
|
|
247
|
-
copyModule: (dep) => !isTypeScriptSource(dep.path),
|
|
248
|
-
// pack 产物保留源工程相对路径(与编译输出的目录结构一致)
|
|
129
|
+
// pack 产物保留源工程相对路径
|
|
249
130
|
playbookRelPath: (pb) => relative(root, pb.filePath),
|
|
250
131
|
});
|
|
251
132
|
// 拷贝 README 等基础文档(若存在)
|
|
@@ -256,42 +137,22 @@ function stageSources(root, stagingPkgDir, plan) {
|
|
|
256
137
|
}
|
|
257
138
|
}
|
|
258
139
|
}
|
|
259
|
-
/**
|
|
260
|
-
* 计算编译后的 Action 入口相对路径(.ts 转 .js、.mts 转 .mjs)。
|
|
261
|
-
*/
|
|
262
|
-
function compiledEntryFor(action) {
|
|
263
|
-
if (action.entry.endsWith(".ts")) {
|
|
264
|
-
return action.entry.slice(0, -3) + ".js";
|
|
265
|
-
}
|
|
266
|
-
if (action.entry.endsWith(".mts")) {
|
|
267
|
-
return action.entry.slice(0, -4) + ".mjs";
|
|
268
|
-
}
|
|
269
|
-
return action.entry;
|
|
270
|
-
}
|
|
271
140
|
/**
|
|
272
141
|
* 生成并写入严格校验后的 actiondock.json 与规范化 package.json。
|
|
273
142
|
*/
|
|
274
143
|
function writeManifestAndPkgJson(stagingPkgDir, plan, sourcePkgJson) {
|
|
275
|
-
// 构建编译后的 Action 清单字典并严格校验入口扩展名
|
|
276
|
-
const compiledEntries = new Map();
|
|
277
144
|
for (const act of plan.actions) {
|
|
278
145
|
if (!isOwnAction(act))
|
|
279
146
|
continue;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
throw new BuilderError(`Action '${act.id}' entry '${destEntry}' must point to compiled .js or .mjs`);
|
|
283
|
-
}
|
|
284
|
-
if (!existsSync(join(stagingPkgDir, destEntry))) {
|
|
285
|
-
throw new BuilderError(`Compiled action entry file not found: ${destEntry}`);
|
|
147
|
+
if (!existsSync(join(stagingPkgDir, act.entry))) {
|
|
148
|
+
throw new BuilderError(`Action entry file not found: ${act.entry}`);
|
|
286
149
|
}
|
|
287
|
-
compiledEntries.set(act.id, destEntry);
|
|
288
150
|
}
|
|
289
151
|
// 严格生成与校验 actiondock.json(严禁包含废弃 actionsDir 或 playbooksDir 字段)
|
|
290
152
|
const packedConfig = serializePlanManifest(plan, {
|
|
291
153
|
includeSchema: true,
|
|
292
154
|
configBeforeActions: true,
|
|
293
155
|
includePlaybooks: false,
|
|
294
|
-
actionEntryOverride: (act) => compiledEntries.get(act.id),
|
|
295
156
|
});
|
|
296
157
|
if ("actionsDir" in packedConfig || "playbooksDir" in packedConfig) {
|
|
297
158
|
throw new BuilderError("Packed manifest must not contain deprecated fields 'actionsDir' or 'playbooksDir'.");
|
|
@@ -304,68 +165,3 @@ function writeManifestAndPkgJson(stagingPkgDir, plan, sourcePkgJson) {
|
|
|
304
165
|
const normalizedPkg = normalizePkgExportsAndEngines(sourcePkgJson);
|
|
305
166
|
writeFileSync(join(stagingPkgDir, "package.json"), JSON.stringify(normalizedPkg, null, 2) + "\n", "utf-8");
|
|
306
167
|
}
|
|
307
|
-
/**
|
|
308
|
-
* 在暂存目录调用带有 --ignore-scripts 的 npm pack 生成标准 npm 压缩包(.tgz)。
|
|
309
|
-
* 优先解析 --json 结构化输出,失败时回退到尾部行文件名解析。
|
|
310
|
-
* Windows 兼容:npm 是 .cmd 脚本,无 shell 直接 spawn 会 ENOENT/EINVAL。
|
|
311
|
-
*/
|
|
312
|
-
async function runNpmPack(stagingPkgDir, packDestination, fallbackName) {
|
|
313
|
-
const { stdout } = await new Promise((resolvePromise, rejectPromise) => {
|
|
314
|
-
const child = spawn("npm", ["pack", "--ignore-scripts", "--json", "--pack-destination", packDestination], {
|
|
315
|
-
cwd: stagingPkgDir,
|
|
316
|
-
shell: process.platform === "win32",
|
|
317
|
-
});
|
|
318
|
-
let stdoutBuf = "";
|
|
319
|
-
let stderrBuf = "";
|
|
320
|
-
child.stdout.on("data", (chunk) => (stdoutBuf += chunk));
|
|
321
|
-
child.stderr.on("data", (chunk) => (stderrBuf += chunk));
|
|
322
|
-
child.on("error", rejectPromise);
|
|
323
|
-
child.on("close", (code) => {
|
|
324
|
-
if (code === 0) {
|
|
325
|
-
resolvePromise({ stdout: stdoutBuf, stderr: stderrBuf });
|
|
326
|
-
}
|
|
327
|
-
else {
|
|
328
|
-
rejectPromise(new BuilderError(`npm pack exited with code ${code}. Output:\n${stdoutBuf}\n${stderrBuf}`));
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
});
|
|
332
|
-
// 优先解析 --json 结构化输出中的压缩包文件名
|
|
333
|
-
let generatedName;
|
|
334
|
-
const trimmed = stdout.trim();
|
|
335
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
336
|
-
try {
|
|
337
|
-
const parsed = JSON.parse(trimmed);
|
|
338
|
-
let items;
|
|
339
|
-
if (Array.isArray(parsed)) {
|
|
340
|
-
items = parsed;
|
|
341
|
-
}
|
|
342
|
-
else if (typeof parsed === "object" && parsed !== null) {
|
|
343
|
-
// npm >= 11 的 pack --json 输出为按包名索引的对象,取全部包含 filename 的条目
|
|
344
|
-
items = Object.values(parsed).filter((entry) => typeof entry === "object" && entry !== null && "filename" in entry);
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
items = [];
|
|
348
|
-
}
|
|
349
|
-
// 多包输出时取最后一个 filename(npm 按依赖顺序逐包输出,末尾为当前项目产物)
|
|
350
|
-
const filenames = items
|
|
351
|
-
.map((item) => item.filename)
|
|
352
|
-
.filter((name) => typeof name === "string" && name.length > 0);
|
|
353
|
-
const filename = filenames[filenames.length - 1];
|
|
354
|
-
if (filename !== undefined) {
|
|
355
|
-
generatedName = filename;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
catch {
|
|
359
|
-
// 回退到尾部行解析
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
if (!generatedName) {
|
|
363
|
-
const packLines = trimmed.split("\n");
|
|
364
|
-
generatedName = packLines.pop()?.trim() || fallbackName;
|
|
365
|
-
}
|
|
366
|
-
const generatedTarball = join(packDestination, generatedName);
|
|
367
|
-
if (!existsSync(generatedTarball)) {
|
|
368
|
-
throw new BuilderError(`npm pack did not produce expected tarball at ${generatedTarball}. Output:\n${stdout}`);
|
|
369
|
-
}
|
|
370
|
-
return generatedTarball;
|
|
371
|
-
}
|
package/dist/planner.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
1
|
+
import { existsSync, statSync } from "node:fs";
|
|
3
2
|
import { basename, join, relative, resolve } from "node:path";
|
|
4
|
-
import { loadProjectConfig, } from "@actiondock/core";
|
|
3
|
+
import { ActionDockError, ACTION_NOT_FOUND, UNDECLARED_ACTION_DEPENDENCY, loadProjectConfig, } from "@actiondock/core";
|
|
5
4
|
import { assertPathWithinRoot, traverseDirectory, loadManifest, loadPlaybooks, validateManifest, } from "@actiondock/core/project";
|
|
6
5
|
import { DefaultActionCatalog, PackageGraphBuilder, resolveAction, } from "@actiondock/core/graph";
|
|
7
6
|
import { PlannerError } from "./errors.js";
|
|
8
7
|
import { assertRelativeDependenciesIntegrity } from "./dependency-check.js";
|
|
8
|
+
import { computeLockfileInfo } from "./lockfile-resolver.js";
|
|
9
|
+
import { extractExternalDependencies, isIgnoredPath } from "./dependency-resolver.js";
|
|
9
10
|
/**
|
|
10
11
|
* 递归扫描指定目录下的文件,返回绝对路径列表。
|
|
11
12
|
* 目录遍历统一复用 core 的 traverseDirectory 单一事实源,
|
|
@@ -14,83 +15,6 @@ import { assertRelativeDependenciesIntegrity } from "./dependency-check.js";
|
|
|
14
15
|
function walkDirectory(dir) {
|
|
15
16
|
return traverseDirectory(dir).map((entry) => entry.fullPath);
|
|
16
17
|
}
|
|
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
|
-
];
|
|
31
|
-
/**
|
|
32
|
-
* 忽略的文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
33
|
-
*/
|
|
34
|
-
function isIgnoredPath(relPath) {
|
|
35
|
-
const normalized = relPath.replace(/\\/g, "/");
|
|
36
|
-
return (normalized.startsWith("node_modules/") ||
|
|
37
|
-
normalized.includes("/node_modules/") ||
|
|
38
|
-
normalized.startsWith(".git/") ||
|
|
39
|
-
normalized.startsWith("dist/") ||
|
|
40
|
-
normalized.startsWith(".actiondock/") ||
|
|
41
|
-
TEST_FILE_SUFFIXES.some((suffix) => normalized.endsWith(suffix)));
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* 支持的包管理器锁文件候选列表。
|
|
45
|
-
*/
|
|
46
|
-
const KNOWN_LOCKFILES = [
|
|
47
|
-
"package-lock.json",
|
|
48
|
-
"npm-shrinkwrap.json",
|
|
49
|
-
"bun.lockb",
|
|
50
|
-
"bun.lock",
|
|
51
|
-
"pnpm-lock.yaml",
|
|
52
|
-
"yarn.lock",
|
|
53
|
-
];
|
|
54
|
-
/**
|
|
55
|
-
* 读取并计算项目锁文件元数据及 SHA-256 摘要。
|
|
56
|
-
*/
|
|
57
|
-
function computeLockfileInfo(projectRoot, preferredLockfile) {
|
|
58
|
-
if (preferredLockfile) {
|
|
59
|
-
const lockPath = resolve(projectRoot, preferredLockfile);
|
|
60
|
-
assertPathWithinRoot(projectRoot, lockPath, "lockfile");
|
|
61
|
-
if (existsSync(lockPath) && statSync(lockPath).isFile()) {
|
|
62
|
-
const content = readFileSync(lockPath);
|
|
63
|
-
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
64
|
-
return {
|
|
65
|
-
name: basename(lockPath),
|
|
66
|
-
path: lockPath,
|
|
67
|
-
sha256,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
throw new PlannerError(`Specified lockfile not found on disk: ${preferredLockfile}`, "LOCKFILE_NOT_FOUND");
|
|
71
|
-
}
|
|
72
|
-
for (const lockFileName of KNOWN_LOCKFILES) {
|
|
73
|
-
const lockPath = join(projectRoot, lockFileName);
|
|
74
|
-
if (existsSync(lockPath)) {
|
|
75
|
-
try {
|
|
76
|
-
if (statSync(lockPath).isFile()) {
|
|
77
|
-
const content = readFileSync(lockPath);
|
|
78
|
-
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
79
|
-
return {
|
|
80
|
-
name: lockFileName,
|
|
81
|
-
path: lockPath,
|
|
82
|
-
sha256,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
}
|
|
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");
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return undefined;
|
|
93
|
-
}
|
|
94
18
|
/**
|
|
95
19
|
* 构造备用清单映射,仅读取文件系统条目与配置声明,杜绝 AST 源码分析与动态代码执行。
|
|
96
20
|
*/
|
|
@@ -141,46 +65,6 @@ function generateFallbackManifest(projectRoot, actionsDir = "actions", config) {
|
|
|
141
65
|
actions,
|
|
142
66
|
};
|
|
143
67
|
}
|
|
144
|
-
/**
|
|
145
|
-
* 收集项目根目录 package.json 中声明的外部 npm 依赖。
|
|
146
|
-
*
|
|
147
|
-
* 防御与透明原则:文件不存在返回空集合(合法的无依赖工程);
|
|
148
|
-
* 文件存在但不可读或 JSON 损坏时抛 PlannerError 并携带路径与原因,
|
|
149
|
-
* 严禁静默吞掉导致后续 build 的生产依赖、pack 的产物依赖与 vendorDeps 全部无声缺失。
|
|
150
|
-
*/
|
|
151
|
-
function extractExternalDependencies(projectRoot) {
|
|
152
|
-
const pkgPath = join(projectRoot, "package.json");
|
|
153
|
-
if (!existsSync(pkgPath))
|
|
154
|
-
return [];
|
|
155
|
-
let parsed;
|
|
156
|
-
try {
|
|
157
|
-
const raw = readFileSync(pkgPath, "utf-8");
|
|
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
|
-
});
|
|
171
|
-
}
|
|
172
|
-
}
|
|
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
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return deps;
|
|
183
|
-
}
|
|
184
68
|
/**
|
|
185
69
|
* 声明式选择集与构建规划器 (SelectionPlanner)。
|
|
186
70
|
* 仅读取 actiondock.json、锁文件与显式构建参数,彻底移除 TypeScript AST 源码扫描。
|
|
@@ -314,54 +198,48 @@ export class SelectionPlanner {
|
|
|
314
198
|
let entry = manifestActions[currentId] || config?.actions?.[currentId];
|
|
315
199
|
let entryRoot = root;
|
|
316
200
|
if (!entry) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
queue.push(trimmedUse);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
201
|
+
const resolved = resolveAction(currentId, { graph, catalog, caller: config?.id });
|
|
202
|
+
const resolvedPkgId = resolved.package.id;
|
|
203
|
+
const resolvedActionId = resolved.ref.actionId;
|
|
204
|
+
const extNode = graph.packages.get(resolvedPkgId);
|
|
205
|
+
if (extNode && existsSync(extNode.root)) {
|
|
206
|
+
const extRoot = extNode.root;
|
|
207
|
+
let extConfig;
|
|
208
|
+
try {
|
|
209
|
+
extConfig = loadProjectConfig(extRoot);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
extConfig = {
|
|
213
|
+
id: resolvedPkgId,
|
|
214
|
+
name: resolvedPkgId,
|
|
215
|
+
version: "0.1.0",
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const externalManifest = loadManifest(extRoot);
|
|
219
|
+
const externalEntry = externalManifest?.actions?.[resolvedActionId] ||
|
|
220
|
+
extConfig?.actions?.[resolvedActionId];
|
|
221
|
+
if (!externalEntry) {
|
|
222
|
+
throw new ActionDockError(UNDECLARED_ACTION_DEPENDENCY, `UNDECLARED_ACTION_DEPENDENCY: Action '${resolvedActionId}' is not declared in package '${resolvedPkgId}' manifest`);
|
|
223
|
+
}
|
|
224
|
+
entry = externalEntry;
|
|
225
|
+
entryRoot = extRoot;
|
|
226
|
+
// 依赖包 actiondock.json 中声明的传递依赖闭包(入队前查重,避免重复膨胀)
|
|
227
|
+
const extPkgUses = extConfig?.uses;
|
|
228
|
+
if (Array.isArray(extPkgUses)) {
|
|
229
|
+
for (const u of extPkgUses) {
|
|
230
|
+
const trimmedUse = typeof u === "string" ? u.trim() : "";
|
|
231
|
+
if (trimmedUse &&
|
|
232
|
+
!resolvedActionIds.has(trimmedUse) &&
|
|
233
|
+
!queued.has(trimmedUse)) {
|
|
234
|
+
queued.add(trimmedUse);
|
|
235
|
+
queue.push(trimmedUse);
|
|
355
236
|
}
|
|
356
237
|
}
|
|
357
238
|
}
|
|
358
239
|
}
|
|
359
|
-
catch {
|
|
360
|
-
// 外部包解析失败时交由下方缺失依赖报错统一透传
|
|
361
|
-
}
|
|
362
240
|
}
|
|
363
241
|
if (!entry) {
|
|
364
|
-
throw new
|
|
242
|
+
throw new ActionDockError(ACTION_NOT_FOUND, `Action '${currentId}' referenced in dependency closure (uses) was not found in manifest or linked packages`);
|
|
365
243
|
}
|
|
366
244
|
if (entryRoot !== root) {
|
|
367
245
|
externalActionRoots.set(currentId, entryRoot);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 复合技能自定义说明书(SKILL.custom.md)的槽位,
|
|
3
|
+
* 决定自定义段落插入到官方模板生成结果中的位置。
|
|
4
|
+
*/
|
|
5
|
+
export type CompositeCustomSlot = "intro" | "after-init" | "after-describe" | "after-actions" | "after-playbooks" | "after-invoke" | "append";
|
|
6
|
+
export declare const COMPOSITE_CUSTOM_SLOTS: readonly CompositeCustomSlot[];
|
|
7
|
+
/** 工作区内约定俗成的复合技能自定义说明书文件名。 */
|
|
8
|
+
export declare const COMPOSITE_CUSTOM_DECLARATION_FILE = "SKILL.custom.md";
|
|
9
|
+
export interface CompositeCustomSection {
|
|
10
|
+
slot: CompositeCustomSlot;
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
export interface CompositeCustomDeclaration {
|
|
14
|
+
/** 自定义说明书 frontmatter 中声明的 description 覆盖值 */
|
|
15
|
+
description?: string;
|
|
16
|
+
sections: CompositeCustomSection[];
|
|
17
|
+
}
|
|
18
|
+
export declare const CUSTOM_SLOT_MARKER_PATTERN: RegExp;
|
|
19
|
+
/**
|
|
20
|
+
* 解析自定义说明书正文:以 `<!-- actiondock:slot <name> -->` 标记行分段。
|
|
21
|
+
* 首个标记之前的内容归入 `append` 槽位;未知槽位名直接抛错,避免拼写错误被静默吞掉。
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseCustomSections(source: string): CompositeCustomSection[];
|
|
24
|
+
/**
|
|
25
|
+
* 解析复合技能自定义说明书文件:支持 YAML frontmatter 中的 `description` 覆盖,
|
|
26
|
+
* 正文按槽位标记解析为自定义段落。
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseCustomSkillDeclaration(source: string): CompositeCustomDeclaration;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { BuilderError } from "../errors.js";
|
|
2
|
+
export const COMPOSITE_CUSTOM_SLOTS = [
|
|
3
|
+
"intro",
|
|
4
|
+
"after-init",
|
|
5
|
+
"after-describe",
|
|
6
|
+
"after-actions",
|
|
7
|
+
"after-playbooks",
|
|
8
|
+
"after-invoke",
|
|
9
|
+
"append",
|
|
10
|
+
];
|
|
11
|
+
/** 工作区内约定俗成的复合技能自定义说明书文件名。 */
|
|
12
|
+
export const COMPOSITE_CUSTOM_DECLARATION_FILE = "SKILL.custom.md";
|
|
13
|
+
export const CUSTOM_SLOT_MARKER_PATTERN = /^\s*<!--\s*actiondock:slot\s+([a-zA-Z][a-zA-Z0-9-]*)\s*-->\s*$/;
|
|
14
|
+
/**
|
|
15
|
+
* 解析自定义说明书正文:以 `<!-- actiondock:slot <name> -->` 标记行分段。
|
|
16
|
+
* 首个标记之前的内容归入 `append` 槽位;未知槽位名直接抛错,避免拼写错误被静默吞掉。
|
|
17
|
+
*/
|
|
18
|
+
export function parseCustomSections(source) {
|
|
19
|
+
const sections = [];
|
|
20
|
+
let currentSlot = "append";
|
|
21
|
+
let buffer = [];
|
|
22
|
+
const flush = () => {
|
|
23
|
+
const content = buffer.join("\n").trim();
|
|
24
|
+
buffer = [];
|
|
25
|
+
if (content) {
|
|
26
|
+
sections.push({ slot: currentSlot, content });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
for (const line of source.split(/\r?\n/)) {
|
|
30
|
+
const match = line.match(CUSTOM_SLOT_MARKER_PATTERN);
|
|
31
|
+
if (match) {
|
|
32
|
+
flush();
|
|
33
|
+
const slot = match[1];
|
|
34
|
+
if (!COMPOSITE_CUSTOM_SLOTS.includes(slot)) {
|
|
35
|
+
throw new BuilderError(`Unknown ActionDock custom slot '${match[1]}'. Valid slots: ${COMPOSITE_CUSTOM_SLOTS.join(", ")}`, "UNKNOWN_CUSTOM_SLOT");
|
|
36
|
+
}
|
|
37
|
+
currentSlot = slot;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
buffer.push(line);
|
|
41
|
+
}
|
|
42
|
+
flush();
|
|
43
|
+
return sections;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 解析复合技能自定义说明书文件:支持 YAML frontmatter 中的 `description` 覆盖,
|
|
47
|
+
* 正文按槽位标记解析为自定义段落。
|
|
48
|
+
*/
|
|
49
|
+
export function parseCustomSkillDeclaration(source) {
|
|
50
|
+
let body = source;
|
|
51
|
+
let description;
|
|
52
|
+
const frontmatter = source.match(/^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/);
|
|
53
|
+
if (frontmatter) {
|
|
54
|
+
body = source.slice(frontmatter[0].length);
|
|
55
|
+
const descLine = frontmatter[1].match(/^description:[ \t]*(.+?)[ \t]*$/m);
|
|
56
|
+
if (descLine) {
|
|
57
|
+
description = descLine[1].replace(/^["']/g, "").replace(/["']$/g, "");
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { description, sections: parseCustomSections(body) };
|
|
61
|
+
}
|
package/dist/skill/index.d.ts
CHANGED
package/dist/skill/index.js
CHANGED