@actiondock/builder 2.0.11-beta.0 → 2.0.12
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 +26 -0
- package/dist/build.d.ts +41 -0
- package/dist/compiler.d.ts +22 -0
- package/dist/errors.d.ts +41 -0
- package/dist/exporter.d.ts +59 -0
- package/{src/index.ts → dist/index.d.ts} +1 -0
- package/dist/index.js +1875 -0
- package/dist/planner.d.ts +26 -0
- package/dist/types.d.ts +311 -0
- package/package.json +13 -14
- 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/planner.ts +0 -731
- package/src/types.ts +0 -337
package/src/planner.ts
DELETED
|
@@ -1,731 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
-
import YAML from "yaml";
|
|
4
|
-
import {
|
|
5
|
-
type ActionDockManifest,
|
|
6
|
-
type ActionManifestEntry,
|
|
7
|
-
loadManifest,
|
|
8
|
-
loadProjectConfig,
|
|
9
|
-
type PlaybookDefinition,
|
|
10
|
-
type ProjectConfig,
|
|
11
|
-
resolveActionProjectSync,
|
|
12
|
-
validateManifest,
|
|
13
|
-
} from "@actiondock/core";
|
|
14
|
-
import { PlannerError } from "./errors";
|
|
15
|
-
import type {
|
|
16
|
-
ActionDependency,
|
|
17
|
-
AssetDependency,
|
|
18
|
-
BuildPlan,
|
|
19
|
-
BuildPlanDependencies,
|
|
20
|
-
BuildPlannerOptions,
|
|
21
|
-
ExternalDependency,
|
|
22
|
-
PlaybookPlanEntry,
|
|
23
|
-
} from "./types";
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 递归扫描指定目录下的文件,返回绝对路径列表。
|
|
27
|
-
*/
|
|
28
|
-
function walkDirectory(dir: string): string[] {
|
|
29
|
-
if (!existsSync(dir)) return [];
|
|
30
|
-
const results: string[] = [];
|
|
31
|
-
const entries = readdirSync(dir);
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
const fullPath = join(dir, entry);
|
|
34
|
-
const stat = statSync(fullPath);
|
|
35
|
-
if (stat.isDirectory()) {
|
|
36
|
-
results.push(...walkDirectory(fullPath));
|
|
37
|
-
} else if (stat.isFile()) {
|
|
38
|
-
results.push(fullPath);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
return results;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 忽略的模块路径与文件模式判断(排除测试文件、类型声明文件以及构建/版本控制等私有目录)。
|
|
46
|
-
*/
|
|
47
|
-
function isIgnoredModulePath(relPath: string): boolean {
|
|
48
|
-
const normalized = relPath.replace(/\\/g, "/");
|
|
49
|
-
return (
|
|
50
|
-
normalized.startsWith("node_modules/") ||
|
|
51
|
-
normalized.includes("/node_modules/") ||
|
|
52
|
-
normalized.startsWith(".git/") ||
|
|
53
|
-
normalized.startsWith("dist/") ||
|
|
54
|
-
normalized.startsWith(".actiondock/") ||
|
|
55
|
-
normalized.endsWith(".test.ts") ||
|
|
56
|
-
normalized.endsWith(".test.js") ||
|
|
57
|
-
normalized.endsWith(".test.tsx") ||
|
|
58
|
-
normalized.endsWith(".test.jsx") ||
|
|
59
|
-
normalized.endsWith(".spec.ts") ||
|
|
60
|
-
normalized.endsWith(".spec.js") ||
|
|
61
|
-
normalized.endsWith(".spec.tsx") ||
|
|
62
|
-
normalized.endsWith(".spec.jsx") ||
|
|
63
|
-
normalized.endsWith(".d.ts")
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 提取源码文本中的所有相对路径模块引用说明符(如 ./... 与 ../...)。
|
|
69
|
-
* 采用纯正则静态提取,杜绝动态 import 与代码执行。
|
|
70
|
-
*/
|
|
71
|
-
function extractRelativeImports(source: string): string[] {
|
|
72
|
-
// 移除多行注释与单行注释,避免注释中的无效相对路径被误提取
|
|
73
|
-
const stripped = source
|
|
74
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
75
|
-
.replace(/\/\/[^\n]*/g, "");
|
|
76
|
-
|
|
77
|
-
const specifiers = new Set<string>();
|
|
78
|
-
|
|
79
|
-
// 匹配 from "..."、from '...'、from `...`
|
|
80
|
-
const fromRegex = /\bfrom\s*["'`]([^"'`]+)["'`]/g;
|
|
81
|
-
let match: RegExpExecArray | null;
|
|
82
|
-
while ((match = fromRegex.exec(stripped)) !== null) {
|
|
83
|
-
const spec = match[1].trim();
|
|
84
|
-
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
85
|
-
specifiers.add(spec);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 匹配 import(...) 或 import "..."
|
|
90
|
-
const importRegex = /\bimport\s*(?:\(\s*)?["'`]([^"'`]+)["'`]/g;
|
|
91
|
-
while ((match = importRegex.exec(stripped)) !== null) {
|
|
92
|
-
const spec = match[1].trim();
|
|
93
|
-
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
94
|
-
specifiers.add(spec);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 匹配 require(...)
|
|
99
|
-
const requireRegex = /\brequire\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
100
|
-
while ((match = requireRegex.exec(stripped)) !== null) {
|
|
101
|
-
const spec = match[1].trim();
|
|
102
|
-
if (spec.startsWith("./") || spec.startsWith("../")) {
|
|
103
|
-
specifiers.add(spec);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return Array.from(specifiers);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const CANDIDATE_EXTENSIONS = [
|
|
111
|
-
"",
|
|
112
|
-
".ts",
|
|
113
|
-
".tsx",
|
|
114
|
-
".js",
|
|
115
|
-
".jsx",
|
|
116
|
-
".mjs",
|
|
117
|
-
".cjs",
|
|
118
|
-
".mts",
|
|
119
|
-
".cts",
|
|
120
|
-
".json",
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* 在文件系统上探测解析相对导入说明符所对应的物理源文件。
|
|
125
|
-
* 兼容 NodeNext 规范中将 .js / .mjs / .cjs 说明符解析映射到 .ts / .mts / .cts 源文件。
|
|
126
|
-
*/
|
|
127
|
-
function resolveLocalModulePath(baseDir: string, specifier: string): string | null {
|
|
128
|
-
const candidate = resolve(baseDir, specifier);
|
|
129
|
-
|
|
130
|
-
// 1. 若候选路径直接存在且为文件
|
|
131
|
-
if (existsSync(candidate)) {
|
|
132
|
-
try {
|
|
133
|
-
if (statSync(candidate).isFile()) {
|
|
134
|
-
return candidate;
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
// 忽略文件属性读取异常
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 2. NodeNext / ESM 规范映射:若导入说明符以 .js / .mjs / .cjs 结尾,优先尝试对应 TypeScript 源码文件
|
|
142
|
-
if (candidate.endsWith(".js")) {
|
|
143
|
-
const tsPath = candidate.slice(0, -3) + ".ts";
|
|
144
|
-
if (existsSync(tsPath)) return tsPath;
|
|
145
|
-
const tsxPath = candidate.slice(0, -3) + ".tsx";
|
|
146
|
-
if (existsSync(tsxPath)) return tsxPath;
|
|
147
|
-
} else if (candidate.endsWith(".mjs")) {
|
|
148
|
-
const mtsPath = candidate.slice(0, -4) + ".mts";
|
|
149
|
-
if (existsSync(mtsPath)) return mtsPath;
|
|
150
|
-
} else if (candidate.endsWith(".cjs")) {
|
|
151
|
-
const ctsPath = candidate.slice(0, -4) + ".cts";
|
|
152
|
-
if (existsSync(ctsPath)) return ctsPath;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// 3. 尝试追加常见扩展名
|
|
156
|
-
for (const ext of CANDIDATE_EXTENSIONS) {
|
|
157
|
-
if (!ext) continue;
|
|
158
|
-
const withExt = candidate + ext;
|
|
159
|
-
if (existsSync(withExt)) {
|
|
160
|
-
try {
|
|
161
|
-
if (statSync(withExt).isFile()) {
|
|
162
|
-
return withExt;
|
|
163
|
-
}
|
|
164
|
-
} catch {
|
|
165
|
-
// 忽略
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// 4. 若为目录,尝试 index 文件
|
|
171
|
-
if (existsSync(candidate)) {
|
|
172
|
-
try {
|
|
173
|
-
if (statSync(candidate).isDirectory()) {
|
|
174
|
-
for (const ext of [".ts", ".tsx", ".js", ".mjs", ".json"]) {
|
|
175
|
-
const indexFile = join(candidate, `index${ext}`);
|
|
176
|
-
if (existsSync(indexFile)) {
|
|
177
|
-
return indexFile;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
} catch {
|
|
182
|
-
// 忽略
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* 从一组入口文件出发,静态深度追踪所有相对路径引用的本地源码模块闭包。
|
|
191
|
-
*/
|
|
192
|
-
function traceLocalModuleDependencies(
|
|
193
|
-
entryFiles: string[],
|
|
194
|
-
projectRoot: string
|
|
195
|
-
): string[] {
|
|
196
|
-
const visited = new Set<string>();
|
|
197
|
-
const modules = new Set<string>();
|
|
198
|
-
const queue = [...entryFiles];
|
|
199
|
-
|
|
200
|
-
while (queue.length > 0) {
|
|
201
|
-
const current = queue.shift()!;
|
|
202
|
-
if (visited.has(current)) continue;
|
|
203
|
-
visited.add(current);
|
|
204
|
-
|
|
205
|
-
const relToRoot = relative(projectRoot, current);
|
|
206
|
-
if (relToRoot.startsWith("..") || isAbsolute(relToRoot)) {
|
|
207
|
-
continue;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
let content = "";
|
|
211
|
-
try {
|
|
212
|
-
content = readFileSync(current, "utf-8");
|
|
213
|
-
} catch {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const specifiers = extractRelativeImports(content);
|
|
218
|
-
const dir = dirname(current);
|
|
219
|
-
|
|
220
|
-
for (const spec of specifiers) {
|
|
221
|
-
const resolved = resolveLocalModulePath(dir, spec);
|
|
222
|
-
if (resolved) {
|
|
223
|
-
const rel = relative(projectRoot, resolved);
|
|
224
|
-
if (!rel.startsWith("..") && !isAbsolute(rel) && !isIgnoredModulePath(rel)) {
|
|
225
|
-
modules.add(resolved);
|
|
226
|
-
if (!visited.has(resolved)) {
|
|
227
|
-
queue.push(resolved);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return Array.from(modules);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* 纯文本解析 Playbook Markdown 文件,提取 YAML Frontmatter,不执行任何业务代码。
|
|
239
|
-
*/
|
|
240
|
-
function parsePlaybookFile(filePath: string): PlaybookPlanEntry | null {
|
|
241
|
-
try {
|
|
242
|
-
const content = readFileSync(filePath, "utf-8");
|
|
243
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
244
|
-
if (match) {
|
|
245
|
-
const parsed = YAML.parse(match[1]) || {};
|
|
246
|
-
const id = parsed.id || basename(filePath, ".md");
|
|
247
|
-
return {
|
|
248
|
-
id,
|
|
249
|
-
filePath,
|
|
250
|
-
actions: Array.isArray(parsed.actions) ? parsed.actions : [],
|
|
251
|
-
description: parsed.description,
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
return {
|
|
255
|
-
id: basename(filePath, ".md"),
|
|
256
|
-
filePath,
|
|
257
|
-
actions: [],
|
|
258
|
-
};
|
|
259
|
-
} catch {
|
|
260
|
-
return null;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* 静态加载项目目录下的 Playbook 列表。
|
|
266
|
-
*/
|
|
267
|
-
function loadProjectPlaybooks(projectRoot: string, playbooksDir = "playbooks"): Map<string, PlaybookPlanEntry> {
|
|
268
|
-
const map = new Map<string, PlaybookPlanEntry>();
|
|
269
|
-
const dir = join(projectRoot, playbooksDir);
|
|
270
|
-
if (!existsSync(dir)) return map;
|
|
271
|
-
|
|
272
|
-
const files = walkDirectory(dir).filter((f) => f.endsWith(".md"));
|
|
273
|
-
for (const file of files) {
|
|
274
|
-
const pb = parsePlaybookFile(file);
|
|
275
|
-
if (pb) {
|
|
276
|
-
map.set(pb.id, pb);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return map;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* 静态扫描 actions 目录生成备用清单,杜绝动态 import 与代码执行。
|
|
284
|
-
*/
|
|
285
|
-
function generateStaticManifest(projectRoot: string, actionsDir = "actions"): ActionDockManifest {
|
|
286
|
-
const dir = join(projectRoot, actionsDir);
|
|
287
|
-
const actions: Record<string, ActionManifestEntry> = {};
|
|
288
|
-
|
|
289
|
-
if (existsSync(dir)) {
|
|
290
|
-
const files = walkDirectory(dir).filter(
|
|
291
|
-
(f) =>
|
|
292
|
-
(f.endsWith(".ts") || f.endsWith(".js")) &&
|
|
293
|
-
!f.endsWith(".d.ts") &&
|
|
294
|
-
!f.endsWith(".test.ts") &&
|
|
295
|
-
!f.endsWith(".spec.ts")
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
for (const file of files) {
|
|
299
|
-
const relPath = relative(projectRoot, file);
|
|
300
|
-
const filename = basename(file);
|
|
301
|
-
const actionId = filename.replace(/\.(ts|js)$/, "");
|
|
302
|
-
|
|
303
|
-
// 静态正则提取 id 与 uses,不执行模块代码
|
|
304
|
-
let parsedId = actionId;
|
|
305
|
-
const uses: string[] = [];
|
|
306
|
-
try {
|
|
307
|
-
const source = readFileSync(file, "utf-8");
|
|
308
|
-
const idMatch = source.match(/id\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
309
|
-
if (idMatch && idMatch[1]) {
|
|
310
|
-
parsedId = idMatch[1];
|
|
311
|
-
}
|
|
312
|
-
const usesMatch = source.match(/uses\s*:\s*\[([^\]]*)\]/);
|
|
313
|
-
if (usesMatch && usesMatch[1]) {
|
|
314
|
-
const rawItems = usesMatch[1].split(",");
|
|
315
|
-
for (const raw of rawItems) {
|
|
316
|
-
const clean = raw.trim().replace(/^["'`]|["'`]$/g, "");
|
|
317
|
-
if (clean) {
|
|
318
|
-
uses.push(clean);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
} catch {
|
|
323
|
-
// 忽略文件读取异常
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
actions[parsedId] = {
|
|
327
|
-
entry: relPath,
|
|
328
|
-
description: `Action ${parsedId}`,
|
|
329
|
-
uses,
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
schemaVersion: 1,
|
|
336
|
-
actions,
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* 收集项目根目录 package.json 中声明的外部 npm 依赖。
|
|
342
|
-
*/
|
|
343
|
-
function extractExternalDependencies(projectRoot: string): ExternalDependency[] {
|
|
344
|
-
const pkgPath = join(projectRoot, "package.json");
|
|
345
|
-
if (!existsSync(pkgPath)) return [];
|
|
346
|
-
|
|
347
|
-
try {
|
|
348
|
-
const raw = readFileSync(pkgPath, "utf-8");
|
|
349
|
-
const parsed = JSON.parse(raw);
|
|
350
|
-
const deps: ExternalDependency[] = [];
|
|
351
|
-
|
|
352
|
-
if (parsed.dependencies && typeof parsed.dependencies === "object") {
|
|
353
|
-
for (const [name, versionRange] of Object.entries(parsed.dependencies)) {
|
|
354
|
-
deps.push({
|
|
355
|
-
name,
|
|
356
|
-
versionRange: String(versionRange),
|
|
357
|
-
isDev: false,
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (parsed.devDependencies && typeof parsed.devDependencies === "object") {
|
|
363
|
-
for (const [name, versionRange] of Object.entries(parsed.devDependencies)) {
|
|
364
|
-
deps.push({
|
|
365
|
-
name,
|
|
366
|
-
versionRange: String(versionRange),
|
|
367
|
-
isDev: true,
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return deps;
|
|
373
|
-
} catch {
|
|
374
|
-
return [];
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* 构建规划器。
|
|
380
|
-
* 纯声明式解析 actiondock.manifest.json 与配置,绝不执行 Action 业务代码。
|
|
381
|
-
*/
|
|
382
|
-
export class BuildPlanner {
|
|
383
|
-
private projectRoot: string;
|
|
384
|
-
|
|
385
|
-
constructor(options?: { projectRoot?: string }) {
|
|
386
|
-
this.projectRoot = resolve(options?.projectRoot || process.cwd());
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* 执行依赖闭包裁剪与构建规划。
|
|
391
|
-
*
|
|
392
|
-
* @param options 规划参数
|
|
393
|
-
* @returns 完整的 BuildPlan 结构
|
|
394
|
-
*/
|
|
395
|
-
public plan(options?: BuildPlannerOptions): BuildPlan {
|
|
396
|
-
const root = resolve(options?.projectRoot || this.projectRoot);
|
|
397
|
-
|
|
398
|
-
// 1. 获取项目配置(优先使用传入对象,缺失则读取 actiondock.json)
|
|
399
|
-
let config = options?.config;
|
|
400
|
-
if (!config) {
|
|
401
|
-
try {
|
|
402
|
-
config = loadProjectConfig(root);
|
|
403
|
-
} catch (err: any) {
|
|
404
|
-
throw new PlannerError(
|
|
405
|
-
`Failed to load project config: ${err.message}`,
|
|
406
|
-
"CONFIG_LOAD_ERROR"
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// 2. 获取声明式清单(优先使用传入清单,缺失则读取 actiondock.manifest.json,未提供则采用安全静态扫描回退)
|
|
412
|
-
let manifest = options?.manifest;
|
|
413
|
-
if (!manifest) {
|
|
414
|
-
const loaded = loadManifest(root);
|
|
415
|
-
if (loaded) {
|
|
416
|
-
manifest = loaded;
|
|
417
|
-
} else {
|
|
418
|
-
manifest = generateStaticManifest(root, config.actionsDir || "actions");
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const validation = validateManifest(manifest);
|
|
423
|
-
if (!validation.valid) {
|
|
424
|
-
throw new PlannerError(
|
|
425
|
-
`Invalid actiondock.manifest.json: ${(validation.errors || []).join("; ")}`,
|
|
426
|
-
"INVALID_MANIFEST"
|
|
427
|
-
);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// 3. 静态读取 Playbook 规程定义
|
|
431
|
-
const playbooksMap = loadProjectPlaybooks(root, config.playbooksDir || "playbooks");
|
|
432
|
-
|
|
433
|
-
// 4. 计算初始 Action 与 Playbook 集合
|
|
434
|
-
const initialActionIds = new Set<string>();
|
|
435
|
-
let selectedPlaybooks: PlaybookPlanEntry[] = [];
|
|
436
|
-
|
|
437
|
-
// 若指定了 Playbook 列表,进行过滤并提取关联 Action
|
|
438
|
-
if (options?.playbooks && options.playbooks.length > 0) {
|
|
439
|
-
for (const pbId of options.playbooks) {
|
|
440
|
-
const pb = playbooksMap.get(pbId);
|
|
441
|
-
if (!pb) {
|
|
442
|
-
throw new PlannerError(
|
|
443
|
-
`Playbook '${pbId}' specified in build options was not found in project`,
|
|
444
|
-
"PLAYBOOK_NOT_FOUND"
|
|
445
|
-
);
|
|
446
|
-
}
|
|
447
|
-
selectedPlaybooks.push(pb);
|
|
448
|
-
if (pb.actions && pb.actions.length > 0) {
|
|
449
|
-
for (const act of pb.actions) {
|
|
450
|
-
initialActionIds.add(act);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
} else {
|
|
455
|
-
selectedPlaybooks = Array.from(playbooksMap.values());
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// 若显式指定了 Action 列表,加入集合
|
|
459
|
-
if (options?.actions && options.actions.length > 0) {
|
|
460
|
-
for (const actId of options.actions) {
|
|
461
|
-
if (!manifest.actions[actId]) {
|
|
462
|
-
throw new PlannerError(
|
|
463
|
-
`Action '${actId}' specified in build options was not found in manifest`,
|
|
464
|
-
"ACTION_NOT_FOUND"
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
initialActionIds.add(actId);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// 若均未显式指定,默认打包清单中的全部 Action
|
|
472
|
-
if ((!options?.actions || options.actions.length === 0) && (!options?.playbooks || options.playbooks.length === 0)) {
|
|
473
|
-
for (const actId of Object.keys(manifest.actions)) {
|
|
474
|
-
initialActionIds.add(actId);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// 5. 进行依赖闭包(uses)解析计算,自动处理环形依赖与传递依赖(支持当前清单与已链接外部包)
|
|
479
|
-
const resolvedActionIds = new Set<string>();
|
|
480
|
-
const queue = Array.from(initialActionIds);
|
|
481
|
-
const externalActionRoots = new Map<string, string>();
|
|
482
|
-
const externalActionEntries = new Map<string, ActionManifestEntry>();
|
|
483
|
-
|
|
484
|
-
while (queue.length > 0) {
|
|
485
|
-
const currentId = queue.shift()!;
|
|
486
|
-
if (resolvedActionIds.has(currentId)) {
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
let entry = manifest.actions[currentId];
|
|
491
|
-
let entryRoot = root;
|
|
492
|
-
|
|
493
|
-
if (!entry) {
|
|
494
|
-
try {
|
|
495
|
-
const resolvedExternal = resolveActionProjectSync(currentId, root);
|
|
496
|
-
if (resolvedExternal && existsSync(resolvedExternal.projectRoot)) {
|
|
497
|
-
const externalManifest = loadManifest(resolvedExternal.projectRoot);
|
|
498
|
-
const externalEntry = externalManifest?.actions?.[resolvedExternal.actionId];
|
|
499
|
-
if (externalEntry) {
|
|
500
|
-
entry = externalEntry;
|
|
501
|
-
entryRoot = resolvedExternal.projectRoot;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
} catch {
|
|
505
|
-
// 忽略
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (!entry) {
|
|
510
|
-
throw new PlannerError(
|
|
511
|
-
`Action '${currentId}' referenced in dependency closure (uses) was not found in manifest or linked packages`,
|
|
512
|
-
"MISSING_DEPENDENCY"
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
if (entryRoot !== root) {
|
|
517
|
-
externalActionRoots.set(currentId, entryRoot);
|
|
518
|
-
externalActionEntries.set(currentId, entry);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
resolvedActionIds.add(currentId);
|
|
522
|
-
|
|
523
|
-
if (entry.uses && Array.isArray(entry.uses)) {
|
|
524
|
-
for (const depId of entry.uses) {
|
|
525
|
-
if (!resolvedActionIds.has(depId)) {
|
|
526
|
-
queue.push(depId);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// 6. 若指定了 Action 但未指定 Playbook,反向裁剪排除无法满足依赖的 Playbook
|
|
533
|
-
if (options?.actions && options.actions.length > 0 && (!options.playbooks || options.playbooks.length === 0)) {
|
|
534
|
-
selectedPlaybooks = selectedPlaybooks.filter((pb) => {
|
|
535
|
-
if (!pb.actions || pb.actions.length === 0) return true;
|
|
536
|
-
return pb.actions.every((a) => resolvedActionIds.has(a));
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// 7. 构造 Action 依赖结构
|
|
541
|
-
const actionDependencies: ActionDependency[] = [];
|
|
542
|
-
for (const actId of resolvedActionIds) {
|
|
543
|
-
const entry = manifest.actions[actId] || externalActionEntries.get(actId);
|
|
544
|
-
const entryRoot = externalActionRoots.get(actId) || root;
|
|
545
|
-
if (!entry) {
|
|
546
|
-
throw new PlannerError(
|
|
547
|
-
`Action '${actId}' entry not found`,
|
|
548
|
-
"ENTRY_FILE_NOT_FOUND"
|
|
549
|
-
);
|
|
550
|
-
}
|
|
551
|
-
const resolvedPath = resolve(entryRoot, entry.entry);
|
|
552
|
-
if (!existsSync(resolvedPath)) {
|
|
553
|
-
throw new PlannerError(
|
|
554
|
-
`Action '${actId}' entry file not found on disk: ${entry.entry}`,
|
|
555
|
-
"ENTRY_FILE_NOT_FOUND"
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
actionDependencies.push({
|
|
559
|
-
id: actId,
|
|
560
|
-
entry: entry.entry,
|
|
561
|
-
resolvedPath,
|
|
562
|
-
uses: entry.uses || [],
|
|
563
|
-
description: entry.description,
|
|
564
|
-
inputSchema: entry.inputSchema,
|
|
565
|
-
outputSchema: entry.outputSchema,
|
|
566
|
-
tags: entry.tags,
|
|
567
|
-
annotations: entry.annotations,
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
// 8. 构造模块与资产依赖结构
|
|
572
|
-
const modulesAndAssets: AssetDependency[] = [];
|
|
573
|
-
const assetPathSet = new Set<string>();
|
|
574
|
-
const modulePathSet = new Set<string>();
|
|
575
|
-
const actionPathSet = new Set(actionDependencies.map((a) => a.resolvedPath));
|
|
576
|
-
|
|
577
|
-
// 静态递归追踪 Action 源码引用的本地模块代码(如 lib/、辅助工具等)
|
|
578
|
-
const rootActionsMap = new Map<string, string[]>();
|
|
579
|
-
for (const act of actionDependencies) {
|
|
580
|
-
const actRoot = externalActionRoots.get(act.id) || root;
|
|
581
|
-
if (!rootActionsMap.has(actRoot)) {
|
|
582
|
-
rootActionsMap.set(actRoot, []);
|
|
583
|
-
}
|
|
584
|
-
rootActionsMap.get(actRoot)!.push(act.resolvedPath);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
for (const [actRoot, files] of rootActionsMap) {
|
|
588
|
-
const tracedModulePaths = traceLocalModuleDependencies(files, actRoot);
|
|
589
|
-
for (const modPath of tracedModulePaths) {
|
|
590
|
-
if (!actionPathSet.has(modPath)) {
|
|
591
|
-
const rel = relative(actRoot, modPath).replace(/\\/g, "/");
|
|
592
|
-
if (!modulePathSet.has(rel) && !isIgnoredModulePath(rel)) {
|
|
593
|
-
modulePathSet.add(rel);
|
|
594
|
-
modulesAndAssets.push({
|
|
595
|
-
path: rel,
|
|
596
|
-
resolvedPath: modPath,
|
|
597
|
-
type: "module",
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// 若未显式过滤 Action 与 Playbook(全量构建模式),且存在根目录 lib 目录,自动全量扫描 lib 源码
|
|
605
|
-
const isSelective =
|
|
606
|
-
Boolean(options?.actions && options.actions.length > 0) ||
|
|
607
|
-
Boolean(options?.playbooks && options.playbooks.length > 0);
|
|
608
|
-
|
|
609
|
-
if (!isSelective) {
|
|
610
|
-
const defaultLibDir = join(root, "lib");
|
|
611
|
-
if (existsSync(defaultLibDir)) {
|
|
612
|
-
const libFiles = walkDirectory(defaultLibDir);
|
|
613
|
-
for (const file of libFiles) {
|
|
614
|
-
const rel = relative(root, file).replace(/\\/g, "/");
|
|
615
|
-
if (!isIgnoredModulePath(rel) && !actionPathSet.has(file) && !modulePathSet.has(rel)) {
|
|
616
|
-
modulePathSet.add(rel);
|
|
617
|
-
modulesAndAssets.push({
|
|
618
|
-
path: rel,
|
|
619
|
-
resolvedPath: file,
|
|
620
|
-
type: "module",
|
|
621
|
-
});
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// 清单声明资产
|
|
628
|
-
if (manifest.assets && Array.isArray(manifest.assets)) {
|
|
629
|
-
for (const assetRel of manifest.assets) {
|
|
630
|
-
if (!assetPathSet.has(assetRel)) {
|
|
631
|
-
assetPathSet.add(assetRel);
|
|
632
|
-
modulesAndAssets.push({
|
|
633
|
-
path: assetRel,
|
|
634
|
-
resolvedPath: resolve(root, assetRel),
|
|
635
|
-
type: "asset",
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// 默认 assets 目录资产扫描
|
|
642
|
-
const defaultAssetsDir = join(root, "assets");
|
|
643
|
-
if (existsSync(defaultAssetsDir)) {
|
|
644
|
-
const assetFiles = walkDirectory(defaultAssetsDir);
|
|
645
|
-
for (const file of assetFiles) {
|
|
646
|
-
const rel = relative(root, file);
|
|
647
|
-
if (!assetPathSet.has(rel)) {
|
|
648
|
-
assetPathSet.add(rel);
|
|
649
|
-
modulesAndAssets.push({
|
|
650
|
-
path: rel,
|
|
651
|
-
resolvedPath: file,
|
|
652
|
-
type: "asset",
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
// Playbook 规程文档依赖
|
|
659
|
-
for (const pb of selectedPlaybooks) {
|
|
660
|
-
const rel = relative(root, pb.filePath);
|
|
661
|
-
modulesAndAssets.push({
|
|
662
|
-
path: rel,
|
|
663
|
-
resolvedPath: pb.filePath,
|
|
664
|
-
type: "playbook",
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// 项目核心配置文件
|
|
669
|
-
const configPath = join(root, "actiondock.json");
|
|
670
|
-
if (existsSync(configPath)) {
|
|
671
|
-
modulesAndAssets.push({
|
|
672
|
-
path: "actiondock.json",
|
|
673
|
-
resolvedPath: configPath,
|
|
674
|
-
type: "config",
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const manifestPath = join(root, "actiondock.manifest.json");
|
|
679
|
-
if (existsSync(manifestPath)) {
|
|
680
|
-
modulesAndAssets.push({
|
|
681
|
-
path: "actiondock.manifest.json",
|
|
682
|
-
resolvedPath: manifestPath,
|
|
683
|
-
type: "config",
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// 9. 外部 npm 依赖解析
|
|
688
|
-
const externalDependencies = extractExternalDependencies(root);
|
|
689
|
-
|
|
690
|
-
// 10. 生成最终 BuildPlan
|
|
691
|
-
const dependencies: BuildPlanDependencies = {
|
|
692
|
-
actions: actionDependencies,
|
|
693
|
-
modulesAndAssets,
|
|
694
|
-
external: externalDependencies,
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
return {
|
|
698
|
-
packageId: config.id,
|
|
699
|
-
packageName: config.name || config.id,
|
|
700
|
-
version: config.version || "0.1.0",
|
|
701
|
-
description: config.description,
|
|
702
|
-
projectRoot: root,
|
|
703
|
-
actions: actionDependencies,
|
|
704
|
-
playbooks: selectedPlaybooks,
|
|
705
|
-
dependencies,
|
|
706
|
-
assets: Array.from(assetPathSet),
|
|
707
|
-
configDefs: config.config,
|
|
708
|
-
metadata: {
|
|
709
|
-
plannedAt: new Date().toISOString(),
|
|
710
|
-
schemaVersion: 1,
|
|
711
|
-
actionCount: actionDependencies.length,
|
|
712
|
-
playbookCount: selectedPlaybooks.length,
|
|
713
|
-
},
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* 静态辅助调用方法。
|
|
719
|
-
*/
|
|
720
|
-
public static plan(options: BuildPlannerOptions): BuildPlan {
|
|
721
|
-
const planner = new BuildPlanner({ projectRoot: options.projectRoot });
|
|
722
|
-
return planner.plan(options);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
/**
|
|
727
|
-
* 快捷构建规划函数。
|
|
728
|
-
*/
|
|
729
|
-
export function buildPlan(options: BuildPlannerOptions): BuildPlan {
|
|
730
|
-
return BuildPlanner.plan(options);
|
|
731
|
-
}
|