@actiondock/core 2.0.9 → 2.0.11-beta.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.
@@ -55,11 +55,27 @@ export function createStorage(
55
55
  * 工厂函数:创建或连接 ActionDock 全局共享数据库(~/.actiondock/global.db)。
56
56
  * 用于跨 Package 共享的全局配置项存储。
57
57
  *
58
- * @param customHome 自定义家目录路径(可选)
58
+ * @param customHomeOrOptions 自定义家目录路径或配置对象(可选)
59
+ * @param dataDirArg 自定义数据存储目录(可选)
59
60
  */
60
- export function createGlobalStorage(customHome?: string): RuntimeStorage {
61
- const baseDir = getActionDockHome(customHome);
62
- const dbPath = join(baseDir, ".actiondock", "global.db");
61
+ export function createGlobalStorage(
62
+ customHomeOrOptions?: string | { customHome?: string; dataDir?: string },
63
+ dataDirArg?: string
64
+ ): RuntimeStorage {
65
+ let customHome: string | undefined;
66
+ let dataDir: string | undefined;
67
+
68
+ if (typeof customHomeOrOptions === "object" && customHomeOrOptions !== null) {
69
+ customHome = customHomeOrOptions.customHome;
70
+ dataDir = customHomeOrOptions.dataDir;
71
+ } else {
72
+ customHome = customHomeOrOptions;
73
+ dataDir = dataDirArg;
74
+ }
75
+
76
+ const dbPath = dataDir
77
+ ? join(dataDir, "global.db")
78
+ : join(getActionDockHome(customHome), ".actiondock", "global.db");
63
79
  return new SqliteRuntimeStorage({ dbPath, packageId: "__global__" });
64
80
  }
65
81
 
@@ -1,10 +1,12 @@
1
1
  import { chmodSync, existsSync, mkdirSync } from "node:fs";
2
2
  import { dirname } from "node:path";
3
3
  import type { JsonValue, RuntimeError, RunRecord } from "@actiondock/sdk";
4
+ import { type Clock, getSystemClock } from "../runtime/clock";
4
5
  import { createDefaultSqliteDriver } from "./driver";
5
6
  import type {
6
7
  RuntimeStorage,
7
8
  SqliteDriver,
9
+ SqliteStatement,
8
10
  StateEntry,
9
11
  StorageOptions,
10
12
  TerminalRunStatus,
@@ -17,10 +19,21 @@ import type {
17
19
  export class SqliteRuntimeStorage implements RuntimeStorage {
18
20
  private driver: SqliteDriver;
19
21
  private packageId: string;
22
+ private clock: Clock;
20
23
  private isClosed = false;
24
+ private statementCache = new Map<string, SqliteStatement>();
25
+
26
+ get isOpen(): boolean {
27
+ return !this.isClosed;
28
+ }
29
+
30
+ get closed(): boolean {
31
+ return this.isClosed;
32
+ }
21
33
 
22
34
  constructor(options: StorageOptions) {
23
35
  this.packageId = options.packageId;
36
+ this.clock = options.clock ?? getSystemClock();
24
37
  const dbPath = options.dbPath || ":memory:";
25
38
 
26
39
  if (dbPath !== ":memory:") {
@@ -161,10 +174,22 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
161
174
  }
162
175
  }
163
176
 
177
+ /**
178
+ * 获取或复用预编译 SQL 语句缓存。
179
+ */
180
+ private getStatement(sql: string): SqliteStatement {
181
+ let stmt = this.statementCache.get(sql);
182
+ if (!stmt) {
183
+ stmt = this.driver.prepare(sql);
184
+ this.statementCache.set(sql, stmt);
185
+ }
186
+ return stmt;
187
+ }
188
+
164
189
  // --- Config 配置管理 ---
165
190
 
166
191
  getConfig<T = unknown>(key: string): T | undefined {
167
- const stmt = this.driver.prepare(
192
+ const stmt = this.getStatement(
168
193
  "SELECT value_json FROM config WHERE package_id = ? AND key = ?"
169
194
  );
170
195
  const row = stmt.get<{ value_json: string }>(this.packageId, key);
@@ -179,7 +204,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
179
204
  }
180
205
 
181
206
  listConfig(): Record<string, unknown> {
182
- const stmt = this.driver.prepare(
207
+ const stmt = this.getStatement(
183
208
  "SELECT key, value_json FROM config WHERE package_id = ?"
184
209
  );
185
210
  const rows = stmt.all<{ key: string; value_json: string }>(this.packageId);
@@ -195,7 +220,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
195
220
  }
196
221
 
197
222
  setConfig(key: string, value: unknown): void {
198
- const stmt = this.driver.prepare(`
223
+ const stmt = this.getStatement(`
199
224
  INSERT INTO config (package_id, key, value_json, updated_at)
200
225
  VALUES (?, ?, ?, ?)
201
226
  ON CONFLICT(package_id, key) DO UPDATE SET
@@ -203,12 +228,12 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
203
228
  updated_at = excluded.updated_at
204
229
  `);
205
230
  const valJson = JSON.stringify(value);
206
- const now = new Date().toISOString();
231
+ const now = this.clock.now().toISOString();
207
232
  stmt.run(this.packageId, key, valJson, now);
208
233
  }
209
234
 
210
235
  deleteConfig(key: string): boolean {
211
- const stmt = this.driver.prepare(
236
+ const stmt = this.getStatement(
212
237
  "DELETE FROM config WHERE package_id = ? AND key = ?"
213
238
  );
214
239
  const res = stmt.run(this.packageId, key);
@@ -218,7 +243,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
218
243
  // --- State 状态管理 ---
219
244
 
220
245
  async getState<T = unknown>(namespace: string, key: string): Promise<T | undefined> {
221
- const stmt = this.driver.prepare(
246
+ const stmt = this.getStatement(
222
247
  "SELECT value_json, expires_at FROM state WHERE package_id = ? AND namespace = ? AND key = ?"
223
248
  );
224
249
  const row = stmt.get<{ value_json: string; expires_at?: string }>(
@@ -232,7 +257,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
232
257
 
233
258
  if (row.expires_at) {
234
259
  const expires = new Date(row.expires_at).getTime();
235
- if (Date.now() >= expires) {
260
+ if (this.clock.now().getTime() >= expires) {
236
261
  this.deleteState(namespace, key).catch(() => {});
237
262
  return undefined;
238
263
  }
@@ -262,7 +287,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
262
287
  const val = await this.getState<T>(ns, actualKey);
263
288
  if (val === undefined) return undefined;
264
289
 
265
- const stmt = this.driver.prepare(
290
+ const stmt = this.getStatement(
266
291
  "SELECT updated_at, expires_at FROM state WHERE package_id = ? AND namespace = ? AND key = ?"
267
292
  );
268
293
  const row = stmt.get<{ updated_at: string; expires_at?: string }>(
@@ -277,14 +302,14 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
277
302
  key: actualKey,
278
303
  fullKey: targetKey,
279
304
  value: val,
280
- updatedAt: row?.updated_at || new Date().toISOString(),
305
+ updatedAt: row?.updated_at || this.clock.now().toISOString(),
281
306
  expiresAt: row?.expires_at,
282
307
  };
283
308
  }
284
309
 
285
310
  const val = await this.getState<T>("", actualKey);
286
311
  if (val !== undefined) {
287
- const stmt = this.driver.prepare(
312
+ const stmt = this.getStatement(
288
313
  "SELECT updated_at, expires_at FROM state WHERE package_id = ? AND namespace = ? AND key = ?"
289
314
  );
290
315
  const row = stmt.get<{ updated_at: string; expires_at?: string }>(
@@ -298,12 +323,12 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
298
323
  key: actualKey,
299
324
  fullKey: actualKey,
300
325
  value: val,
301
- updatedAt: row?.updated_at || new Date().toISOString(),
326
+ updatedAt: row?.updated_at || this.clock.now().toISOString(),
302
327
  expiresAt: row?.expires_at,
303
328
  };
304
329
  }
305
330
 
306
- const stmt = this.driver.prepare(
331
+ const stmt = this.getStatement(
307
332
  "SELECT namespace, key, value_json, updated_at, expires_at FROM state WHERE package_id = ? AND key = ?"
308
333
  );
309
334
  const rows = stmt.all<{
@@ -314,7 +339,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
314
339
  expires_at?: string;
315
340
  }>(this.packageId, actualKey);
316
341
 
317
- const now = Date.now();
342
+ const now = this.clock.now().getTime();
318
343
  for (const row of rows) {
319
344
  if (row.expires_at && now >= new Date(row.expires_at).getTime()) {
320
345
  continue;
@@ -345,7 +370,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
345
370
  value: T,
346
371
  ttl?: number
347
372
  ): Promise<void> {
348
- const stmt = this.driver.prepare(`
373
+ const stmt = this.getStatement(`
349
374
  INSERT INTO state (package_id, namespace, key, value_json, updated_at, expires_at)
350
375
  VALUES (?, ?, ?, ?, ?, ?)
351
376
  ON CONFLICT(package_id, namespace, key) DO UPDATE SET
@@ -354,7 +379,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
354
379
  expires_at = excluded.expires_at
355
380
  `);
356
381
  const valJson = JSON.stringify(value);
357
- const now = new Date();
382
+ const now = this.clock.now();
358
383
  const updatedAt = now.toISOString();
359
384
 
360
385
  let expiresAt: string | null = null;
@@ -366,7 +391,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
366
391
  }
367
392
 
368
393
  async deleteState(namespace: string, key: string): Promise<boolean> {
369
- const stmt = this.driver.prepare(
394
+ const stmt = this.getStatement(
370
395
  "DELETE FROM state WHERE package_id = ? AND namespace = ? AND key = ?"
371
396
  );
372
397
  const res = stmt.run(this.packageId, namespace, key);
@@ -390,7 +415,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
390
415
  const deletedRoot = await this.deleteState("", actualKey);
391
416
  if (deletedRoot) return true;
392
417
 
393
- const stmt = this.driver.prepare(
418
+ const stmt = this.getStatement(
394
419
  "DELETE FROM state WHERE package_id = ? AND key = ?"
395
420
  );
396
421
  const res = stmt.run(this.packageId, actualKey);
@@ -414,7 +439,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
414
439
  params.push(`${escapedPrefix}%`);
415
440
  }
416
441
 
417
- const stmt = this.driver.prepare(sql);
442
+ const stmt = this.getStatement(sql);
418
443
  const res = stmt.run(...params);
419
444
  return res.changes;
420
445
  }
@@ -437,14 +462,14 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
437
462
  params.push(`${escapedPrefix}%`);
438
463
  }
439
464
 
440
- const stmt = this.driver.prepare(sql);
465
+ const stmt = this.getStatement(sql);
441
466
  const rows = stmt.all<{
442
467
  namespace: string;
443
468
  key: string;
444
469
  expires_at?: string;
445
470
  }>(...params);
446
471
 
447
- const now = Date.now();
472
+ const now = this.clock.now().getTime();
448
473
  const result: string[] = [];
449
474
 
450
475
  for (const row of rows) {
@@ -478,7 +503,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
478
503
  params.push(`${escapedPrefix}%`);
479
504
  }
480
505
 
481
- const stmt = this.driver.prepare(sql);
506
+ const stmt = this.getStatement(sql);
482
507
  const rows = stmt.all<{
483
508
  namespace: string;
484
509
  key: string;
@@ -487,7 +512,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
487
512
  expires_at?: string;
488
513
  }>(...params);
489
514
 
490
- const now = Date.now();
515
+ const now = this.clock.now().getTime();
491
516
  const results: StateEntry[] = [];
492
517
 
493
518
  for (const row of rows) {
@@ -519,7 +544,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
519
544
  // --- Runs 运行记录管理 ---
520
545
 
521
546
  createRun(record: RunRecord | any): void {
522
- const stmt = this.driver.prepare(`
547
+ const stmt = this.getStatement(`
523
548
  INSERT INTO runs (
524
549
  id, root_run_id, parent_run_id, package_id, package_instance_id,
525
550
  action_id, generation_id, owner_id, status, input_json, output_json,
@@ -567,7 +592,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
567
592
  ): void {
568
593
  if (this.isClosed) return;
569
594
  try {
570
- const stmt = this.driver.prepare(`
595
+ const stmt = this.getStatement(`
571
596
  UPDATE runs
572
597
  SET status = ?, output_json = ?, error_json = ?, finished_at = ?
573
598
  WHERE id = ?
@@ -576,16 +601,18 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
576
601
  status,
577
602
  output !== undefined ? JSON.stringify(output) : null,
578
603
  error ? JSON.stringify(error) : null,
579
- finishedAt || new Date().toISOString(),
604
+ finishedAt || this.clock.now().toISOString(),
580
605
  id
581
606
  );
582
- } catch {
583
- // 数据库已关闭或操作异常,安全忽略
607
+ } catch (err) {
608
+ if (this.isClosed) return;
609
+ console.warn(`[SqliteRuntimeStorage] Failed to update run "${id}":`, err);
610
+ throw err;
584
611
  }
585
612
  }
586
613
 
587
614
  getRun(id: string): RunRecord | null {
588
- const stmt = this.driver.prepare(
615
+ const stmt = this.getStatement(
589
616
  "SELECT * FROM runs WHERE id = ? AND package_id = ?"
590
617
  );
591
618
  const row = stmt.get<any>(id, this.packageId);
@@ -597,7 +624,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
597
624
  const limit = options.limit || 50;
598
625
  let rows: any[];
599
626
  if (options.actionId) {
600
- const stmt = this.driver.prepare(`
627
+ const stmt = this.getStatement(`
601
628
  SELECT * FROM runs
602
629
  WHERE package_id = ? AND action_id = ?
603
630
  ORDER BY started_at DESC
@@ -605,7 +632,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
605
632
  `);
606
633
  rows = stmt.all(this.packageId, options.actionId, limit);
607
634
  } else {
608
- const stmt = this.driver.prepare(`
635
+ const stmt = this.getStatement(`
609
636
  SELECT * FROM runs
610
637
  WHERE package_id = ?
611
638
  ORDER BY started_at DESC
@@ -627,7 +654,7 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
627
654
  sql += " AND status = ?";
628
655
  params.push(options.status);
629
656
  }
630
- const stmt = this.driver.prepare(sql);
657
+ const stmt = this.getStatement(sql);
631
658
  const res = stmt.run(...params);
632
659
  return res.changes;
633
660
  }
@@ -675,7 +702,9 @@ export class SqliteRuntimeStorage implements RuntimeStorage {
675
702
  }
676
703
 
677
704
  close(): void {
705
+ if (this.isClosed) return;
678
706
  this.isClosed = true;
707
+ this.statementCache.clear();
679
708
  try {
680
709
  this.driver.close();
681
710
  } catch {
@@ -1,4 +1,5 @@
1
1
  import type { JsonValue, RuntimeError, RunRecord } from "@actiondock/sdk";
2
+ import type { Clock } from "../runtime/clock";
2
3
 
3
4
  /**
4
5
  * SQLite 基础参数值类型。
@@ -73,6 +74,8 @@ export interface StorageOptions {
73
74
  packageId: string;
74
75
  /** 显式注入的 SQLite 底层驱动 */
75
76
  driver?: SqliteDriver;
77
+ /** 可选注入的时间提供器,便于与模拟时钟联动 */
78
+ clock?: Clock;
76
79
  }
77
80
 
78
81
  /**
@@ -89,6 +92,11 @@ export type TerminalRunStatus =
89
92
  * 统一运行时存储抽象接口。
90
93
  */
91
94
  export interface RuntimeStorage {
95
+ /** 数据库是否处于打开状态 */
96
+ readonly isOpen?: boolean;
97
+ /** 数据库是否已关闭 */
98
+ readonly closed?: boolean;
99
+
92
100
  // --- Config 配置管理 ---
93
101
  getConfig<T = unknown>(key: string): T | undefined;
94
102
  listConfig(): Record<string, unknown>;
@@ -1,5 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
 
3
+ export { findExecutable } from "@actiondock/sdk";
4
+
3
5
  /**
4
6
  * Parses duration strings like "500ms", "30s", "5m", "1h", "1d" or pure numbers into milliseconds.
5
7
  */
@@ -1,218 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { basename, dirname, join, resolve } from "node:path";
5
- import { loadActionFileMap, loadActions, loadProjectConfig } from "../project/loader";
6
- import type { ProjectConfig } from "../project/types";
7
- import { getPackageSlug } from "../utils";
8
- import { type ActionImport, generateStandaloneEntrypoint } from "./templates";
9
-
10
- /**
11
- * 独立二进制可执行文件构建选项。
12
- */
13
- export interface BuildOptions {
14
- /** 目标项目根目录 */
15
- projectRoot: string;
16
- /** 目标架构(如 "bun-linux-x64", "bun-darwin-arm64", "bun-windows-x64" 等) */
17
- target?: string;
18
- /** 输出可执行文件的目标路径(默认输出到 dist/ 目录) */
19
- outfile?: string;
20
- /** 是否开启代码压缩混淆(默认 true) */
21
- minify?: boolean;
22
- /** 是否编译为 V8/JavaScriptCore 字节码(默认 true) */
23
- bytecode?: boolean;
24
- /** 显式挑选打包的 Action ID 清单(用于按需子集打包) */
25
- actions?: string[];
26
- }
27
-
28
- /**
29
- * 独立二进制构建完成后的元数据结果对象。
30
- */
31
- export interface BuildResult {
32
- /** 所属 Package ID */
33
- packageId: string;
34
- /** 打包的项目版本号 */
35
- version: string;
36
- /** 编译的目标平台架构 */
37
- target: string;
38
- /** 生成的独立二进制可执行文件绝对路径 */
39
- executablePath: string;
40
- /** 生成的 sidecar 元数据 JSON 文件绝对路径 */
41
- metadataPath: string;
42
- /** 打包内置的 Action ID 列表 */
43
- actions: string[];
44
- }
45
-
46
- /**
47
- * 调用 Bun 原生编译引擎(Bun.build --compile)将 Action Package 打包为零外部依赖的独立二进制可执行文件。
48
- *
49
- * 构建过程:
50
- * 1. 动态生成 Standalone 入口点代码(包含 StandaloneRuntime 与 Action 注册)。
51
- * 2. 生成 sidecar metadata 文件(.actiondock-meta.json),包含 Package 元数据与 sha256 校验和。
52
- * 3. 执行 Bun.build({ compile: true, target, minify, bytecode })。
53
- *
54
- * @param options 构建参数
55
- * @returns 构建产物结果元数据
56
- */
57
- export async function buildProject(options: BuildOptions): Promise<BuildResult> {
58
- const root = resolve(options.projectRoot);
59
- const config = loadProjectConfig(root);
60
- const actionsMap = await loadActions(root, config.actionsDir);
61
-
62
- if (actionsMap.size === 0) {
63
- throw new Error(`No valid actions found in ${join(root, config.actionsDir || "actions")}`);
64
- }
65
-
66
- if (options.actions && options.actions.length > 0) {
67
- const requestedActions = new Set(options.actions);
68
- for (const reqId of requestedActions) {
69
- if (!actionsMap.has(reqId)) {
70
- throw new Error(`Action '${reqId}' requested in build options not found in project`);
71
- }
72
- }
73
- for (const id of Array.from(actionsMap.keys())) {
74
- if (!requestedActions.has(id)) {
75
- actionsMap.delete(id);
76
- }
77
- }
78
- }
79
-
80
- // Action imports list
81
- const actionFileMap = await loadActionFileMap(root, config.actionsDir);
82
- const actionImports: ActionImport[] = [];
83
-
84
- for (const [id, entry] of actionFileMap.entries()) {
85
- if (actionsMap.has(id)) {
86
- actionImports.push({
87
- id,
88
- filePath: entry.filePath,
89
- });
90
- }
91
- }
92
-
93
- if (actionImports.length === 0) {
94
- throw new Error("Could not map any action files for build");
95
- }
96
-
97
- // Create build dir
98
- const buildDir = join(root, ".actiondock", ".build");
99
- mkdirSync(buildDir, { recursive: true });
100
-
101
- const entryCode = generateStandaloneEntrypoint(
102
- config.id,
103
- config.version,
104
- config.description,
105
- actionImports,
106
- config.config
107
- );
108
- const entryPath = join(buildDir, "entry.ts");
109
- writeFileSync(entryPath, entryCode, "utf-8");
110
-
111
- // Determine target and outfile
112
- const target = options.target || "bun";
113
- const binaryName = getPackageSlug(config.id);
114
-
115
- const defaultOutfile = join(root, "dist", binaryName);
116
- const outfile = resolve(options.outfile || defaultOutfile);
117
-
118
- mkdirSync(dirname(outfile), { recursive: true });
119
-
120
- // Run bun build --compile --bytecode --minify
121
- const buildArgs = [
122
- "bun",
123
- "build",
124
- entryPath,
125
- "--compile",
126
- "--outfile",
127
- outfile,
128
- ];
129
-
130
- if (options.bytecode !== false) {
131
- buildArgs.push("--bytecode");
132
- }
133
-
134
- if (options.minify !== false) {
135
- buildArgs.push("--minify");
136
- }
137
-
138
- if (options.target && options.target !== "bun" && options.target !== "host") {
139
- // e.g. bun-linux-x64 or linux-x64
140
- const formattedTarget = options.target.startsWith("bun-")
141
- ? options.target
142
- : `bun-${options.target}`;
143
- buildArgs.push(`--target=${formattedTarget}`);
144
- }
145
-
146
- const proc = spawnSync(buildArgs[0], buildArgs.slice(1), {
147
- cwd: root,
148
- stdio: "pipe",
149
- });
150
-
151
- if (proc.error) {
152
- throw new Error(`Bun compile failed to spawn: ${proc.error.message}`);
153
- }
154
-
155
- if (proc.status !== 0) {
156
- const errText = proc.stderr?.toString() || proc.stdout?.toString() || "Unknown error";
157
- throw new Error(`Bun compile failed (exit code ${proc.status}):\n${errText}`);
158
- }
159
-
160
- // Compile artifact resolution (on Windows bun compile automatically appends .exe)
161
- let artifactPath = outfile;
162
- if (!existsSync(artifactPath)) {
163
- const withExe = outfile + ".exe";
164
- if (existsSync(withExe)) {
165
- artifactPath = withExe;
166
- }
167
- }
168
-
169
- // Calculate build hash
170
- const binaryBuffer = readFileSync(artifactPath);
171
- const buildHash = createHash("sha256").update(binaryBuffer).digest("hex").slice(0, 16);
172
-
173
- // Calculate lockHash
174
- let lockHash = "none";
175
- const lockFiles = ["bun.lock", "bun.lockb", "package.json"];
176
- for (const lf of lockFiles) {
177
- const p = join(root, lf);
178
- if (existsSync(p)) {
179
- lockHash = createHash("sha256").update(readFileSync(p)).digest("hex").slice(0, 16);
180
- break;
181
- }
182
- }
183
-
184
- const detectedBunVersion = (typeof (globalThis as any).Bun !== "undefined" && (globalThis as any).Bun.version) || (() => {
185
- try {
186
- const vProc = spawnSync("bun", ["--version"], { stdio: "pipe" });
187
- return vProc.stdout ? vProc.stdout.toString().trim() : "unknown";
188
- } catch {
189
- return "unknown";
190
- }
191
- })();
192
-
193
- // Generate artifact.json metadata
194
- const metadata = {
195
- packageId: config.id,
196
- name: config.name,
197
- version: config.version,
198
- description: config.description,
199
- target: options.target || "host",
200
- actions: actionImports.map((a) => a.id),
201
- bunVersion: detectedBunVersion,
202
- lockHash,
203
- buildHash,
204
- createdAt: new Date().toISOString(),
205
- };
206
-
207
- const metadataPath = join(dirname(artifactPath), "artifact.json");
208
- writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + "\n", "utf-8");
209
-
210
- return {
211
- packageId: config.id,
212
- version: config.version,
213
- target: options.target || "host",
214
- executablePath: artifactPath,
215
- metadataPath,
216
- actions: metadata.actions,
217
- };
218
- }