@actiondock/builder 2.0.12 → 2.2.1

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/build.js ADDED
@@ -0,0 +1,576 @@
1
+ import { createHash } from "node:crypto";
2
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs";
3
+ import { basename, dirname, join, resolve } from "node:path";
4
+ import { STANDALONE_ASYNC_UNSUPPORTED, getPackageSlug, } from "@actiondock/core";
5
+ import { createZipArchiveAsync } from "./archive.js";
6
+ import { BuilderError } from "./errors.js";
7
+ import { collectRelativeFiles, getInternalDependencyVersion, replaceDirAtomic } from "./fs-utils.js";
8
+ import { assertValidManifestActionIds, serializePlanManifest } from "./manifest.js";
9
+ import { SelectionPlanner } from "./planner.js";
10
+ /**
11
+ * 检查外部依赖中声明的生命周期安装脚本。
12
+ */
13
+ function detectLifecycleScripts(root, dependencies) {
14
+ const detected = [];
15
+ const checked = new Set();
16
+ for (const dep of dependencies) {
17
+ if (checked.has(dep.name))
18
+ continue;
19
+ checked.add(dep.name);
20
+ const candidates = [
21
+ join(root, "node_modules", dep.name, "package.json"),
22
+ resolve(import.meta.dirname, "../../../node_modules", dep.name, "package.json"),
23
+ ];
24
+ for (const cand of candidates) {
25
+ if (existsSync(cand)) {
26
+ try {
27
+ const raw = readFileSync(cand, "utf-8");
28
+ const pkg = JSON.parse(raw);
29
+ const scripts = pkg.scripts || {};
30
+ const lifecycleStages = ["preinstall", "install", "postinstall"];
31
+ for (const stage of lifecycleStages) {
32
+ if (typeof scripts[stage] === "string" && scripts[stage].trim().length > 0) {
33
+ detected.push({
34
+ package: dep.name,
35
+ version: pkg.version || dep.versionRange || "*",
36
+ stage,
37
+ });
38
+ }
39
+ }
40
+ }
41
+ catch {
42
+ // 忽略单个依赖解析异常
43
+ }
44
+ break;
45
+ }
46
+ }
47
+ }
48
+ return detected;
49
+ }
50
+ /**
51
+ * 计算目录内容规范化 SHA-256 摘要。
52
+ */
53
+ function calculateDirectoryDigest(dir) {
54
+ const files = collectRelativeFiles(dir);
55
+ const hasher = createHash("sha256");
56
+ for (const file of files) {
57
+ hasher.update(file);
58
+ const fullPath = join(dir, file);
59
+ hasher.update(readFileSync(fullPath));
60
+ }
61
+ return hasher.digest("hex");
62
+ }
63
+ /**
64
+ * 生成 Host 子进程入口脚本源码(负责运行 ActionDockHost,通过 Node IPC 暴露 Target)。
65
+ */
66
+ function generateNodeHostEntrySource(plan, relativeActionPaths) {
67
+ const imports = relativeActionPaths
68
+ .map((relPath, idx) => `import action_${idx} from ${JSON.stringify(relPath)};`)
69
+ .join("\n");
70
+ const actionsDict = {};
71
+ for (const a of plan.actions) {
72
+ actionsDict[a.id] = {
73
+ entry: a.entry,
74
+ description: a.description || "",
75
+ inputSchema: a.inputSchema ?? null,
76
+ outputSchema: a.outputSchema ?? null,
77
+ uses: a.uses || [],
78
+ tags: a.tags || [],
79
+ annotations: a.annotations || {},
80
+ };
81
+ }
82
+ const actionItems = plan.actions
83
+ .map((a, idx) => {
84
+ const entryObj = `{
85
+ ...(typeof action_${idx} === "function" ? { run: action_${idx} } : action_${idx}),
86
+ id: action_${idx}?.id || ${JSON.stringify(a.id)},
87
+ description: action_${idx}?.description || ${JSON.stringify(a.description || "")},
88
+ inputSchema: action_${idx}?.inputSchema ?? ${JSON.stringify(a.inputSchema ?? null)},
89
+ outputSchema: action_${idx}?.outputSchema ?? ${JSON.stringify(a.outputSchema ?? null)},
90
+ uses: action_${idx}?.uses || ${JSON.stringify(a.uses || [])},
91
+ tags: action_${idx}?.tags || ${JSON.stringify(a.tags || [])},
92
+ annotations: action_${idx}?.annotations || ${JSON.stringify(a.annotations || {})},
93
+ }`;
94
+ return entryObj;
95
+ })
96
+ .join(",\n ");
97
+ return `#!/usr/bin/env node
98
+ // AUTO-GENERATED HOST ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
99
+ import {
100
+ createActionDockHost,
101
+ createActionDockTarget,
102
+ serveParentIpc,
103
+ } from "@actiondock/core";
104
+ import { createNodePlatform } from "@actiondock/runtime-node";
105
+ ${imports}
106
+
107
+ let dataDir;
108
+ const configOverrides = {};
109
+ const args = process.argv.slice(2);
110
+ for (let i = 0; i < args.length; i++) {
111
+ if (args[i] === "--data-dir" && i + 1 < args.length) {
112
+ dataDir = args[++i];
113
+ } else if (args[i].startsWith("--data-dir=")) {
114
+ dataDir = args[i].slice(11);
115
+ } else if (args[i] === "--config" && i + 1 < args.length) {
116
+ const raw = args[++i];
117
+ const [k, ...v] = raw.split("=");
118
+ if (k) configOverrides[k] = v.join("=");
119
+ } else if (args[i].startsWith("--config=")) {
120
+ const raw = args[i].slice(9);
121
+ const [k, ...v] = raw.split("=");
122
+ if (k) configOverrides[k] = v.join("=");
123
+ }
124
+ }
125
+
126
+ const host = await createActionDockHost({
127
+ dataDir,
128
+ packages: [
129
+ {
130
+ dataDir,
131
+ configOverrides,
132
+ projectConfig: {
133
+ id: ${JSON.stringify(plan.packageId)},
134
+ name: ${JSON.stringify(plan.packageName)},
135
+ version: ${JSON.stringify(plan.version)},
136
+ description: ${JSON.stringify(plan.description || "")},
137
+ config: ${JSON.stringify(plan.configDefs || {})},
138
+ actions: ${JSON.stringify(actionsDict)},
139
+ },
140
+ actions: [
141
+ ${actionItems}
142
+ ],
143
+ },
144
+ ],
145
+ platform: createNodePlatform({ dataDir }),
146
+ });
147
+
148
+ const target = await createActionDockTarget({ type: "local", host });
149
+ await serveParentIpc(target);
150
+ `;
151
+ }
152
+ /**
153
+ * 生成轻量监督父进程脚本源码(负责参数解析、诊断日志限流、退出码管理与标准输出隔离)。
154
+ */
155
+ function generateNodeSupervisorEntrySource(plan) {
156
+ return `#!/usr/bin/env node
157
+ // AUTO-GENERATED SUPERVISOR ENTRYPOINT BY ACTIONDOCK BUILDER. DO NOT EDIT.
158
+ import { spawn } from "node:child_process";
159
+ import { join } from "node:path";
160
+ import {
161
+ IpcActionDockTarget,
162
+ StandaloneDispatcher,
163
+ ExitCode,
164
+ STANDALONE_ASYNC_UNSUPPORTED,
165
+ } from "@actiondock/core";
166
+
167
+ const METADATA = {
168
+ packageId: ${JSON.stringify(plan.packageId)},
169
+ version: ${JSON.stringify(plan.version)},
170
+ description: ${JSON.stringify(plan.description || "")},
171
+ };
172
+
173
+ const argv = process.argv.slice(2);
174
+
175
+ // 1. 监督进程接管参数校验,明确拒绝 --async
176
+ if (argv.includes("--async")) {
177
+ const isJson = argv.includes("--json") || argv.includes("--envelope");
178
+ if (isJson) {
179
+ console.log(
180
+ JSON.stringify(
181
+ {
182
+ ok: false,
183
+ error: {
184
+ code: ${JSON.stringify(STANDALONE_ASYNC_UNSUPPORTED)},
185
+ message:
186
+ "Async execution is not supported in standalone single-execution binaries. Use 'ad serve' or remote target.",
187
+ },
188
+ },
189
+ null,
190
+ 2
191
+ )
192
+ );
193
+ } else {
194
+ console.error(
195
+ "Error [" + STANDALONE_ASYNC_UNSUPPORTED + "]: Async execution is not supported in standalone single-execution binaries."
196
+ );
197
+ }
198
+ process.exit(ExitCode.FAILURE);
199
+ }
200
+
201
+ // 2. 静态元数据快速返回
202
+ if (argv.includes("-v") || argv.includes("-V") || argv.includes("--version") || argv[0] === "version") {
203
+ console.log(\`\${METADATA.packageId} v\${METADATA.version}\`);
204
+ process.exit(ExitCode.SUCCESS);
205
+ }
206
+
207
+ if (argv.includes("-h") || argv.includes("--help") || argv[0] === "help") {
208
+ console.log(\`\${METADATA.packageId} (v\${METADATA.version})\`);
209
+ if (METADATA.description) console.log(METADATA.description + "\\n");
210
+ console.log("Usage:");
211
+ console.log(" <cmd> list [--json] List available actions");
212
+ console.log(" <cmd> describe <id> [--json] Show action details and schemas");
213
+ console.log(" <cmd> run <id> [--input '<json>'] Execute action with JSON input");
214
+ console.log(" <cmd> config list/get/set/delete Manage package configuration");
215
+ console.log(" <cmd> state list/get/set/delete Manage shared state store");
216
+ console.log("\\nGlobal options:");
217
+ console.log(" --data-dir <path> Custom runtime database directory");
218
+ console.log(" --config <KEY=val> Temporary config override");
219
+ process.exit(ExitCode.SUCCESS);
220
+ }
221
+
222
+ // 3. 建立物理隔离监督边界,启动运行 ActionDockHost 的独立子进程
223
+ const hostScript = join(import.meta.dirname, "entry-host.js");
224
+ const child = spawn(process.execPath, [hostScript, ...argv], {
225
+ cwd: process.cwd(),
226
+ env: process.env,
227
+ stdio: ["pipe", "pipe", "pipe", "ipc"],
228
+ });
229
+
230
+ // 4. 标准输出通道物理隔离与受控限流排空
231
+ const target = new IpcActionDockTarget({
232
+ childProcess: child,
233
+ maxDiagnosticBytes: 512 * 1024,
234
+ maxDiagnosticRate: 64 * 1024,
235
+ diagnosticTarget: process.stderr,
236
+ });
237
+
238
+ let cleanedUp = false;
239
+ const cleanup = async () => {
240
+ if (cleanedUp) return;
241
+ cleanedUp = true;
242
+ try {
243
+ await target.close();
244
+ } catch {}
245
+ };
246
+
247
+ process.once("SIGINT", async () => {
248
+ await cleanup();
249
+ process.exit(ExitCode.SIGINT);
250
+ });
251
+
252
+ process.once("SIGTERM", async () => {
253
+ await cleanup();
254
+ process.exit(143);
255
+ });
256
+
257
+ const dispatcher = new StandaloneDispatcher({
258
+ packageId: METADATA.packageId,
259
+ version: METADATA.version,
260
+ description: METADATA.description,
261
+ target,
262
+ });
263
+
264
+ try {
265
+ const exitCode = await dispatcher.dispatch(argv);
266
+ await cleanup();
267
+ process.exit(exitCode);
268
+ } catch (err) {
269
+ if (err?.code === "HOST_PROCESS_EXITED") {
270
+ console.error("[Supervisor] Host process exited unexpectedly:", err.message);
271
+ } else {
272
+ console.error("[Supervisor] Error:", err?.message || err);
273
+ }
274
+ await cleanup();
275
+ process.exit(ExitCode.FAILURE);
276
+ }
277
+ `;
278
+ }
279
+ /**
280
+ * 拷贝 Action 源码、Playbook 规程与声明的代码文件/静态资产到暂存目录。
281
+ */
282
+ function stageSources(root, stagingDir, plan) {
283
+ // 拷贝 Action 源码文件,保留相对路径
284
+ const relativeActionImports = [];
285
+ for (const act of plan.actions) {
286
+ if (existsSync(act.resolvedPath)) {
287
+ const destFile = join(stagingDir, act.entry);
288
+ mkdirSync(dirname(destFile), { recursive: true });
289
+ copyFileSync(act.resolvedPath, destFile);
290
+ relativeActionImports.push(`./${act.entry.replace(/\\/g, "/")}`);
291
+ }
292
+ }
293
+ // 拷贝 Playbook 规程文件
294
+ if (plan.playbooks.length > 0) {
295
+ const playbooksDir = plan.playbooksDir || "playbooks";
296
+ const playbooksDestDir = join(stagingDir, playbooksDir);
297
+ mkdirSync(playbooksDestDir, { recursive: true });
298
+ for (const pb of plan.playbooks) {
299
+ if (existsSync(pb.filePath)) {
300
+ const destPb = join(playbooksDestDir, basename(pb.filePath));
301
+ copyFileSync(pb.filePath, destPb);
302
+ }
303
+ }
304
+ }
305
+ // 拷贝声明的代码文件与静态资产
306
+ for (const dep of plan.dependencies.modulesAndAssets) {
307
+ if ((dep.type === "asset" || dep.type === "module" || dep.type === "file") &&
308
+ existsSync(dep.resolvedPath)) {
309
+ const destPath = join(stagingDir, dep.path);
310
+ mkdirSync(dirname(destPath), { recursive: true });
311
+ copyFileSync(dep.resolvedPath, destPath);
312
+ }
313
+ }
314
+ return relativeActionImports;
315
+ }
316
+ /**
317
+ * 生成裁剪后的 actiondock.json(项目元数据与清单的单一事实源)。
318
+ */
319
+ function writeManifest(stagingDir, plan) {
320
+ const exportedConfig = serializePlanManifest(plan);
321
+ if (exportedConfig.actions && typeof exportedConfig.actions === "object") {
322
+ assertValidManifestActionIds(exportedConfig.actions);
323
+ }
324
+ writeFileSync(join(stagingDir, "actiondock.json"), JSON.stringify(exportedConfig, null, 2) + "\n", "utf-8");
325
+ }
326
+ /**
327
+ * 生成部署用 package.json(指向监督父进程双入口体系)并复制锁文件。
328
+ */
329
+ function writePkgJsonAndLockfiles(root, stagingDir, plan, pkgSlug) {
330
+ // 准备锁定的生产依赖
331
+ const productionDependencies = {
332
+ "@actiondock/core": getInternalDependencyVersion(),
333
+ "@actiondock/runtime-node": getInternalDependencyVersion(),
334
+ "@actiondock/sdk": getInternalDependencyVersion(),
335
+ };
336
+ for (const ext of plan.dependencies.external) {
337
+ if (!ext.isDev) {
338
+ productionDependencies[ext.name] = ext.versionRange || "*";
339
+ }
340
+ }
341
+ const deploymentPkg = {
342
+ name: pkgSlug,
343
+ version: plan.version,
344
+ description: plan.description,
345
+ type: "module",
346
+ main: "./entry-supervisor.js",
347
+ bin: {
348
+ [pkgSlug]: "./entry-supervisor.js",
349
+ },
350
+ engines: {
351
+ node: ">=24.12.0",
352
+ },
353
+ dependencies: productionDependencies,
354
+ };
355
+ writeFileSync(join(stagingDir, "package.json"), JSON.stringify(deploymentPkg, null, 2) + "\n", "utf-8");
356
+ // 复制锁文件(若存在)
357
+ if (plan.lockfile && existsSync(plan.lockfile.path)) {
358
+ copyFileSync(plan.lockfile.path, join(stagingDir, plan.lockfile.name));
359
+ }
360
+ const actiondockLock = join(root, "actiondock.lock.json");
361
+ if (existsSync(actiondockLock)) {
362
+ copyFileSync(actiondockLock, join(stagingDir, "actiondock.lock.json"));
363
+ }
364
+ }
365
+ /**
366
+ * 生成三个入口脚本(Host 子进程、监督父进程与兼容代理转发入口)并设置可执行权限。
367
+ */
368
+ function writeEntrypoints(stagingDir, plan, relativeActionImports) {
369
+ const hostCode = generateNodeHostEntrySource(plan, relativeActionImports);
370
+ const hostPath = join(stagingDir, "entry-host.js");
371
+ writeFileSync(hostPath, hostCode, "utf-8");
372
+ const supervisorCode = generateNodeSupervisorEntrySource(plan);
373
+ const supervisorPath = join(stagingDir, "entry-supervisor.js");
374
+ writeFileSync(supervisorPath, supervisorCode, "utf-8");
375
+ const forwarderCode = `#!/usr/bin/env node\n// AUTO-GENERATED ENTRYPOINT FORWARDER BY ACTIONDOCK BUILDER. DO NOT EDIT.\nimport "./entry-supervisor.js";\n`;
376
+ const forwarderPath = join(stagingDir, "entry.mjs");
377
+ writeFileSync(forwarderPath, forwarderCode, "utf-8");
378
+ try {
379
+ chmodSync(hostPath, 0o755);
380
+ chmodSync(supervisorPath, 0o755);
381
+ chmodSync(forwarderPath, 0o755);
382
+ }
383
+ catch {
384
+ // 忽略部分平台权限设置异常
385
+ }
386
+ }
387
+ /**
388
+ * 物化锁定生产依赖到产物 node_modules 目录(vendorDeps 开启时)。
389
+ */
390
+ function vendorDependencies(root, stagingDir, plan) {
391
+ const destNodeModules = join(stagingDir, "node_modules");
392
+ mkdirSync(destNodeModules, { recursive: true });
393
+ // 尝试复制本地已解析的生产依赖
394
+ const sourceCandidates = [
395
+ join(root, "node_modules"),
396
+ resolve(import.meta.dirname, "../../../node_modules"),
397
+ ];
398
+ const copyVendorPackage = (srcDir, destDir) => {
399
+ if (!existsSync(srcDir))
400
+ return;
401
+ mkdirSync(destDir, { recursive: true });
402
+ const entries = readdirSync(srcDir);
403
+ for (const entry of entries) {
404
+ if (entry === "node_modules" ||
405
+ entry === ".git" ||
406
+ entry === ".actiondock" ||
407
+ entry === "test" ||
408
+ entry === "tests") {
409
+ continue;
410
+ }
411
+ const srcPath = join(srcDir, entry);
412
+ const destPath = join(destDir, entry);
413
+ try {
414
+ const stat = statSync(srcPath);
415
+ if (stat.isDirectory()) {
416
+ copyVendorPackage(srcPath, destPath);
417
+ }
418
+ else if (stat.isFile()) {
419
+ copyFileSync(srcPath, destPath);
420
+ }
421
+ }
422
+ catch {
423
+ // 忽略无法读取的文件或死链接
424
+ }
425
+ }
426
+ };
427
+ for (const dep of plan.dependencies.external) {
428
+ if (dep.isDev)
429
+ continue;
430
+ for (const baseModules of sourceCandidates) {
431
+ const srcDep = join(baseModules, dep.name);
432
+ if (existsSync(srcDep)) {
433
+ const targetDep = join(destNodeModules, dep.name);
434
+ mkdirSync(dirname(targetDep), { recursive: true });
435
+ copyVendorPackage(srcDep, targetDep);
436
+ break;
437
+ }
438
+ }
439
+ }
440
+ // 复制 ActionDock 内部依赖
441
+ for (const internalPkg of ["@actiondock/core", "@actiondock/sdk", "@actiondock/runtime-node"]) {
442
+ for (const baseModules of sourceCandidates) {
443
+ const srcDep = join(baseModules, internalPkg);
444
+ if (existsSync(srcDep)) {
445
+ const targetDep = join(destNodeModules, internalPkg);
446
+ mkdirSync(dirname(targetDep), { recursive: true });
447
+ copyVendorPackage(srcDep, targetDep);
448
+ break;
449
+ }
450
+ }
451
+ }
452
+ }
453
+ /**
454
+ * 构建 Node.js 目录型交付产物。
455
+ * 主流程仅做编排,各阶段职责由独立函数承担。
456
+ *
457
+ * @param options 构建选项
458
+ * @returns 构建产物结果描述
459
+ */
460
+ export async function buildProject(options) {
461
+ // 彻底删除原有单文件二进制输出语义,明确拒绝并报错
462
+ if (options.target !== undefined || options.bytecode !== undefined) {
463
+ throw new BuilderError("Unsupported build mode: '--target' and '--bytecode' standalone single-file binary compilation have been removed in ActionDock 2.0. Directory-based Node.js builds are now the standard distribution format.", "UNSUPPORTED_BUILD_MODE");
464
+ }
465
+ const root = resolve(options.projectRoot);
466
+ const plan = SelectionPlanner.plan({
467
+ projectRoot: root,
468
+ config: options.config,
469
+ manifest: options.manifest,
470
+ actions: options.actions,
471
+ playbooks: options.playbooks,
472
+ skipDependencyValidation: options.skipDependencyValidation,
473
+ });
474
+ if (plan.actions.length === 0) {
475
+ throw new BuilderError("No actions resolved for build");
476
+ }
477
+ // 检查依赖中的生命周期安装脚本
478
+ const lifecycleScripts = detectLifecycleScripts(root, plan.dependencies.external);
479
+ const requiresInstallScripts = lifecycleScripts.length > 0;
480
+ // 可复现性校验:若要求可复现且必须执行安装脚本,在创建产物前报错
481
+ if (options.requireReproducible && options.allowInstallScripts && requiresInstallScripts) {
482
+ throw new BuilderError("Cannot produce reproducible build when lifecycle install scripts are required to run.", "REPRODUCIBLE_BUILD_VIOLATION");
483
+ }
484
+ const reproducible = !options.allowInstallScripts || !requiresInstallScripts;
485
+ const pkgSlug = getPackageSlug(plan.packageId);
486
+ const defaultOutDir = join(root, "dist", `${pkgSlug}-build`);
487
+ const rawTargetOut = options.outDir || options.outfile || defaultOutDir;
488
+ let outputDir;
489
+ let shouldArchive = Boolean(options.archive);
490
+ if (rawTargetOut.endsWith(".zip")) {
491
+ shouldArchive = true;
492
+ outputDir = resolve(rawTargetOut.slice(0, -4));
493
+ }
494
+ else {
495
+ outputDir = resolve(rawTargetOut);
496
+ }
497
+ const parentDir = dirname(outputDir);
498
+ mkdirSync(parentDir, { recursive: true });
499
+ const stagingDir = join(parentDir, `.tmp-build-${basename(outputDir)}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
500
+ mkdirSync(stagingDir, { recursive: true });
501
+ let platformInfo;
502
+ try {
503
+ const relativeActionImports = stageSources(root, stagingDir, plan);
504
+ writeManifest(stagingDir, plan);
505
+ writePkgJsonAndLockfiles(root, stagingDir, plan, pkgSlug);
506
+ writeEntrypoints(stagingDir, plan, relativeActionImports);
507
+ // 若开启 options.vendorDeps,物化锁定生产依赖
508
+ if (options.vendorDeps) {
509
+ platformInfo = {
510
+ os: process.platform,
511
+ arch: process.arch,
512
+ nodeAbi: process.versions.modules,
513
+ };
514
+ vendorDependencies(root, stagingDir, plan);
515
+ }
516
+ // 生成构建元数据 artifact.json
517
+ const metadataObj = {
518
+ schemaVersion: 2,
519
+ packageId: plan.packageId,
520
+ packageName: plan.packageName,
521
+ version: plan.version,
522
+ description: plan.description,
523
+ actions: plan.actions.map((a) => a.id),
524
+ playbooks: plan.playbooks.map((p) => p.id),
525
+ engines: {
526
+ node: ">=24.12.0",
527
+ },
528
+ entrypoint: "entry-supervisor.js",
529
+ supervisorEntry: "entry-supervisor.js",
530
+ hostEntry: "entry-host.js",
531
+ vendorDeps: Boolean(options.vendorDeps),
532
+ allowInstallScripts: Boolean(options.allowInstallScripts),
533
+ installScriptsRun: options.allowInstallScripts ? lifecycleScripts : [],
534
+ reproducible,
535
+ platform: platformInfo,
536
+ manifestDigest: plan.metadata?.lockfileDigest || "",
537
+ lockfileDigest: plan.lockfile?.sha256,
538
+ files: collectRelativeFiles(stagingDir),
539
+ };
540
+ writeFileSync(join(stagingDir, "artifact.json"), JSON.stringify(metadataObj, null, 2) + "\n", "utf-8");
541
+ // 原子移动至目标产物目录
542
+ await replaceDirAtomic(stagingDir, outputDir);
543
+ }
544
+ finally {
545
+ if (existsSync(stagingDir)) {
546
+ rmSync(stagingDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
547
+ }
548
+ }
549
+ // 归档处理
550
+ let archivePath;
551
+ if (shouldArchive) {
552
+ const defaultArchive = `${outputDir}.zip`;
553
+ archivePath = defaultArchive;
554
+ if (existsSync(archivePath)) {
555
+ rmSync(archivePath, { force: true });
556
+ }
557
+ await createZipArchiveAsync(outputDir, archivePath);
558
+ }
559
+ const sha256 = calculateDirectoryDigest(outputDir);
560
+ const finalEntrypoint = join(outputDir, "entry-supervisor.js");
561
+ const finalMetadata = join(outputDir, "artifact.json");
562
+ return {
563
+ packageId: plan.packageId,
564
+ version: plan.version,
565
+ outputDir,
566
+ archivePath,
567
+ entrypointPath: finalEntrypoint,
568
+ executablePath: finalEntrypoint,
569
+ metadataPath: finalMetadata,
570
+ actions: plan.actions.map((a) => a.id),
571
+ playbooks: plan.playbooks.map((p) => p.id),
572
+ vendorDeps: Boolean(options.vendorDeps),
573
+ reproducible,
574
+ sha256,
575
+ };
576
+ }
@@ -0,0 +1,14 @@
1
+ import type { SelectionPlan } from "./types.js";
2
+ /**
3
+ * 从源码中快速提取以相对路径(./ 或 ../)引入的静态或动态导入说明符。
4
+ */
5
+ export declare function extractRelativeSpecifiers(source: string): string[];
6
+ /**
7
+ * 将相对模块说明符解析为磁盘上的物理真实文件路径。
8
+ */
9
+ export declare function resolveRelativeModule(fromDir: string, specifier: string): string | undefined;
10
+ /**
11
+ * 断言 Action 源码中引用的本地相对模块已全部纳入导出规划。
12
+ * 若发现引用了未被 actions 或 files 声明收集的本地文件,直接抛出 BuilderError 终止构建。
13
+ */
14
+ export declare function assertRelativeDependenciesIntegrity(projectRoot: string, plan: SelectionPlan): void;