@gravito/launchpad 1.2.2 → 1.3.2

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/index.js CHANGED
@@ -20,13 +20,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ DEFAULT_POOL_CONFIG: () => DEFAULT_POOL_CONFIG,
24
+ DEFAULT_REFURBISH_CONFIG: () => DEFAULT_REFURBISH_CONFIG,
23
25
  LaunchpadOrbit: () => LaunchpadOrbit,
24
26
  Mission: () => Mission,
27
+ MissionAssigned: () => MissionAssigned,
25
28
  MissionControl: () => MissionControl,
29
+ MissionQueue: () => MissionQueue,
30
+ MissionQueueTimeout: () => MissionQueueTimeout,
31
+ MissionQueued: () => MissionQueued,
26
32
  PayloadInjector: () => PayloadInjector,
33
+ PoolExhausted: () => PoolExhausted,
34
+ PoolExhaustedException: () => PoolExhaustedException,
27
35
  PoolManager: () => PoolManager,
36
+ QueueTimeoutException: () => QueueTimeoutException,
28
37
  RefurbishUnit: () => RefurbishUnit,
38
+ RefurbishmentCompleted: () => RefurbishmentCompleted,
29
39
  Rocket: () => Rocket,
40
+ RocketIgnited: () => RocketIgnited,
41
+ RocketSplashedDown: () => RocketSplashedDown,
30
42
  RocketStatus: () => RocketStatus,
31
43
  bootstrapLaunchpad: () => bootstrapLaunchpad
32
44
  });
@@ -143,6 +155,36 @@ registry = "${fallbackRegistry}"
143
155
  }
144
156
  };
145
157
 
158
+ // src/Domain/Interfaces.ts
159
+ var DEFAULT_POOL_CONFIG = {
160
+ maxRockets: 20,
161
+ warmupCount: 3,
162
+ maxQueueSize: 50,
163
+ queueTimeoutMs: 3e4,
164
+ exhaustionStrategy: "queue",
165
+ dynamicLimit: 5
166
+ };
167
+ var DEFAULT_REFURBISH_CONFIG = {
168
+ strategy: "deep-clean",
169
+ cleanupCommands: [
170
+ // 1. 殺掉所有使用者進程
171
+ "pkill -9 -u bun || true",
172
+ 'pkill -9 -u root -f "bun|node" || true',
173
+ // 2. 清理應用目錄
174
+ "rm -rf /app/* /app/.* 2>/dev/null || true",
175
+ // 3. 清理暫存檔案
176
+ "rm -rf /tmp/* /var/tmp/* 2>/dev/null || true",
177
+ // 4. 清理 bun 暫存
178
+ "rm -rf /root/.bun/install/cache/tmp/* 2>/dev/null || true",
179
+ "rm -rf /home/bun/.bun/install/cache/tmp/* 2>/dev/null || true",
180
+ // 5. 清理日誌
181
+ "rm -rf /var/log/*.log 2>/dev/null || true"
182
+ ],
183
+ cleanupTimeoutMs: 3e4,
184
+ failureAction: "decommission",
185
+ maxRetries: 2
186
+ };
187
+
146
188
  // src/Domain/Rocket.ts
147
189
  var import_enterprise2 = require("@gravito/enterprise");
148
190
 
@@ -173,6 +215,28 @@ var RefurbishmentCompleted = class extends import_enterprise.DomainEvent {
173
215
  this.rocketId = rocketId;
174
216
  }
175
217
  };
218
+ var PoolExhausted = class extends import_enterprise.DomainEvent {
219
+ constructor(totalRockets, maxRockets, queueLength) {
220
+ super();
221
+ this.totalRockets = totalRockets;
222
+ this.maxRockets = maxRockets;
223
+ this.queueLength = queueLength;
224
+ }
225
+ };
226
+ var MissionQueued = class extends import_enterprise.DomainEvent {
227
+ constructor(missionId, queuePosition) {
228
+ super();
229
+ this.missionId = missionId;
230
+ this.queuePosition = queuePosition;
231
+ }
232
+ };
233
+ var MissionQueueTimeout = class extends import_enterprise.DomainEvent {
234
+ constructor(missionId, waitTimeMs) {
235
+ super();
236
+ this.missionId = missionId;
237
+ this.waitTimeMs = waitTimeMs;
238
+ }
239
+ };
176
240
 
177
241
  // src/Domain/RocketStatus.ts
178
242
  var RocketStatus = /* @__PURE__ */ ((RocketStatus2) => {
@@ -260,6 +324,23 @@ var Rocket = class _Rocket extends import_enterprise2.AggregateRoot {
260
324
  decommission() {
261
325
  this._status = "DECOMMISSIONED" /* DECOMMISSIONED */;
262
326
  }
327
+ /**
328
+ * 替換底層容器(僅在 REFURBISHING 狀態時允許)
329
+ * 用於 destroy-recreate 策略
330
+ *
331
+ * @param newContainerId - 新容器 ID
332
+ * @throws {Error} 當火箭不在 REFURBISHING 狀態時
333
+ *
334
+ * @public
335
+ * @since 1.3.0
336
+ */
337
+ replaceContainer(newContainerId) {
338
+ if (this._status !== "REFURBISHING" /* REFURBISHING */) {
339
+ throw new Error(`\u7121\u6CD5\u66FF\u63DB\u5BB9\u5668\uFF1A\u706B\u7BAD ${this.id} \u4E0D\u5728 REFURBISHING \u72C0\u614B`);
340
+ }
341
+ this._containerId = newContainerId;
342
+ this._assignedDomain = null;
343
+ }
263
344
  toJSON() {
264
345
  return {
265
346
  id: this.id,
@@ -278,44 +359,218 @@ var Rocket = class _Rocket extends import_enterprise2.AggregateRoot {
278
359
  }
279
360
  };
280
361
 
362
+ // src/Application/MissionQueue.ts
363
+ var MissionQueue = class {
364
+ queue = [];
365
+ maxSize;
366
+ timeoutMs;
367
+ constructor(maxSize = 50, timeoutMs = 3e4) {
368
+ this.maxSize = maxSize;
369
+ this.timeoutMs = timeoutMs;
370
+ }
371
+ get length() {
372
+ return this.queue.length;
373
+ }
374
+ get isFull() {
375
+ return this.queue.length >= this.maxSize;
376
+ }
377
+ /**
378
+ * 將任務加入隊列,返回 Promise 等待分配
379
+ */
380
+ enqueue(mission) {
381
+ if (this.isFull) {
382
+ return Promise.reject(
383
+ new PoolExhaustedException(`\u4EFB\u52D9\u968A\u5217\u5DF2\u6EFF\uFF08${this.maxSize}\uFF09\uFF0C\u7121\u6CD5\u63A5\u53D7\u65B0\u4EFB\u52D9`)
384
+ );
385
+ }
386
+ return new Promise((resolve, reject) => {
387
+ const timeoutId = setTimeout(() => {
388
+ this.removeFromQueue(mission.id);
389
+ reject(new QueueTimeoutException(`\u4EFB\u52D9 ${mission.id} \u7B49\u5F85\u8D85\u6642`));
390
+ }, this.timeoutMs);
391
+ this.queue.push({
392
+ mission,
393
+ resolve,
394
+ reject,
395
+ enqueuedAt: Date.now(),
396
+ timeoutId
397
+ });
398
+ console.log(
399
+ `[MissionQueue] \u4EFB\u52D9 ${mission.id} \u5DF2\u52A0\u5165\u968A\u5217\uFF0C\u7576\u524D\u968A\u5217\u9577\u5EA6: ${this.queue.length}`
400
+ );
401
+ });
402
+ }
403
+ /**
404
+ * 取出下一個等待的任務
405
+ */
406
+ dequeue() {
407
+ const item = this.queue.shift();
408
+ if (item) {
409
+ clearTimeout(item.timeoutId);
410
+ }
411
+ return item ?? null;
412
+ }
413
+ /**
414
+ * 從隊列中移除指定任務
415
+ */
416
+ removeFromQueue(missionId) {
417
+ const index = this.queue.findIndex((q) => q.mission.id === missionId);
418
+ if (index >= 0) {
419
+ const [removed] = this.queue.splice(index, 1);
420
+ clearTimeout(removed.timeoutId);
421
+ }
422
+ }
423
+ /**
424
+ * 清空隊列並拒絕所有等待的任務
425
+ */
426
+ clear(reason) {
427
+ for (const item of this.queue) {
428
+ clearTimeout(item.timeoutId);
429
+ item.reject(new Error(reason));
430
+ }
431
+ this.queue = [];
432
+ }
433
+ /**
434
+ * 取得隊列統計資訊
435
+ */
436
+ getStats() {
437
+ const oldestWaitMs = this.queue.length > 0 ? Date.now() - this.queue[0].enqueuedAt : null;
438
+ return {
439
+ length: this.queue.length,
440
+ maxSize: this.maxSize,
441
+ oldestWaitMs
442
+ };
443
+ }
444
+ };
445
+ var PoolExhaustedException = class extends Error {
446
+ constructor(message) {
447
+ super(message);
448
+ this.name = "PoolExhaustedException";
449
+ }
450
+ };
451
+ var QueueTimeoutException = class extends Error {
452
+ constructor(message) {
453
+ super(message);
454
+ this.name = "QueueTimeoutException";
455
+ }
456
+ };
457
+
281
458
  // src/Application/PoolManager.ts
282
459
  var PoolManager = class {
283
- constructor(dockerAdapter, rocketRepository, refurbishUnit, router) {
460
+ constructor(dockerAdapter, rocketRepository, refurbishUnit, router, config) {
284
461
  this.dockerAdapter = dockerAdapter;
285
462
  this.rocketRepository = rocketRepository;
286
463
  this.refurbishUnit = refurbishUnit;
287
464
  this.router = router;
465
+ this.config = { ...DEFAULT_POOL_CONFIG, ...config };
466
+ this.missionQueue = new MissionQueue(this.config.maxQueueSize, this.config.queueTimeoutMs);
467
+ }
468
+ config;
469
+ missionQueue;
470
+ dynamicRocketCount = 0;
471
+ /**
472
+ * 取得當前 Pool 狀態
473
+ */
474
+ async getPoolStatus() {
475
+ const rockets = await this.rocketRepository.findAll();
476
+ const statusCounts = {
477
+ total: rockets.length,
478
+ idle: 0,
479
+ orbiting: 0,
480
+ refurbishing: 0,
481
+ decommissioned: 0
482
+ };
483
+ for (const rocket of rockets) {
484
+ switch (rocket.status) {
485
+ case "IDLE" /* IDLE */:
486
+ statusCounts.idle++;
487
+ break;
488
+ case "PREPARING" /* PREPARING */:
489
+ case "ORBITING" /* ORBITING */:
490
+ statusCounts.orbiting++;
491
+ break;
492
+ case "REFURBISHING" /* REFURBISHING */:
493
+ statusCounts.refurbishing++;
494
+ break;
495
+ case "DECOMMISSIONED" /* DECOMMISSIONED */:
496
+ statusCounts.decommissioned++;
497
+ break;
498
+ }
499
+ }
500
+ return {
501
+ ...statusCounts,
502
+ dynamicCount: this.dynamicRocketCount,
503
+ maxRockets: this.config.maxRockets,
504
+ queueLength: this.missionQueue.length
505
+ };
288
506
  }
289
507
  /**
290
508
  * 初始化發射場:預先準備指定數量的火箭
291
509
  */
292
510
  async warmup(count) {
511
+ const targetCount = count ?? this.config.warmupCount;
293
512
  const currentRockets = await this.rocketRepository.findAll();
294
- const needed = count - currentRockets.length;
513
+ const activeRockets = currentRockets.filter((r) => r.status !== "DECOMMISSIONED" /* DECOMMISSIONED */);
514
+ const needed = Math.min(
515
+ targetCount - activeRockets.length,
516
+ this.config.maxRockets - activeRockets.length
517
+ );
295
518
  if (needed <= 0) {
519
+ console.log(`[PoolManager] \u7121\u9700\u71B1\u6A5F\uFF0C\u7576\u524D\u5DF2\u6709 ${activeRockets.length} \u67B6\u706B\u7BAD`);
296
520
  return;
297
521
  }
298
- console.log(`[LaunchPad] \u6B63\u5728\u71B1\u6A5F\uFF0C\u6E96\u5099\u767C\u5C04 ${needed} \u67B6\u65B0\u706B\u7BAD...`);
522
+ console.log(`[PoolManager] \u6B63\u5728\u71B1\u6A5F\uFF0C\u6E96\u5099\u767C\u5C04 ${needed} \u67B6\u65B0\u706B\u7BAD...`);
299
523
  for (let i = 0; i < needed; i++) {
300
524
  const containerId = await this.dockerAdapter.createBaseContainer();
301
525
  const rocketId = `rocket-${crypto.randomUUID()}`;
302
526
  const rocket = new Rocket(rocketId, containerId);
303
527
  await this.rocketRepository.save(rocket);
304
528
  }
529
+ console.log(`[PoolManager] \u71B1\u6A5F\u5B8C\u6210\uFF0CPool \u7576\u524D\u5171 ${activeRockets.length + needed} \u67B6\u706B\u7BAD`);
305
530
  }
306
531
  /**
307
532
  * 獲取一架可用的火箭並分配任務
533
+ *
534
+ * @throws {PoolExhaustedException} 當 Pool 耗盡且無法處理請求時
308
535
  */
309
536
  async assignMission(mission) {
310
537
  let rocket = await this.rocketRepository.findIdle();
311
- if (!rocket) {
312
- console.log(`[LaunchPad] \u8CC7\u6E90\u5403\u7DCA\uFF0C\u6B63\u5728\u7DCA\u6025\u547C\u53EB\u5F8C\u63F4\u706B\u7BAD...`);
313
- const containerId = await this.dockerAdapter.createBaseContainer();
314
- rocket = new Rocket(`rocket-dynamic-${Date.now()}`, containerId);
538
+ if (rocket) {
539
+ rocket.assignMission(mission);
540
+ await this.rocketRepository.save(rocket);
541
+ return rocket;
542
+ }
543
+ const poolStatus = await this.getPoolStatus();
544
+ const activeCount = poolStatus.total - poolStatus.decommissioned;
545
+ console.log(
546
+ `[PoolManager] \u7121\u53EF\u7528\u706B\u7BAD\uFF0C\u7576\u524D\u72C0\u614B: \u7E3D\u6578=${activeCount}/${this.config.maxRockets}, \u968A\u5217=${this.missionQueue.length}/${this.config.maxQueueSize}`
547
+ );
548
+ switch (this.config.exhaustionStrategy) {
549
+ case "reject":
550
+ throw new PoolExhaustedException(`Rocket Pool \u5DF2\u8017\u76E1\uFF0C\u7121\u6CD5\u8655\u7406\u4EFB\u52D9 ${mission.id}`);
551
+ case "dynamic": {
552
+ if (activeCount < this.config.maxRockets && this.dynamicRocketCount < (this.config.dynamicLimit ?? 5)) {
553
+ console.log(`[PoolManager] \u52D5\u614B\u5EFA\u7ACB\u5F8C\u63F4\u706B\u7BAD...`);
554
+ const containerId = await this.dockerAdapter.createBaseContainer();
555
+ rocket = new Rocket(`rocket-dynamic-${Date.now()}`, containerId);
556
+ this.dynamicRocketCount++;
557
+ rocket.assignMission(mission);
558
+ await this.rocketRepository.save(rocket);
559
+ return rocket;
560
+ }
561
+ if (this.missionQueue.isFull) {
562
+ throw new PoolExhaustedException(`Rocket Pool \u8207\u968A\u5217\u5747\u5DF2\u6EFF\uFF0C\u7121\u6CD5\u8655\u7406\u4EFB\u52D9 ${mission.id}`);
563
+ }
564
+ console.log(`[PoolManager] \u4EFB\u52D9 ${mission.id} \u9032\u5165\u7B49\u5F85\u968A\u5217`);
565
+ return this.missionQueue.enqueue(mission);
566
+ }
567
+ default:
568
+ if (this.missionQueue.isFull) {
569
+ throw new PoolExhaustedException(`Rocket Pool \u8207\u968A\u5217\u5747\u5DF2\u6EFF\uFF0C\u7121\u6CD5\u8655\u7406\u4EFB\u52D9 ${mission.id}`);
570
+ }
571
+ console.log(`[PoolManager] \u4EFB\u52D9 ${mission.id} \u9032\u5165\u7B49\u5F85\u968A\u5217`);
572
+ return this.missionQueue.enqueue(mission);
315
573
  }
316
- rocket.assignMission(mission);
317
- await this.rocketRepository.save(rocket);
318
- return rocket;
319
574
  }
320
575
  /**
321
576
  * 回收指定任務的火箭
@@ -324,7 +579,7 @@ var PoolManager = class {
324
579
  const allRockets = await this.rocketRepository.findAll();
325
580
  const rocket = allRockets.find((r) => r.currentMission?.id === missionId);
326
581
  if (!rocket) {
327
- console.warn(`[LaunchPad] \u627E\u4E0D\u5230\u5C6C\u65BC\u4EFB\u52D9 ${missionId} \u7684\u706B\u7BAD`);
582
+ console.warn(`[PoolManager] \u627E\u4E0D\u5230\u5C6C\u65BC\u4EFB\u52D9 ${missionId} \u7684\u706B\u7BAD`);
328
583
  return;
329
584
  }
330
585
  if (this.router && rocket.assignedDomain) {
@@ -337,30 +592,67 @@ var PoolManager = class {
337
592
  rocket.finishRefurbishment();
338
593
  }
339
594
  await this.rocketRepository.save(rocket);
595
+ await this.processQueue();
596
+ }
597
+ /**
598
+ * 處理等待隊列中的任務
599
+ */
600
+ async processQueue() {
601
+ const queuedItem = this.missionQueue.dequeue();
602
+ if (!queuedItem) {
603
+ return;
604
+ }
605
+ try {
606
+ const rocket = await this.rocketRepository.findIdle();
607
+ if (rocket) {
608
+ rocket.assignMission(queuedItem.mission);
609
+ await this.rocketRepository.save(rocket);
610
+ queuedItem.resolve(rocket);
611
+ console.log(`[PoolManager] \u968A\u5217\u4EFB\u52D9 ${queuedItem.mission.id} \u5DF2\u5206\u914D\u706B\u7BAD ${rocket.id}`);
612
+ } else {
613
+ console.warn(`[PoolManager] \u8655\u7406\u968A\u5217\u6642\u4ECD\u7121\u53EF\u7528\u706B\u7BAD\uFF0C\u4EFB\u52D9\u91CD\u65B0\u5165\u968A`);
614
+ this.missionQueue.enqueue(queuedItem.mission).then(queuedItem.resolve).catch(queuedItem.reject);
615
+ }
616
+ } catch (error) {
617
+ queuedItem.reject(error);
618
+ }
619
+ }
620
+ /**
621
+ * 取得隊列統計資訊
622
+ */
623
+ getQueueStats() {
624
+ return this.missionQueue.getStats();
340
625
  }
341
626
  };
342
627
 
343
628
  // src/Application/RefurbishUnit.ts
344
629
  var RefurbishUnit = class {
345
- constructor(docker) {
630
+ constructor(docker, config) {
346
631
  this.docker = docker;
632
+ this.config = { ...DEFAULT_REFURBISH_CONFIG, ...config };
347
633
  }
634
+ config;
348
635
  /**
349
636
  * 執行火箭翻新邏輯
350
637
  */
351
638
  async refurbish(rocket) {
352
- console.log(`[RefurbishUnit] \u6B63\u5728\u7FFB\u65B0\u706B\u7BAD: ${rocket.id} (\u5BB9\u5668: ${rocket.containerId})`);
639
+ console.log(
640
+ `[RefurbishUnit] \u6B63\u5728\u7FFB\u65B0\u706B\u7BAD: ${rocket.id} (\u7B56\u7565: ${this.config.strategy}, \u5BB9\u5668: ${rocket.containerId})`
641
+ );
353
642
  rocket.splashDown();
354
643
  try {
355
- const cleanupCommands = ["sh", "-c", "rm -rf /app/* && pkill -f bun || true && rm -rf /tmp/*"];
356
- const result = await this.docker.executeCommand(rocket.containerId, cleanupCommands);
644
+ const commands = this.config.cleanupCommands ?? [];
645
+ const fullCommand = commands.join(" && ");
646
+ const result = await this.docker.executeCommand(rocket.containerId, ["sh", "-c", fullCommand]);
357
647
  if (result.exitCode !== 0) {
358
648
  console.error(`[RefurbishUnit] \u6E05\u7406\u5931\u6557: ${result.stderr}`);
359
- rocket.decommission();
360
- return;
649
+ if (this.config.failureAction === "decommission") {
650
+ rocket.decommission();
651
+ return;
652
+ }
361
653
  }
362
654
  rocket.finishRefurbishment();
363
- console.log(`[RefurbishUnit] \u706B\u7BAD ${rocket.id} \u7FFB\u65B0\u5B8C\u6210\uFF0C\u5DF2\u9032\u5165 IDLE \u72C0\u614B\u3002`);
655
+ console.log(`[RefurbishUnit] \u706B\u7BAD ${rocket.id} \u7FFB\u65B0\u5B8C\u6210\uFF0C\u5DF2\u9032\u5165 IDLE \u72C0\u614B`);
364
656
  } catch (error) {
365
657
  console.error(`[RefurbishUnit] \u56DE\u6536\u904E\u7A0B\u767C\u751F\u7570\u5E38:`, error);
366
658
  rocket.decommission();
@@ -393,8 +685,15 @@ var import_core = require("@gravito/core");
393
685
  var DockerAdapter = class {
394
686
  baseImage = "oven/bun:1.0-slim";
395
687
  runtime = (0, import_core.getRuntimeAdapter)();
688
+ // 快取目錄配置
689
+ cacheConfig = {
690
+ hostCachePath: process.env.BUN_CACHE_PATH || `${process.env.HOME}/.bun/install/cache`,
691
+ containerCachePathRoot: "/root/.bun/install/cache",
692
+ containerCachePathBun: "/home/bun/.bun/install/cache"
693
+ };
396
694
  async createBaseContainer() {
397
695
  const rocketId = `rocket-${crypto.randomUUID()}`;
696
+ await this.ensureCacheDirectory();
398
697
  const proc = this.runtime.spawn([
399
698
  "docker",
400
699
  "run",
@@ -406,10 +705,39 @@ var DockerAdapter = class {
406
705
  "-p",
407
706
  "3000",
408
707
  // 讓 Docker 分配隨機宿主機埠
708
+ // === 快取掛載(關鍵) ===
409
709
  "-v",
410
- `${process.env.HOME}/.bun/install/cache:/root/.bun/install/cache`,
710
+ `${this.cacheConfig.hostCachePath}:${this.cacheConfig.containerCachePathRoot}:rw`,
411
711
  "-v",
412
- `${process.env.HOME}/.bun/install/cache:/home/bun/.bun/install/cache`,
712
+ `${this.cacheConfig.hostCachePath}:${this.cacheConfig.containerCachePathBun}:rw`,
713
+ // === 環境變數配置(確保 bun 使用快取) ===
714
+ "-e",
715
+ `BUN_INSTALL_CACHE_DIR=${this.cacheConfig.containerCachePathBun}`,
716
+ "-e",
717
+ "BUN_INSTALL_CACHE=shared",
718
+ "-e",
719
+ "BUN_INSTALL_PREFER_OFFLINE=true",
720
+ // === 效能相關環境變數 ===
721
+ "-e",
722
+ "NODE_ENV=development",
723
+ // === 資源限制(防止單一容器占用過多資源) ===
724
+ "--memory",
725
+ "1g",
726
+ "--memory-swap",
727
+ "1g",
728
+ "--cpus",
729
+ "1.0",
730
+ // === 安全設定 ===
731
+ "--security-opt",
732
+ "no-new-privileges:true",
733
+ "--cap-drop",
734
+ "ALL",
735
+ "--cap-add",
736
+ "CHOWN",
737
+ "--cap-add",
738
+ "SETUID",
739
+ "--cap-add",
740
+ "SETGID",
413
741
  this.baseImage,
414
742
  "tail",
415
743
  "-f",
@@ -419,6 +747,7 @@ var DockerAdapter = class {
419
747
  const containerId = stdout.trim();
420
748
  const exitCode = await proc.exited;
421
749
  if (containerId.length === 64 && /^[0-9a-f]+$/.test(containerId)) {
750
+ await this.verifyCacheMount(containerId);
422
751
  return containerId;
423
752
  }
424
753
  if (exitCode !== 0) {
@@ -427,6 +756,37 @@ var DockerAdapter = class {
427
756
  }
428
757
  return containerId;
429
758
  }
759
+ /**
760
+ * 確保快取目錄存在
761
+ *
762
+ * @private
763
+ * @since 1.3.0
764
+ */
765
+ async ensureCacheDirectory() {
766
+ const cachePath = this.cacheConfig.hostCachePath;
767
+ const proc = this.runtime.spawn(["mkdir", "-p", cachePath]);
768
+ await proc.exited;
769
+ const chmodProc = this.runtime.spawn(["chmod", "777", cachePath]);
770
+ await chmodProc.exited;
771
+ }
772
+ /**
773
+ * 驗證快取是否正確掛載
774
+ *
775
+ * @private
776
+ * @since 1.3.0
777
+ */
778
+ async verifyCacheMount(containerId) {
779
+ const result = await this.executeCommand(containerId, [
780
+ "sh",
781
+ "-c",
782
+ `ls -la ${this.cacheConfig.containerCachePathBun} 2>/dev/null || echo 'NOT_MOUNTED'`
783
+ ]);
784
+ if (result.stdout.includes("NOT_MOUNTED")) {
785
+ console.warn(`[DockerAdapter] \u8B66\u544A: \u5BB9\u5668 ${containerId} \u7684\u5FEB\u53D6\u76EE\u9304\u53EF\u80FD\u672A\u6B63\u78BA\u639B\u8F09`);
786
+ } else {
787
+ console.log(`[DockerAdapter] \u5FEB\u53D6\u76EE\u9304\u5DF2\u78BA\u8A8D\u639B\u8F09: ${containerId}`);
788
+ }
789
+ }
430
790
  async getExposedPort(containerId, containerPort = 3e3) {
431
791
  const proc = this.runtime.spawn(["docker", "port", containerId, containerPort.toString()]);
432
792
  const stdout = await new Response(proc.stdout ?? null).text();
@@ -807,13 +1167,25 @@ async function bootstrapLaunchpad() {
807
1167
  }
808
1168
  // Annotate the CommonJS export names for ESM import in node:
809
1169
  0 && (module.exports = {
1170
+ DEFAULT_POOL_CONFIG,
1171
+ DEFAULT_REFURBISH_CONFIG,
810
1172
  LaunchpadOrbit,
811
1173
  Mission,
1174
+ MissionAssigned,
812
1175
  MissionControl,
1176
+ MissionQueue,
1177
+ MissionQueueTimeout,
1178
+ MissionQueued,
813
1179
  PayloadInjector,
1180
+ PoolExhausted,
1181
+ PoolExhaustedException,
814
1182
  PoolManager,
1183
+ QueueTimeoutException,
815
1184
  RefurbishUnit,
1185
+ RefurbishmentCompleted,
816
1186
  Rocket,
1187
+ RocketIgnited,
1188
+ RocketSplashedDown,
817
1189
  RocketStatus,
818
1190
  bootstrapLaunchpad
819
1191
  });