@actiondock/builder 2.2.2 → 2.3.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.d.ts +5 -0
- package/dist/archive.js +7 -2
- package/dist/build.js +24 -12
- package/dist/dependency-check.js +9 -2
- package/dist/exporter.js +32 -10
- package/dist/fs-utils.d.ts +3 -2
- package/dist/fs-utils.js +59 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pack.js +38 -10
- package/dist/planner.js +13 -5
- package/package.json +4 -4
package/dist/archive.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { Readable, Transform, Writable } from "node:stream";
|
|
2
|
+
/** Unix 毫秒时间戳转 DOS 时间格式(date/time 各 16 位) */
|
|
3
|
+
export declare function dosDateTime(ms: number): {
|
|
4
|
+
date: number;
|
|
5
|
+
time: number;
|
|
6
|
+
};
|
|
2
7
|
/**
|
|
3
8
|
* 将目录打包为标准 zip 归档(PKZIP APPNOTE 结构)。
|
|
4
9
|
* 直接流式分块写入磁盘,避免在内存中积压全量归档内容。
|
package/dist/archive.js
CHANGED
|
@@ -92,9 +92,14 @@ function crc32(buf) {
|
|
|
92
92
|
return (updateCrc32(0xffffffff, buf) ^ 0xffffffff) >>> 0;
|
|
93
93
|
}
|
|
94
94
|
/** Unix 毫秒时间戳转 DOS 时间格式(date/time 各 16 位) */
|
|
95
|
-
function dosDateTime(ms) {
|
|
95
|
+
export function dosDateTime(ms) {
|
|
96
96
|
const d = new Date(ms);
|
|
97
|
-
const
|
|
97
|
+
const rawYear = d.getFullYear();
|
|
98
|
+
// DOS 时间字段边界:年份仅能表达 [1980, 2107],超出统一映射为 1980-01-01 00:00:00
|
|
99
|
+
const year = Math.min(Math.max(rawYear, 1980), 2107);
|
|
100
|
+
if (rawYear < 1980) {
|
|
101
|
+
return { date: ((1980 - 1980) << 9) | (1 << 5) | 1, time: 0 };
|
|
102
|
+
}
|
|
98
103
|
const time = (d.getHours() << 11) | (d.getMinutes() << 5) | (d.getSeconds() >> 1);
|
|
99
104
|
const date = ((year - 1980) << 9) | ((d.getMonth() + 1) << 5) | d.getDate();
|
|
100
105
|
return { date, time };
|
package/dist/build.js
CHANGED
|
@@ -62,13 +62,17 @@ function calculateDirectoryDigest(dir) {
|
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* 生成 Host 子进程入口脚本源码(负责运行 ActionDockHost,通过 Node IPC 暴露 Target)。
|
|
65
|
+
* 与 serializePlanManifest / stageSources 的过滤条件保持一致:跨包外部 Action 不进入 import 与运行时注册,
|
|
66
|
+
* 避免入口 import 清单没有的源文件产生孤儿模块(外部源码未物化进本包目录,运行时必然加载失败)。
|
|
65
67
|
*/
|
|
66
|
-
function generateNodeHostEntrySource(plan
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
function generateNodeHostEntrySource(plan) {
|
|
69
|
+
// 仅包自有 Action 参与 import 与注册,与产物清单保持同一事实源
|
|
70
|
+
const ownActions = plan.actions.filter((a) => !a.isExternal && !a.id.includes("/"));
|
|
71
|
+
const imports = ownActions
|
|
72
|
+
.map((act, idx) => `import action_${idx} from ${JSON.stringify(`./${act.entry.replace(/\\/g, "/")}`)};`)
|
|
69
73
|
.join("\n");
|
|
70
74
|
const actionsDict = {};
|
|
71
|
-
for (const a of
|
|
75
|
+
for (const a of ownActions) {
|
|
72
76
|
actionsDict[a.id] = {
|
|
73
77
|
entry: a.entry,
|
|
74
78
|
description: a.description || "",
|
|
@@ -79,7 +83,7 @@ function generateNodeHostEntrySource(plan, relativeActionPaths) {
|
|
|
79
83
|
annotations: a.annotations || {},
|
|
80
84
|
};
|
|
81
85
|
}
|
|
82
|
-
const actionItems =
|
|
86
|
+
const actionItems = ownActions
|
|
83
87
|
.map((a, idx) => {
|
|
84
88
|
const entryObj = `{
|
|
85
89
|
...(typeof action_${idx} === "function" ? { run: action_${idx} } : action_${idx}),
|
|
@@ -278,11 +282,16 @@ try {
|
|
|
278
282
|
}
|
|
279
283
|
/**
|
|
280
284
|
* 拷贝 Action 源码、Playbook 规程与声明的代码文件/静态资产到暂存目录。
|
|
285
|
+
* 与 serializePlanManifest 的过滤条件保持一致:跨包外部 Action 不物化进本包目录,
|
|
286
|
+
* 避免产物混入外部源文件与本包入口发生非确定性覆盖。
|
|
281
287
|
*/
|
|
282
288
|
function stageSources(root, stagingDir, plan) {
|
|
283
|
-
// 拷贝 Action
|
|
289
|
+
// 拷贝 Action 源码文件,保留相对路径(仅拷贝包自有 Action,跨包外部依赖不物化进本包目录)
|
|
284
290
|
const relativeActionImports = [];
|
|
285
291
|
for (const act of plan.actions) {
|
|
292
|
+
if (act.isExternal || act.id.includes("/")) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
286
295
|
if (existsSync(act.resolvedPath)) {
|
|
287
296
|
const destFile = join(stagingDir, act.entry);
|
|
288
297
|
mkdirSync(dirname(destFile), { recursive: true });
|
|
@@ -365,8 +374,8 @@ function writePkgJsonAndLockfiles(root, stagingDir, plan, pkgSlug) {
|
|
|
365
374
|
/**
|
|
366
375
|
* 生成三个入口脚本(Host 子进程、监督父进程与兼容代理转发入口)并设置可执行权限。
|
|
367
376
|
*/
|
|
368
|
-
function writeEntrypoints(stagingDir, plan
|
|
369
|
-
const hostCode = generateNodeHostEntrySource(plan
|
|
377
|
+
function writeEntrypoints(stagingDir, plan) {
|
|
378
|
+
const hostCode = generateNodeHostEntrySource(plan);
|
|
370
379
|
const hostPath = join(stagingDir, "entry-host.js");
|
|
371
380
|
writeFileSync(hostPath, hostCode, "utf-8");
|
|
372
381
|
const supervisorCode = generateNodeSupervisorEntrySource(plan);
|
|
@@ -395,15 +404,18 @@ function vendorDependencies(root, stagingDir, plan) {
|
|
|
395
404
|
join(root, "node_modules"),
|
|
396
405
|
resolve(import.meta.dirname, "../../../node_modules"),
|
|
397
406
|
];
|
|
407
|
+
// 嵌套 node_modules 不再跳过:多版本共存时传递依赖必须完整物化,
|
|
408
|
+
// 否则产物运行时 Cannot find module 且构建阶段无任何警告;
|
|
409
|
+
// 保留 .git、.bin 等目录排除(.bin 为平台相关的命令行 shim,不参与运行时模块解析)
|
|
398
410
|
const copyVendorPackage = (srcDir, destDir) => {
|
|
399
411
|
if (!existsSync(srcDir))
|
|
400
412
|
return;
|
|
401
413
|
mkdirSync(destDir, { recursive: true });
|
|
402
414
|
const entries = readdirSync(srcDir);
|
|
403
415
|
for (const entry of entries) {
|
|
404
|
-
if (entry === "
|
|
405
|
-
entry === ".git" ||
|
|
416
|
+
if (entry === ".git" ||
|
|
406
417
|
entry === ".actiondock" ||
|
|
418
|
+
entry === ".bin" ||
|
|
407
419
|
entry === "test" ||
|
|
408
420
|
entry === "tests") {
|
|
409
421
|
continue;
|
|
@@ -500,10 +512,10 @@ export async function buildProject(options) {
|
|
|
500
512
|
mkdirSync(stagingDir, { recursive: true });
|
|
501
513
|
let platformInfo;
|
|
502
514
|
try {
|
|
503
|
-
|
|
515
|
+
stageSources(root, stagingDir, plan);
|
|
504
516
|
writeManifest(stagingDir, plan);
|
|
505
517
|
writePkgJsonAndLockfiles(root, stagingDir, plan, pkgSlug);
|
|
506
|
-
writeEntrypoints(stagingDir, plan
|
|
518
|
+
writeEntrypoints(stagingDir, plan);
|
|
507
519
|
// 若开启 options.vendorDeps,物化锁定生产依赖
|
|
508
520
|
if (options.vendorDeps) {
|
|
509
521
|
platformInfo = {
|
package/dist/dependency-check.js
CHANGED
|
@@ -222,8 +222,15 @@ export function assertRelativeDependenciesIntegrity(projectRoot, plan) {
|
|
|
222
222
|
}
|
|
223
223
|
// 检查目标文件是否越出项目根目录
|
|
224
224
|
const relToRoot = relative(realRoot, resolvedTarget);
|
|
225
|
-
if (isPathOutsideBoundary(relToRoot)
|
|
226
|
-
|
|
225
|
+
if (isPathOutsideBoundary(relToRoot)) {
|
|
226
|
+
// 区分「项目外路径」与「monorepo 相邻包」两种情形,给出针对性修复指引
|
|
227
|
+
const resolvedParent = dirname(realRoot);
|
|
228
|
+
const relToParent = relative(resolvedParent, resolvedTarget);
|
|
229
|
+
const isSiblingPackage = !isPathOutsideBoundary(relToParent);
|
|
230
|
+
const detail = isSiblingPackage
|
|
231
|
+
? `它位于项目根的父目录内,疑似 monorepo 相邻包。若确需依赖,请将其纳入本项目或改用包管理器依赖声明;若为本地辅助模块,请调整目录结构或将其声明进 files`
|
|
232
|
+
: `它完全位于项目外部。导出的 Skill 产物不允许携带项目外部的相对路径依赖,请将所需模块移入项目内并声明进 files`;
|
|
233
|
+
throw new BuilderError(`Action '${actionId}' 通过相对路径 '${specifier}' 引用了项目根之外的模块(解析到 '${resolvedTarget}')。${detail}`, "EXTERNAL_LOCAL_DEPENDENCY");
|
|
227
234
|
}
|
|
228
235
|
// 检查目标文件是否在构建收集集合中
|
|
229
236
|
if (!collectedPaths.has(resolvedTarget)) {
|
package/dist/exporter.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { COMPOSITE_CUSTOM_DECLARATION_FILE, generateCompositeSkillMd, generateSourceSkillMd, generateStandaloneSkillMd, getPackageSlug, loadManifest, loadPlaybooks, loadProjectConfig, parseCustomSkillDeclaration, } from "@actiondock/core";
|
|
4
4
|
import { createTarGzArchiveAsync, createZipArchiveAsync } from "./archive.js";
|
|
@@ -21,24 +21,33 @@ function toPlaybookDefinitions(playbooks) {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* 执行归档压缩操作。
|
|
24
|
+
* 先压缩到临时路径,成功后原子重命名覆盖最终路径;失败时清理临时文件后透传原始异常,
|
|
25
|
+
* 避免旧档已删、新档半成品残留的中间态。
|
|
24
26
|
*/
|
|
25
27
|
async function createArchive(skillDir, format) {
|
|
26
28
|
const parentDir = dirname(skillDir);
|
|
27
29
|
const folderName = basename(skillDir);
|
|
28
30
|
const archiveName = `${folderName}.${format === "tar.gz" ? "tar.gz" : "zip"}`;
|
|
29
31
|
const archivePath = join(parentDir, archiveName);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const tempArchivePath = `${archivePath}.tmp`;
|
|
33
|
+
if (existsSync(tempArchivePath)) {
|
|
34
|
+
// 清理上次失败可能残留的临时文件(兼容目录形态,避免阻塞本次压缩)
|
|
35
|
+
rmSync(tempArchivePath, { force: true, recursive: true, maxRetries: 3, retryDelay: 50 });
|
|
32
36
|
}
|
|
33
37
|
try {
|
|
34
38
|
if (format === "tar.gz") {
|
|
35
|
-
await createTarGzArchiveAsync(skillDir,
|
|
39
|
+
await createTarGzArchiveAsync(skillDir, tempArchivePath);
|
|
36
40
|
}
|
|
37
41
|
else {
|
|
38
|
-
await createZipArchiveAsync(skillDir,
|
|
42
|
+
await createZipArchiveAsync(skillDir, tempArchivePath);
|
|
39
43
|
}
|
|
44
|
+
renameSync(tempArchivePath, archivePath);
|
|
40
45
|
}
|
|
41
46
|
catch (err) {
|
|
47
|
+
// 压缩失败:清理临时半成品,旧档保持原样,透传原始异常
|
|
48
|
+
if (existsSync(tempArchivePath)) {
|
|
49
|
+
rmSync(tempArchivePath, { force: true, recursive: true, maxRetries: 3, retryDelay: 50 });
|
|
50
|
+
}
|
|
42
51
|
throw new BuilderError(`Failed to create ${format} archive: ${err?.message || String(err)}`);
|
|
43
52
|
}
|
|
44
53
|
return archivePath;
|
|
@@ -108,9 +117,8 @@ export function findExistingCompositeSkillMd(options) {
|
|
|
108
117
|
searchDirs.add(process.cwd());
|
|
109
118
|
for (const root of options.projectRoots) {
|
|
110
119
|
const absRoot = resolve(root);
|
|
111
|
-
|
|
112
|
-
searchDirs.add(
|
|
113
|
-
searchDirs.add(dirname(parentDir));
|
|
120
|
+
// 仅搜索父目录(工作区根)一级;祖父目录搜索可能命中毫不相关的全局 SKILL.md 并复制进产物
|
|
121
|
+
searchDirs.add(dirname(absRoot));
|
|
114
122
|
}
|
|
115
123
|
const candidateRelativePaths = [
|
|
116
124
|
join("skills", options.bundleName, "SKILL.md"),
|
|
@@ -356,6 +364,8 @@ function writeCompositePkgJson(stagingDir, packagesDestDir, packageInfos, bundle
|
|
|
356
364
|
catch (err) {
|
|
357
365
|
if (err instanceof BuilderError)
|
|
358
366
|
throw err;
|
|
367
|
+
// 复合导出缺依赖影响面大:读文件与解析失败必须显式报错,严禁静默吞掉导致聚合依赖缺失
|
|
368
|
+
throw new BuilderError(`Failed to read or parse package.json of exported subpackage '${info.packageDir}' (${pkgJsonPath}): ${err instanceof Error ? err.message : String(err)}`);
|
|
359
369
|
}
|
|
360
370
|
}
|
|
361
371
|
}
|
|
@@ -405,8 +415,16 @@ export class SkillExporter {
|
|
|
405
415
|
const defaultSkillDir = join(root, "dist", defaultFolderName);
|
|
406
416
|
const targetSkillDir = resolve(options.outDir || defaultSkillDir);
|
|
407
417
|
const hasExternalDeps = Boolean(plan.externalProjectRoots && plan.externalProjectRoots.length > 0);
|
|
408
|
-
//
|
|
409
|
-
|
|
418
|
+
// 单包源码导出若依赖闭包含外部包,导成 mini-workspace 形态(packages/ 下带齐依赖包),和 bundle 统一成一个交付形状
|
|
419
|
+
// 该复合分支会展开全部依赖包,无法保留调用方传入的 actions/playbooks 过滤选项;
|
|
420
|
+
// 静默丢弃过滤会导致产物全量导出,必须显式拒绝并给出调整指引(CLI 侧已有 roots>1 守卫,此处补齐 composite 分支守卫)
|
|
421
|
+
const hasFilters = Boolean((options.actions && options.actions.length > 0) ||
|
|
422
|
+
(options.playbooks && options.playbooks.length > 0));
|
|
423
|
+
const willUseComposite = mode === "source" && !options._isSubpackage && hasExternalDeps;
|
|
424
|
+
if (hasFilters && willUseComposite) {
|
|
425
|
+
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");
|
|
426
|
+
}
|
|
427
|
+
if (willUseComposite) {
|
|
410
428
|
const allProjectRoots = Array.from(new Set([root, ...plan.externalProjectRoots]));
|
|
411
429
|
const compositeRes = await this.exportComposite({
|
|
412
430
|
bundleName: plan.packageName || plan.packageId,
|
|
@@ -680,6 +698,10 @@ export class SkillExporter {
|
|
|
680
698
|
? resolve(options.outDir)
|
|
681
699
|
: resolve(options.outDir ? join(options.outDir, "SKILL.md") : join(process.cwd(), "SKILL.md"));
|
|
682
700
|
mkdirSync(dirname(targetFile), { recursive: true });
|
|
701
|
+
// 目标文件即将被覆盖时输出警告,避免无提示覆盖用户已有内容
|
|
702
|
+
if (existsSync(targetFile)) {
|
|
703
|
+
console.warn(`[WARN] Existing SKILL.md will be overwritten: ${targetFile}`);
|
|
704
|
+
}
|
|
683
705
|
writeFileSync(targetFile, compositeSkillMd, "utf-8");
|
|
684
706
|
return {
|
|
685
707
|
bundleName: options.bundleName,
|
package/dist/fs-utils.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export declare function moveDirAtomic(src: string, dest: string): Promise<void>;
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* 递归收集目录内全部文件的相对路径(正斜杠分隔)。
|
|
4
|
+
* 收集完成后对最终 POSIX 相对路径统一排序,保证跨平台 digest 一致性;
|
|
5
|
+
* 符号链接防护策略与 archive.ts collectEntries 对齐:越出根边界的软链接与循环软链接直接跳过。
|
|
5
6
|
*/
|
|
6
7
|
export declare function collectRelativeFiles(dir: string, baseDir?: string): string[];
|
|
7
8
|
/**
|
package/dist/fs-utils.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { cpSync, existsSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
1
|
+
import { cpSync, existsSync, readdirSync, realpathSync, renameSync, rmSync, statSync } from "node:fs";
|
|
2
2
|
import { join, relative, sep } from "node:path";
|
|
3
|
-
import { ACTIONDOCK_VERSION } from "@actiondock/core";
|
|
3
|
+
import { ACTIONDOCK_VERSION, assertPathWithinRoot } from "@actiondock/core";
|
|
4
4
|
/**
|
|
5
5
|
* builder 包内共享的文件系统基础设施。
|
|
6
6
|
* 消除 pack / build / exporter 三个业务模块中成体系的重复实现,
|
|
@@ -65,22 +65,71 @@ export async function moveDirAtomic(src, dest) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* 递归收集目录内全部文件的相对路径(正斜杠分隔)。
|
|
69
|
+
* 收集完成后对最终 POSIX 相对路径统一排序,保证跨平台 digest 一致性;
|
|
70
|
+
* 符号链接防护策略与 archive.ts collectEntries 对齐:越出根边界的软链接与循环软链接直接跳过。
|
|
70
71
|
*/
|
|
71
72
|
export function collectRelativeFiles(dir, baseDir = dir) {
|
|
73
|
+
const results = collectRelativeFilesUnsorted(dir, baseDir, new Set());
|
|
74
|
+
return results.sort();
|
|
75
|
+
}
|
|
76
|
+
function collectRelativeFilesUnsorted(dir, baseDir, visited) {
|
|
72
77
|
if (!existsSync(dir))
|
|
73
78
|
return [];
|
|
79
|
+
if (visited.size === 0) {
|
|
80
|
+
try {
|
|
81
|
+
visited.add(existsSync(baseDir) ? realpathSync(baseDir) : baseDir);
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// 忽略根目录真实路径解析异常,退化为不携带根真实路径的循环拦截
|
|
85
|
+
}
|
|
86
|
+
}
|
|
74
87
|
const results = [];
|
|
75
|
-
|
|
88
|
+
let entries;
|
|
89
|
+
try {
|
|
90
|
+
entries = readdirSync(dir).sort();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
76
95
|
for (const entry of entries) {
|
|
77
96
|
const fullPath = join(dir, entry);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
97
|
+
try {
|
|
98
|
+
assertPathWithinRoot(baseDir, fullPath, "collect files path");
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
let real;
|
|
104
|
+
try {
|
|
105
|
+
real = existsSync(fullPath) ? realpathSync(fullPath) : fullPath;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
assertPathWithinRoot(baseDir, real, "collect files path");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// 忽略并跳过指向 baseDir 外部的软链接,与归档防护策略保持一致
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (visited.has(real)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
visited.add(real);
|
|
121
|
+
try {
|
|
122
|
+
const stat = statSync(fullPath);
|
|
123
|
+
if (stat.isDirectory()) {
|
|
124
|
+
results.push(...collectRelativeFilesUnsorted(fullPath, baseDir, visited));
|
|
125
|
+
}
|
|
126
|
+
else if (stat.isFile()) {
|
|
127
|
+
results.push(relative(baseDir, fullPath).split(sep).join("/"));
|
|
128
|
+
}
|
|
81
129
|
}
|
|
82
|
-
|
|
83
|
-
|
|
130
|
+
catch {
|
|
131
|
+
// 忽略无法访问或损坏的文件/符号链接
|
|
132
|
+
continue;
|
|
84
133
|
}
|
|
85
134
|
}
|
|
86
135
|
return results;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { BuilderError, PlannerError } from "./errors.js";
|
|
|
6
6
|
export { collectRelativeFiles, getInternalDependencyVersion, moveDirAtomic, replaceDirAtomic, } from "./fs-utils.js";
|
|
7
7
|
export { assertNoFileProtocolDeps, assertValidManifestActionIds, serializeManifestAction, serializeManifestPlaybooks, serializePlanManifest, } from "./manifest.js";
|
|
8
8
|
export type { SerializePlanManifestOptions } from "./manifest.js";
|
|
9
|
-
export { createTarGzArchive, createTarGzArchiveAsync, createZipArchive, createZipArchiveAsync, writeToStream, } from "./archive.js";
|
|
9
|
+
export { createTarGzArchive, createTarGzArchiveAsync, createZipArchive, createZipArchiveAsync, dosDateTime, writeToStream, } from "./archive.js";
|
|
10
10
|
export type { TarGzArchiveOptions } from "./archive.js";
|
|
11
11
|
export { BuildPlanner, buildPlan, SelectionPlanner, selectionPlan } from "./planner.js";
|
|
12
12
|
export { assertRelativeDependenciesIntegrity, extractRelativeSpecifiers, resolveRelativeModule, } from "./dependency-check.js";
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ export { collectRelativeFiles, getInternalDependencyVersion, moveDirAtomic, repl
|
|
|
9
9
|
// 清单组装与依赖协议校验
|
|
10
10
|
export { assertNoFileProtocolDeps, assertValidManifestActionIds, serializeManifestAction, serializeManifestPlaybooks, serializePlanManifest, } from "./manifest.js";
|
|
11
11
|
// 归档压缩
|
|
12
|
-
export { createTarGzArchive, createTarGzArchiveAsync, createZipArchive, createZipArchiveAsync, writeToStream, } from "./archive.js";
|
|
12
|
+
export { createTarGzArchive, createTarGzArchiveAsync, createZipArchive, createZipArchiveAsync, dosDateTime, writeToStream, } from "./archive.js";
|
|
13
13
|
// 构建规划
|
|
14
14
|
export { BuildPlanner, buildPlan, SelectionPlanner, selectionPlan } from "./planner.js";
|
|
15
15
|
// 相对依赖完整性校验
|
package/dist/pack.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
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 { dirname, join, relative, resolve } from "node:path";
|
|
6
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
7
7
|
import { getPackageSlug, loadProjectConfig } from "@actiondock/core";
|
|
8
8
|
import { BuilderError } from "./errors.js";
|
|
9
9
|
import { assertNoFileProtocolDeps, assertValidManifestActionIds, normalizePkgExportsAndEngines, readPackageJson, serializePlanManifest, } from "./manifest.js";
|
|
@@ -70,13 +70,34 @@ export async function packProject(options) {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
mkdirSync(targetOutDir, { recursive: true });
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
rmSync(finalTarballPath, { force: true });
|
|
76
|
-
}
|
|
73
|
+
// 优先使用 npm pack 返回的实名产物(保证与压缩包内 package.json name 一致),
|
|
74
|
+
// 移动而非复制改名;仅当无法获取实名时回退到本地拼接名
|
|
77
75
|
const generatedTarball = await runNpmPack(stagingPkgDir, tempBase, tarballName);
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
let finalTarballPath;
|
|
77
|
+
let finalTarballName;
|
|
78
|
+
if (basename(generatedTarball) === tarballName) {
|
|
79
|
+
finalTarballPath = join(targetOutDir, tarballName);
|
|
80
|
+
finalTarballName = tarballName;
|
|
81
|
+
if (existsSync(finalTarballPath)) {
|
|
82
|
+
rmSync(finalTarballPath, { force: true });
|
|
83
|
+
}
|
|
84
|
+
renameSync(generatedTarball, finalTarballPath);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
finalTarballName = basename(generatedTarball);
|
|
88
|
+
finalTarballPath = join(targetOutDir, finalTarballName);
|
|
89
|
+
if (existsSync(finalTarballPath)) {
|
|
90
|
+
rmSync(finalTarballPath, { force: true });
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
renameSync(generatedTarball, finalTarballPath);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// 跨设备 rename 失败时回退为复制后删除源文件
|
|
97
|
+
copyFileSync(generatedTarball, finalTarballPath);
|
|
98
|
+
rmSync(generatedTarball, { force: true });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
80
101
|
const stat = statSync(finalTarballPath);
|
|
81
102
|
const fileBuf = readFileSync(finalTarballPath);
|
|
82
103
|
const sha256 = createHash("sha256").update(fileBuf).digest("hex");
|
|
@@ -85,7 +106,7 @@ export async function packProject(options) {
|
|
|
85
106
|
packageName: plan.packageName,
|
|
86
107
|
version: plan.version,
|
|
87
108
|
tarballPath: finalTarballPath,
|
|
88
|
-
tarballName,
|
|
109
|
+
tarballName: finalTarballName,
|
|
89
110
|
sizeBytes: stat.size,
|
|
90
111
|
sha256,
|
|
91
112
|
files: relativeFiles,
|
|
@@ -168,6 +189,10 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
|
|
|
168
189
|
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
169
190
|
if (!configFile.error) {
|
|
170
191
|
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, root);
|
|
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
|
+
}
|
|
171
196
|
Object.assign(compilerOptions, parsedConfig.options, {
|
|
172
197
|
outDir: stagingPkgDir,
|
|
173
198
|
rootDir: root,
|
|
@@ -178,7 +203,10 @@ async function compileTypeScript(root, stagingPkgDir, plan) {
|
|
|
178
203
|
});
|
|
179
204
|
}
|
|
180
205
|
}
|
|
181
|
-
catch {
|
|
206
|
+
catch (err) {
|
|
207
|
+
if (err instanceof BuilderError) {
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
182
210
|
// 忽略 tsconfig 解析失败,回退到标准 compilerOptions
|
|
183
211
|
}
|
|
184
212
|
}
|
package/dist/planner.js
CHANGED
|
@@ -329,12 +329,15 @@ export class SelectionPlanner {
|
|
|
329
329
|
*/
|
|
330
330
|
resolveClosure(root, manifest, config, initialActionIds) {
|
|
331
331
|
const resolvedActionIds = new Set();
|
|
332
|
+
// queued 与 resolvedActionIds 联合判重,避免高入度节点在 uses 图中被重复入队导致 O(边数) 重复解析
|
|
333
|
+
const queued = new Set(initialActionIds);
|
|
332
334
|
const queue = Array.from(initialActionIds);
|
|
333
335
|
const externalActionRoots = new Map();
|
|
334
336
|
const externalActionEntries = new Map();
|
|
335
337
|
const manifestActions = manifest.actions || {};
|
|
336
338
|
while (queue.length > 0) {
|
|
337
339
|
const currentId = queue.shift();
|
|
340
|
+
queued.delete(currentId);
|
|
338
341
|
if (resolvedActionIds.has(currentId)) {
|
|
339
342
|
continue;
|
|
340
343
|
}
|
|
@@ -366,12 +369,16 @@ export class SelectionPlanner {
|
|
|
366
369
|
if (externalEntry) {
|
|
367
370
|
entry = externalEntry;
|
|
368
371
|
entryRoot = extRoot;
|
|
369
|
-
// 依赖包 actiondock.json
|
|
372
|
+
// 依赖包 actiondock.json 中声明的传递依赖闭包(入队前查重,避免重复膨胀)
|
|
370
373
|
const extPkgUses = extConfig?.uses;
|
|
371
374
|
if (Array.isArray(extPkgUses)) {
|
|
372
375
|
for (const u of extPkgUses) {
|
|
373
|
-
|
|
374
|
-
|
|
376
|
+
const trimmedUse = typeof u === "string" ? u.trim() : "";
|
|
377
|
+
if (trimmedUse &&
|
|
378
|
+
!resolvedActionIds.has(trimmedUse) &&
|
|
379
|
+
!queued.has(trimmedUse)) {
|
|
380
|
+
queued.add(trimmedUse);
|
|
381
|
+
queue.push(trimmedUse);
|
|
375
382
|
}
|
|
376
383
|
}
|
|
377
384
|
}
|
|
@@ -379,7 +386,7 @@ export class SelectionPlanner {
|
|
|
379
386
|
}
|
|
380
387
|
}
|
|
381
388
|
catch {
|
|
382
|
-
//
|
|
389
|
+
// 外部包解析失败时交由下方缺失依赖报错统一透传
|
|
383
390
|
}
|
|
384
391
|
}
|
|
385
392
|
if (!entry) {
|
|
@@ -392,7 +399,8 @@ export class SelectionPlanner {
|
|
|
392
399
|
resolvedActionIds.add(currentId);
|
|
393
400
|
if (entry.uses && Array.isArray(entry.uses)) {
|
|
394
401
|
for (const depId of entry.uses) {
|
|
395
|
-
if (!resolvedActionIds.has(depId)) {
|
|
402
|
+
if (!resolvedActionIds.has(depId) && !queued.has(depId)) {
|
|
403
|
+
queued.add(depId);
|
|
396
404
|
queue.push(depId);
|
|
397
405
|
}
|
|
398
406
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@actiondock/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "ActionDock Build Planner, distribution packaging, and Skill exporter",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"test": "node ../../scripts/run-tests.ts"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@actiondock/core": "^2.
|
|
33
|
-
"@actiondock/runtime-node": "^2.
|
|
34
|
-
"@actiondock/sdk": "^2.
|
|
32
|
+
"@actiondock/core": "^2.3.0",
|
|
33
|
+
"@actiondock/runtime-node": "^2.3.0",
|
|
34
|
+
"@actiondock/sdk": "^2.3.0"
|
|
35
35
|
},
|
|
36
36
|
"keywords": [
|
|
37
37
|
"actiondock",
|