@actiondock/builder 2.0.11 → 2.2.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 +46 -30
- package/dist/archive.d.ts +47 -0
- package/dist/archive.js +519 -0
- package/dist/build.d.ts +9 -0
- package/dist/build.js +576 -0
- package/dist/dependency-check.d.ts +14 -0
- package/dist/dependency-check.js +246 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +22 -0
- package/dist/exporter.d.ts +69 -0
- package/dist/exporter.js +761 -0
- package/dist/fs-utils.d.ts +16 -0
- package/dist/fs-utils.js +146 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +21 -0
- package/dist/manifest.d.ts +75 -0
- package/dist/manifest.js +213 -0
- package/dist/pack.d.ts +13 -0
- package/dist/pack.js +339 -0
- package/dist/planner.d.ts +68 -0
- package/dist/planner.js +681 -0
- package/dist/types.d.ts +442 -0
- package/dist/types.js +1 -0
- package/package.json +15 -17
- package/src/archive.ts +0 -248
- package/src/build.ts +0 -106
- package/src/compiler.ts +0 -303
- package/src/errors.ts +0 -67
- package/src/exporter.ts +0 -680
- package/src/index.ts +0 -6
- package/src/planner.ts +0 -731
- package/src/types.ts +0 -337
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function moveDirAtomic(src: string, dest: string): Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* 递归收集目录内全部文件的相对路径(正斜杠分隔,名称排序)。
|
|
4
|
+
* 统一排序保证产物文件清单与内容摘要的确定性。
|
|
5
|
+
*/
|
|
6
|
+
export declare function collectRelativeFiles(dir: string, baseDir?: string): string[];
|
|
7
|
+
/**
|
|
8
|
+
* 获取内部 @actiondock/* 依赖的版本号规则。
|
|
9
|
+
* 预发布版本采用精确锁定,正式版本采用 ^ 语义范围。
|
|
10
|
+
*/
|
|
11
|
+
export declare function getInternalDependencyVersion(version?: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* 以新目录原子替换已存在的目标目录(含失败回滚保护)。
|
|
14
|
+
* promotion 失败后优先 backup -> target 回滚;只有新 target 完整成功后才能删除 backup。
|
|
15
|
+
*/
|
|
16
|
+
export declare function replaceDirAtomic(stagingDir: string, targetDir: string): Promise<void>;
|
package/dist/fs-utils.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { cpSync, existsSync, readdirSync, renameSync, rmSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
import { ACTIONDOCK_VERSION } from "@actiondock/core";
|
|
4
|
+
/**
|
|
5
|
+
* builder 包内共享的文件系统基础设施。
|
|
6
|
+
* 消除 pack / build / exporter 三个业务模块中成体系的重复实现,
|
|
7
|
+
* 保证目录扫描排序与原子移动等关键行为存在单一事实源。
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 跨文件系统/分区的原子移动目录辅助函数。
|
|
11
|
+
* 优先 rename,遇到瞬时性错误码按指数退避重试,最终回退为递归复制后删除。
|
|
12
|
+
*/
|
|
13
|
+
const TRANSIENT_MOVE_ERROR_CODES = new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
|
|
14
|
+
const MOVE_RETRY_ATTEMPTS = 5;
|
|
15
|
+
export async function moveDirAtomic(src, dest) {
|
|
16
|
+
let lastErr;
|
|
17
|
+
for (let attempt = 1; attempt <= MOVE_RETRY_ATTEMPTS; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
renameSync(src, dest);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
lastErr = err;
|
|
24
|
+
if (err?.code === "EXDEV") {
|
|
25
|
+
// 跨文件系统/分区,立即转入跨设备原子搬迁流程
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
if (!TRANSIENT_MOVE_ERROR_CODES.has(err?.code)) {
|
|
29
|
+
throw err;
|
|
30
|
+
}
|
|
31
|
+
await new Promise((r) => setTimeout(r, 50 * attempt));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (lastErr?.code !== "EXDEV") {
|
|
35
|
+
throw lastErr;
|
|
36
|
+
}
|
|
37
|
+
// 跨设备场景:先复制到 dest 同父目录下的临时目录,再同设备 rename 保证 dest 提升的原子性
|
|
38
|
+
const tempDest = `${dest}.tmp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
39
|
+
try {
|
|
40
|
+
cpSync(src, tempDest, { recursive: true });
|
|
41
|
+
for (let attempt = 1; attempt <= MOVE_RETRY_ATTEMPTS; attempt++) {
|
|
42
|
+
try {
|
|
43
|
+
renameSync(tempDest, dest);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (attempt === MOVE_RETRY_ATTEMPTS || !TRANSIENT_MOVE_ERROR_CODES.has(err?.code)) {
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
await new Promise((r) => setTimeout(r, 50 * attempt));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
rmSync(src, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
if (existsSync(tempDest)) {
|
|
57
|
+
try {
|
|
58
|
+
rmSync(tempDest, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// 忽略临时目录清理异常
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw err;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 递归收集目录内全部文件的相对路径(正斜杠分隔,名称排序)。
|
|
69
|
+
* 统一排序保证产物文件清单与内容摘要的确定性。
|
|
70
|
+
*/
|
|
71
|
+
export function collectRelativeFiles(dir, baseDir = dir) {
|
|
72
|
+
if (!existsSync(dir))
|
|
73
|
+
return [];
|
|
74
|
+
const results = [];
|
|
75
|
+
const entries = readdirSync(dir).sort();
|
|
76
|
+
for (const entry of entries) {
|
|
77
|
+
const fullPath = join(dir, entry);
|
|
78
|
+
const stat = statSync(fullPath);
|
|
79
|
+
if (stat.isDirectory()) {
|
|
80
|
+
results.push(...collectRelativeFiles(fullPath, baseDir));
|
|
81
|
+
}
|
|
82
|
+
else if (stat.isFile()) {
|
|
83
|
+
results.push(relative(baseDir, fullPath).split(sep).join("/"));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return results;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 获取内部 @actiondock/* 依赖的版本号规则。
|
|
90
|
+
* 预发布版本采用精确锁定,正式版本采用 ^ 语义范围。
|
|
91
|
+
*/
|
|
92
|
+
export function getInternalDependencyVersion(version = ACTIONDOCK_VERSION) {
|
|
93
|
+
if (version.includes("-")) {
|
|
94
|
+
return version;
|
|
95
|
+
}
|
|
96
|
+
return `^${version}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 以新目录原子替换已存在的目标目录(含失败回滚保护)。
|
|
100
|
+
* promotion 失败后优先 backup -> target 回滚;只有新 target 完整成功后才能删除 backup。
|
|
101
|
+
*/
|
|
102
|
+
export async function replaceDirAtomic(stagingDir, targetDir) {
|
|
103
|
+
if (existsSync(targetDir)) {
|
|
104
|
+
const backupDir = `${targetDir}.old-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
105
|
+
await moveDirAtomic(targetDir, backupDir);
|
|
106
|
+
try {
|
|
107
|
+
await moveDirAtomic(stagingDir, targetDir);
|
|
108
|
+
}
|
|
109
|
+
catch (promoteErr) {
|
|
110
|
+
// staging 提升失败:清理可能残留的损坏目标目录,并将 backup 恢复至 targetDir
|
|
111
|
+
if (existsSync(targetDir)) {
|
|
112
|
+
try {
|
|
113
|
+
rmSync(targetDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// 忽略清理残余异常
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
await moveDirAtomic(backupDir, targetDir);
|
|
121
|
+
}
|
|
122
|
+
catch (rollbackErr) {
|
|
123
|
+
throw new Error(`Failed to promote directory to '${targetDir}' (${promoteErr instanceof Error ? promoteErr.message : String(promoteErr)}), and rollback from backup '${backupDir}' also failed: ${rollbackErr?.message || String(rollbackErr)}`);
|
|
124
|
+
}
|
|
125
|
+
throw promoteErr;
|
|
126
|
+
}
|
|
127
|
+
// 只有新 target 完整成功后才能删除 backup
|
|
128
|
+
rmSync(backupDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
try {
|
|
132
|
+
await moveDirAtomic(stagingDir, targetDir);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
if (existsSync(targetDir)) {
|
|
136
|
+
try {
|
|
137
|
+
rmSync(targetDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// 忽略清理残余异常
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
throw err;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @actiondock/builder 公共导出面。
|
|
3
|
+
* 采用显式命名导出替代全量 export *,保证公开 API 边界清晰可控。
|
|
4
|
+
*/
|
|
5
|
+
export { BuilderError, PlannerError } from "./errors.js";
|
|
6
|
+
export { collectRelativeFiles, getInternalDependencyVersion, moveDirAtomic, replaceDirAtomic, } from "./fs-utils.js";
|
|
7
|
+
export { assertNoFileProtocolDeps, assertValidManifestActionIds, serializeManifestAction, serializeManifestPlaybooks, serializePlanManifest, } from "./manifest.js";
|
|
8
|
+
export type { SerializePlanManifestOptions } from "./manifest.js";
|
|
9
|
+
export { createTarGzArchive, createTarGzArchiveAsync, createZipArchive, createZipArchiveAsync, writeToStream, } from "./archive.js";
|
|
10
|
+
export type { TarGzArchiveOptions } from "./archive.js";
|
|
11
|
+
export { BuildPlanner, buildPlan, SelectionPlanner, selectionPlan } from "./planner.js";
|
|
12
|
+
export { assertRelativeDependenciesIntegrity, extractRelativeSpecifiers, resolveRelativeModule, } from "./dependency-check.js";
|
|
13
|
+
export { buildProject } from "./build.js";
|
|
14
|
+
export { packProject } from "./pack.js";
|
|
15
|
+
export { exportCompositeSkill, exportSkill, exportSkillBatch, findExistingCompositeSkillMd, findExistingSingleSkillMd, SkillExporter, } from "./exporter.js";
|
|
16
|
+
export type { ActionDependency, ArchiveFormat, AssetDependency, BatchSkillExportOptions, BatchSkillExportResult, BuildOptions, BuildPlan, BuildPlanDependencies, BuildPlannerOptions, BuildResult, CompositeSkillExportOptions, CompositeSkillExportResult, ExternalDependency, LockfileInfo, PackOptions, PackResult, PlaybookPlanEntry, ProjectConfigWithDeclarations, SelectionPlan, SelectionPlannerOptions, SkillExporterOptions, SkillExportResult, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @actiondock/builder 公共导出面。
|
|
3
|
+
* 采用显式命名导出替代全量 export *,保证公开 API 边界清晰可控。
|
|
4
|
+
*/
|
|
5
|
+
// 错误类型
|
|
6
|
+
export { BuilderError, PlannerError } from "./errors.js";
|
|
7
|
+
// 共享基础设施(内部复用的通用能力,随包公开便于下游校验)
|
|
8
|
+
export { collectRelativeFiles, getInternalDependencyVersion, moveDirAtomic, replaceDirAtomic, } from "./fs-utils.js";
|
|
9
|
+
// 清单组装与依赖协议校验
|
|
10
|
+
export { assertNoFileProtocolDeps, assertValidManifestActionIds, serializeManifestAction, serializeManifestPlaybooks, serializePlanManifest, } from "./manifest.js";
|
|
11
|
+
// 归档压缩
|
|
12
|
+
export { createTarGzArchive, createTarGzArchiveAsync, createZipArchive, createZipArchiveAsync, writeToStream, } from "./archive.js";
|
|
13
|
+
// 构建规划
|
|
14
|
+
export { BuildPlanner, buildPlan, SelectionPlanner, selectionPlan } from "./planner.js";
|
|
15
|
+
// 相对依赖完整性校验
|
|
16
|
+
export { assertRelativeDependenciesIntegrity, extractRelativeSpecifiers, resolveRelativeModule, } from "./dependency-check.js";
|
|
17
|
+
// 目录型构建与 npm 打包
|
|
18
|
+
export { buildProject } from "./build.js";
|
|
19
|
+
export { packProject } from "./pack.js";
|
|
20
|
+
// Skill 导出
|
|
21
|
+
export { exportCompositeSkill, exportSkill, exportSkillBatch, findExistingCompositeSkillMd, findExistingSingleSkillMd, SkillExporter, } from "./exporter.js";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ActionDependency, PlaybookPlanEntry } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 校验清单 actions 字典中的所有 Action ID 是否符合规范。
|
|
4
|
+
* 强制写入端契约自检,杜绝非法 Action ID 落盘。
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertValidManifestActionIds(actions: Record<string, unknown>): void;
|
|
7
|
+
/**
|
|
8
|
+
* actiondock.json 清单组装与依赖协议校验的单一实现。
|
|
9
|
+
* pack / build / exporter 三个模块的清单拼装与 file: 协议校验全部收敛至此,
|
|
10
|
+
* 字段顺序属于产物字节级契约的一部分,严禁调整。
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* 构造单个 Action 的清单声明项(字段顺序为产物契约的一部分)。
|
|
14
|
+
*/
|
|
15
|
+
export declare function serializeManifestAction(action: ActionDependency): Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* 构造 Playbook 集合的清单字典。
|
|
18
|
+
*/
|
|
19
|
+
export declare function serializeManifestPlaybooks(playbooks: PlaybookPlanEntry[], playbooksDir: string): Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* manifest 拼装选项。
|
|
22
|
+
*/
|
|
23
|
+
export interface SerializePlanManifestOptions {
|
|
24
|
+
/** 是否包含 $schema 声明字段(npm pack 产物包含,目录型产物不包含) */
|
|
25
|
+
includeSchema?: boolean;
|
|
26
|
+
/** config 字段是否前置于 actions 之前(npm pack 产物的历史字段顺序) */
|
|
27
|
+
configBeforeActions?: boolean;
|
|
28
|
+
/** 附加 actionsDir / playbooksDir 目录字段(源码型 Skill 导出需要) */
|
|
29
|
+
includeDirs?: {
|
|
30
|
+
actionsDir?: string;
|
|
31
|
+
playbooksDir?: string;
|
|
32
|
+
};
|
|
33
|
+
/** Action 入口改写映射(npm pack 用于指向编译后的 .js/.mjs 入口) */
|
|
34
|
+
actionEntryOverride?: (action: ActionDependency) => string;
|
|
35
|
+
/** 是否省略空 config 键(源码型 Skill 导出保持旧版字节级行为:未声明时省略) */
|
|
36
|
+
omitEmptyConfig?: boolean;
|
|
37
|
+
/** 是否输出 playbooks 声明(npm pack 产物历史行为不输出,需显式关闭) */
|
|
38
|
+
includePlaybooks?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 将 SelectionPlan 组装为 actiondock.json 清单对象。
|
|
42
|
+
* 标准字段顺序:schemaVersion、id、name、version、description、
|
|
43
|
+
* actionsDir、playbooksDir、actions、playbooks、config、files、assets。
|
|
44
|
+
*/
|
|
45
|
+
export declare function serializePlanManifest(plan: {
|
|
46
|
+
packageId: string;
|
|
47
|
+
packageName: string;
|
|
48
|
+
version: string;
|
|
49
|
+
description?: string;
|
|
50
|
+
actions: ActionDependency[];
|
|
51
|
+
playbooks: PlaybookPlanEntry[];
|
|
52
|
+
playbooksDir?: string;
|
|
53
|
+
files?: string[];
|
|
54
|
+
assets?: string[];
|
|
55
|
+
configDefs?: Record<string, unknown>;
|
|
56
|
+
}, options?: SerializePlanManifestOptions): Record<string, unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* 校验依赖字典中不存在 file: 本地协议声明,违规即抛出 BuilderError。
|
|
59
|
+
*/
|
|
60
|
+
export declare function assertNoFileProtocolDeps(dependencies: Record<string, unknown> | undefined, context: string): void;
|
|
61
|
+
/**
|
|
62
|
+
* 解析 workspace: 依赖的真实版本号。
|
|
63
|
+
* @actiondock/* 内部依赖直接采用内部版本规则;显式约束直接剥离前缀;
|
|
64
|
+
* workspace:* 则在本地 node_modules 与 monorepo packages 目录中搜索目标包版本。
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveWorkspaceDepVersion(root: string, depName: string, ver: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* 读取并解析 package.json,解析失败抛出 BuilderError。
|
|
69
|
+
*/
|
|
70
|
+
export declare function readPackageJson(pkgJsonPath: string): Record<string, any>;
|
|
71
|
+
/**
|
|
72
|
+
* 规范化导出用 package.json:导出 ./actiondock.json,设定 node 引擎约束与 ESM 类型。
|
|
73
|
+
* 返回新对象,不改动传入源。
|
|
74
|
+
*/
|
|
75
|
+
export declare function normalizePkgExportsAndEngines(pkg: Record<string, any>): Record<string, any>;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { ACTION_ID_REGEX } from "@actiondock/core";
|
|
4
|
+
import { BuilderError } from "./errors.js";
|
|
5
|
+
import { getInternalDependencyVersion } from "./fs-utils.js";
|
|
6
|
+
/**
|
|
7
|
+
* 校验清单 actions 字典中的所有 Action ID 是否符合规范。
|
|
8
|
+
* 强制写入端契约自检,杜绝非法 Action ID 落盘。
|
|
9
|
+
*/
|
|
10
|
+
export function assertValidManifestActionIds(actions) {
|
|
11
|
+
for (const actionId of Object.keys(actions)) {
|
|
12
|
+
if (!ACTION_ID_REGEX.test(actionId)) {
|
|
13
|
+
throw new BuilderError(`Invalid action ID '${actionId}' in generated manifest. Action IDs must match ${ACTION_ID_REGEX}`, "INVALID_ACTION_ID");
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* actiondock.json 清单组装与依赖协议校验的单一实现。
|
|
19
|
+
* pack / build / exporter 三个模块的清单拼装与 file: 协议校验全部收敛至此,
|
|
20
|
+
* 字段顺序属于产物字节级契约的一部分,严禁调整。
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* 构造单个 Action 的清单声明项(字段顺序为产物契约的一部分)。
|
|
24
|
+
*/
|
|
25
|
+
export function serializeManifestAction(action) {
|
|
26
|
+
return {
|
|
27
|
+
entry: action.entry,
|
|
28
|
+
description: action.description,
|
|
29
|
+
inputSchema: action.inputSchema,
|
|
30
|
+
outputSchema: action.outputSchema,
|
|
31
|
+
uses: action.uses,
|
|
32
|
+
tags: action.tags,
|
|
33
|
+
annotations: action.annotations,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 构造 Playbook 集合的清单字典。
|
|
38
|
+
*/
|
|
39
|
+
export function serializeManifestPlaybooks(playbooks, playbooksDir) {
|
|
40
|
+
const manifestPlaybooks = {};
|
|
41
|
+
for (const pb of playbooks) {
|
|
42
|
+
const fileName = pb.filePath.replace(/\\/g, "/").split("/").pop();
|
|
43
|
+
manifestPlaybooks[pb.id] = {
|
|
44
|
+
entry: `${playbooksDir}/${fileName}`,
|
|
45
|
+
...(pb.description ? { description: pb.description } : {}),
|
|
46
|
+
...(pb.actions && pb.actions.length > 0 ? { actions: pb.actions } : {}),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return manifestPlaybooks;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 将 SelectionPlan 组装为 actiondock.json 清单对象。
|
|
53
|
+
* 标准字段顺序:schemaVersion、id、name、version、description、
|
|
54
|
+
* actionsDir、playbooksDir、actions、playbooks、config、files、assets。
|
|
55
|
+
*/
|
|
56
|
+
export function serializePlanManifest(plan, options = {}) {
|
|
57
|
+
const manifestActions = {};
|
|
58
|
+
for (const act of plan.actions) {
|
|
59
|
+
// 仅序列化包自有 Action,跨包依赖不写入包清单
|
|
60
|
+
if (act.isExternal || act.id.includes("/")) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const serialized = serializeManifestAction(act);
|
|
64
|
+
if (options.actionEntryOverride) {
|
|
65
|
+
serialized.entry = options.actionEntryOverride(act);
|
|
66
|
+
}
|
|
67
|
+
manifestActions[act.id] = serialized;
|
|
68
|
+
}
|
|
69
|
+
// 写入端契约自检断言:校验所有 Action ID 必须符合规范
|
|
70
|
+
assertValidManifestActionIds(manifestActions);
|
|
71
|
+
const manifestPlaybooks = serializeManifestPlaybooks(plan.playbooks, options.includeDirs?.playbooksDir || plan.playbooksDir || "playbooks");
|
|
72
|
+
const manifest = {};
|
|
73
|
+
if (options.includeSchema) {
|
|
74
|
+
manifest.$schema = "https://actiondock.dev/schema/v2.json";
|
|
75
|
+
}
|
|
76
|
+
manifest.schemaVersion = 2;
|
|
77
|
+
manifest.id = plan.packageId;
|
|
78
|
+
manifest.name = plan.packageName;
|
|
79
|
+
manifest.version = plan.version;
|
|
80
|
+
manifest.description = plan.description;
|
|
81
|
+
if (options.includeDirs?.actionsDir) {
|
|
82
|
+
manifest.actionsDir = options.includeDirs.actionsDir;
|
|
83
|
+
}
|
|
84
|
+
if (options.includeDirs?.playbooksDir) {
|
|
85
|
+
manifest.playbooksDir = options.includeDirs.playbooksDir;
|
|
86
|
+
}
|
|
87
|
+
manifest.actions = manifestActions;
|
|
88
|
+
if (Object.keys(manifestPlaybooks).length > 0 && options.includePlaybooks !== false) {
|
|
89
|
+
manifest.playbooks = manifestPlaybooks;
|
|
90
|
+
}
|
|
91
|
+
if (plan.configDefs) {
|
|
92
|
+
manifest.config = plan.configDefs;
|
|
93
|
+
}
|
|
94
|
+
else if (!options.omitEmptyConfig) {
|
|
95
|
+
manifest.config = {};
|
|
96
|
+
}
|
|
97
|
+
if (plan.files && plan.files.length > 0) {
|
|
98
|
+
manifest.files = plan.files;
|
|
99
|
+
}
|
|
100
|
+
if (plan.assets && plan.assets.length > 0) {
|
|
101
|
+
manifest.assets = plan.assets;
|
|
102
|
+
}
|
|
103
|
+
if (options.configBeforeActions) {
|
|
104
|
+
// npm pack 产物的历史字段顺序:config 前置于 actions
|
|
105
|
+
const ordered = {};
|
|
106
|
+
for (const [key, value] of Object.entries(manifest)) {
|
|
107
|
+
if (key === "actions") {
|
|
108
|
+
ordered.config = manifest.config;
|
|
109
|
+
ordered.actions = value;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (key === "config") {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
ordered[key] = value;
|
|
116
|
+
}
|
|
117
|
+
return ordered;
|
|
118
|
+
}
|
|
119
|
+
return manifest;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 校验依赖字典中不存在 file: 本地协议声明,违规即抛出 BuilderError。
|
|
123
|
+
*/
|
|
124
|
+
export function assertNoFileProtocolDeps(dependencies, context) {
|
|
125
|
+
if (!dependencies || typeof dependencies !== "object")
|
|
126
|
+
return;
|
|
127
|
+
for (const [depName, depVer] of Object.entries(dependencies)) {
|
|
128
|
+
if (String(depVer).startsWith("file:")) {
|
|
129
|
+
throw new BuilderError(`Unsupported file: dependency for '${depName}'. Runtime dependencies must not use file: protocol in ${context}.`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** 依赖名最后一段(scoped 包名去除命名空间前缀) */
|
|
134
|
+
function lastSegment(name) {
|
|
135
|
+
return name.split("/").pop();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 解析 workspace: 依赖的真实版本号。
|
|
139
|
+
* @actiondock/* 内部依赖直接采用内部版本规则;显式约束直接剥离前缀;
|
|
140
|
+
* workspace:* 则在本地 node_modules 与 monorepo packages 目录中搜索目标包版本。
|
|
141
|
+
*/
|
|
142
|
+
export function resolveWorkspaceDepVersion(root, depName, ver) {
|
|
143
|
+
if (depName.startsWith("@actiondock/")) {
|
|
144
|
+
return getInternalDependencyVersion();
|
|
145
|
+
}
|
|
146
|
+
const stripped = ver.replace(/^workspace:/, "").trim();
|
|
147
|
+
if (stripped && stripped !== "*" && stripped !== "^" && stripped !== "~") {
|
|
148
|
+
return stripped;
|
|
149
|
+
}
|
|
150
|
+
const candidates = [
|
|
151
|
+
resolve(root, "node_modules", depName, "package.json"),
|
|
152
|
+
resolve(root, "packages", depName, "package.json"),
|
|
153
|
+
resolve(root, "packages", lastSegment(depName), "package.json"),
|
|
154
|
+
resolve(root, "..", depName, "package.json"),
|
|
155
|
+
resolve(root, "..", lastSegment(depName), "package.json"),
|
|
156
|
+
resolve(root, "..", "packages", depName, "package.json"),
|
|
157
|
+
resolve(root, "..", "packages", lastSegment(depName), "package.json"),
|
|
158
|
+
resolve(root, "..", "..", "packages", depName, "package.json"),
|
|
159
|
+
resolve(root, "..", "..", "packages", lastSegment(depName), "package.json"),
|
|
160
|
+
];
|
|
161
|
+
for (const cand of candidates) {
|
|
162
|
+
if (existsSync(cand)) {
|
|
163
|
+
try {
|
|
164
|
+
const pkgData = JSON.parse(readFileSync(cand, "utf-8"));
|
|
165
|
+
if (pkgData.version) {
|
|
166
|
+
const prefix = stripped === "~" ? "~" : "^";
|
|
167
|
+
return `${prefix}${pkgData.version}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// 忽略单个依赖解析异常
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
throw new BuilderError(`Failed to resolve workspace dependency '${depName}' (${ver}) in '${root}'. Target package version could not be found.`);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 读取并解析 package.json,解析失败抛出 BuilderError。
|
|
179
|
+
*/
|
|
180
|
+
export function readPackageJson(pkgJsonPath) {
|
|
181
|
+
try {
|
|
182
|
+
return JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
throw new BuilderError(`Invalid package.json in ${pkgJsonPath}: ${err?.message || String(err)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* 规范化导出用 package.json:导出 ./actiondock.json,设定 node 引擎约束与 ESM 类型。
|
|
190
|
+
* 返回新对象,不改动传入源。
|
|
191
|
+
*/
|
|
192
|
+
export function normalizePkgExportsAndEngines(pkg) {
|
|
193
|
+
const normalized = { ...pkg, type: "module" };
|
|
194
|
+
let currentExports = {};
|
|
195
|
+
if (typeof normalized.exports === "object" && normalized.exports !== null) {
|
|
196
|
+
currentExports = { ...normalized.exports };
|
|
197
|
+
}
|
|
198
|
+
else if (typeof normalized.exports === "string") {
|
|
199
|
+
currentExports["."] = normalized.exports;
|
|
200
|
+
}
|
|
201
|
+
currentExports["./actiondock.json"] = "./actiondock.json";
|
|
202
|
+
normalized.exports = currentExports;
|
|
203
|
+
const currentEngines = {
|
|
204
|
+
...(typeof normalized.engines === "object" && normalized.engines !== null
|
|
205
|
+
? normalized.engines
|
|
206
|
+
: {}),
|
|
207
|
+
};
|
|
208
|
+
if (!currentEngines.node) {
|
|
209
|
+
currentEngines.node = ">=24.12.0";
|
|
210
|
+
}
|
|
211
|
+
normalized.engines = currentEngines;
|
|
212
|
+
return normalized;
|
|
213
|
+
}
|
package/dist/pack.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PackOptions, PackResult } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* 对 Action Package 执行打包并生成标准 npm Action 压缩包(.tgz)。
|
|
4
|
+
*
|
|
5
|
+
* 职责:
|
|
6
|
+
* - 调用项目本地 TypeScript 编译(Node 执行),输出声明文件(.d.ts)与纯 JavaScript ESM 产物到临时暂存目录。
|
|
7
|
+
* - 严格校验导出的 Manifest(入口全部指向编译后的 .js/.mjs,不得包含 actionsDir 或 playbooksDir 等废弃目录字段)。
|
|
8
|
+
* - 在暂存目录调用带有 --ignore-scripts 的 npm pack 生成标准 npm 压缩包(.tgz),严禁使用正则替换与自建 tar 压缩。
|
|
9
|
+
*
|
|
10
|
+
* @param options 打包参数选项
|
|
11
|
+
* @returns 打包结果元数据
|
|
12
|
+
*/
|
|
13
|
+
export declare function packProject(options: PackOptions): Promise<PackResult>;
|