@uofx/cli 1.0.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.
Files changed (238) hide show
  1. package/LICENSE +40 -0
  2. package/README.md +444 -0
  3. package/THIRD-PARTY-NOTICES.txt +894 -0
  4. package/dist/application/dtos/index.js +24 -0
  5. package/dist/application/dtos/request/delete-instance.request.dto.js +3 -0
  6. package/dist/application/dtos/request/get-config.request.dto.js +3 -0
  7. package/dist/application/dtos/request/get-credentials.request.dto.js +3 -0
  8. package/dist/application/dtos/request/index.js +27 -0
  9. package/dist/application/dtos/request/install-instance.request.dto.js +3 -0
  10. package/dist/application/dtos/request/list-charts.request.dto.js +3 -0
  11. package/dist/application/dtos/request/set-config.request.dto.js +16 -0
  12. package/dist/application/dtos/request/setup-environment.request.dto.js +16 -0
  13. package/dist/application/dtos/request/show-logs.request.dto.js +19 -0
  14. package/dist/application/dtos/request/start-instance.request.dto.js +3 -0
  15. package/dist/application/dtos/request/stop-instance.request.dto.js +3 -0
  16. package/dist/application/dtos/response/credentials.response.dto.js +3 -0
  17. package/dist/application/dtos/response/delete-instance.response.dto.js +3 -0
  18. package/dist/application/dtos/response/index.js +26 -0
  19. package/dist/application/dtos/response/install-instance.response.dto.js +3 -0
  20. package/dist/application/dtos/response/instance-list.response.dto.js +3 -0
  21. package/dist/application/dtos/response/instance-status.response.dto.js +3 -0
  22. package/dist/application/dtos/response/setup-result.response.dto.js +3 -0
  23. package/dist/application/dtos/response/show-logs.response.dto.js +3 -0
  24. package/dist/application/dtos/response/start-instance.response.dto.js +3 -0
  25. package/dist/application/dtos/response/stop-instance.response.dto.js +3 -0
  26. package/dist/application/index.js +25 -0
  27. package/dist/application/interfaces/index.js +24 -0
  28. package/dist/application/interfaces/use-case.interface.js +3 -0
  29. package/dist/application/use-cases/config/get-config.use-case.js +66 -0
  30. package/dist/application/use-cases/config/set-config.use-case.js +49 -0
  31. package/dist/application/use-cases/credentials/get-credentials.use-case.js +57 -0
  32. package/dist/application/use-cases/index.js +28 -0
  33. package/dist/application/use-cases/instance/delete-instance.use-case.js +81 -0
  34. package/dist/application/use-cases/instance/index.js +23 -0
  35. package/dist/application/use-cases/instance/install-instance.use-case.js +424 -0
  36. package/dist/application/use-cases/instance/list-charts.use-case.js +43 -0
  37. package/dist/application/use-cases/instance/list-instances.use-case.js +62 -0
  38. package/dist/application/use-cases/instance/start-instance.use-case.js +154 -0
  39. package/dist/application/use-cases/instance/stop-instance.use-case.js +55 -0
  40. package/dist/application/use-cases/logs/show-logs.use-case.js +66 -0
  41. package/dist/application/use-cases/setup/setup-environment.use-case.js +53 -0
  42. package/dist/cli.js +286 -0
  43. package/dist/constants/config-defaults.js +23 -0
  44. package/dist/constants/defaults.js +89 -0
  45. package/dist/constants/deployment.js +39 -0
  46. package/dist/constants/environments.js +93 -0
  47. package/dist/constants/index.js +53 -0
  48. package/dist/constants/oci-artifacts.js +25 -0
  49. package/dist/constants/paths.js +92 -0
  50. package/dist/constants/timeouts.js +60 -0
  51. package/dist/di/container.js +34 -0
  52. package/dist/di/index.js +22 -0
  53. package/dist/di/modules/application.module.js +54 -0
  54. package/dist/di/modules/infrastructure.module.js +206 -0
  55. package/dist/di/modules/interceptor.module.js +68 -0
  56. package/dist/di/modules/presentation.module.js +31 -0
  57. package/dist/di/tokens.js +149 -0
  58. package/dist/domain/decorators/sensitive.decorator.js +39 -0
  59. package/dist/domain/entities/credentials-resolver.entity.js +127 -0
  60. package/dist/domain/entities/credentials.entity.js +65 -0
  61. package/dist/domain/entities/delete-instance-validation.entity.js +100 -0
  62. package/dist/domain/entities/deployment-parameters.entity.js +120 -0
  63. package/dist/domain/entities/environment-validation.entity.js +125 -0
  64. package/dist/domain/entities/index.js +29 -0
  65. package/dist/domain/entities/instance-lifecycle-state.entity.js +100 -0
  66. package/dist/domain/entities/instance-list-aggregator.entity.js +104 -0
  67. package/dist/domain/entities/instance-metadata.entity.js +86 -0
  68. package/dist/domain/entities/instance-status.entity.js +79 -0
  69. package/dist/domain/entities/instance.entity.js +128 -0
  70. package/dist/domain/entities/log-filter.entity.js +141 -0
  71. package/dist/domain/index.js +29 -0
  72. package/dist/domain/interfaces/safe-loggable.interface.js +3 -0
  73. package/dist/domain/ports/app-config.port.js +3 -0
  74. package/dist/domain/ports/base-image.port.js +3 -0
  75. package/dist/domain/ports/chart-version.port.js +3 -0
  76. package/dist/domain/ports/correlation-id.port.js +3 -0
  77. package/dist/domain/ports/credentials.port.js +3 -0
  78. package/dist/domain/ports/deployment.port.js +3 -0
  79. package/dist/domain/ports/error-handler.port.js +3 -0
  80. package/dist/domain/ports/index.js +46 -0
  81. package/dist/domain/ports/instance-manager.port.js +3 -0
  82. package/dist/domain/ports/instance-metadata.port.js +8 -0
  83. package/dist/domain/ports/instance-storage.port.js +3 -0
  84. package/dist/domain/ports/k8s-deployer.port.js +3 -0
  85. package/dist/domain/ports/logger.port.js +14 -0
  86. package/dist/domain/ports/output.port.js +3 -0
  87. package/dist/domain/ports/runtime-environment.port.js +6 -0
  88. package/dist/domain/ports/user-interaction.port.js +9 -0
  89. package/dist/domain/ports/user-settings.port.js +3 -0
  90. package/dist/domain/types/index.js +22 -0
  91. package/dist/domain/types/logger.types.js +29 -0
  92. package/dist/domain/types/validation.types.js +9 -0
  93. package/dist/domain/value-objects/acr-credentials.value-object.js +92 -0
  94. package/dist/domain/value-objects/chart-version.value-object.js +124 -0
  95. package/dist/domain/value-objects/config-log-level.value-object.js +84 -0
  96. package/dist/domain/value-objects/connection-info.value-object.js +65 -0
  97. package/dist/domain/value-objects/index.js +25 -0
  98. package/dist/domain/value-objects/instance-name.value-object.js +91 -0
  99. package/dist/domain/value-objects/jwt-key.value-object.js +97 -0
  100. package/dist/domain/value-objects/mssql-password.value-object.js +140 -0
  101. package/dist/domain/value-objects/rsa-key-pair.value-object.js +181 -0
  102. package/dist/index.js +6 -0
  103. package/dist/infrastructure/config/app-config.interface.js +3 -0
  104. package/dist/infrastructure/config/app-config.service.js +280 -0
  105. package/dist/infrastructure/config/config-validator.js +31 -0
  106. package/dist/infrastructure/config/crypto.service.js +125 -0
  107. package/dist/infrastructure/deployment/deployment.adapter.js +118 -0
  108. package/dist/infrastructure/deployment/interfaces/acr-credential-manager.interface.js +3 -0
  109. package/dist/infrastructure/deployment/interfaces/app-manager.interface.js +3 -0
  110. package/dist/infrastructure/deployment/interfaces/helm-registry.interface.js +3 -0
  111. package/dist/infrastructure/deployment/interfaces/infra-manager.interface.js +3 -0
  112. package/dist/infrastructure/deployment/interfaces/k8s-job-runner.interface.js +3 -0
  113. package/dist/infrastructure/deployment/interfaces/mssql-database-init.interface.js +3 -0
  114. package/dist/infrastructure/deployment/interfaces/mssql-helm-deployment.interface.js +3 -0
  115. package/dist/infrastructure/deployment/interfaces/mssql-storage.interface.js +3 -0
  116. package/dist/infrastructure/deployment/interfaces/mssql-user-manager.interface.js +3 -0
  117. package/dist/infrastructure/deployment/interfaces/oci-artifact.interface.js +3 -0
  118. package/dist/infrastructure/deployment/interfaces/secret-manager.interface.js +3 -0
  119. package/dist/infrastructure/deployment/interfaces/service-manager.interface.js +3 -0
  120. package/dist/infrastructure/deployment/interfaces/version-compatibility.interface.js +3 -0
  121. package/dist/infrastructure/deployment/services/acr-credential-manager.service.js +144 -0
  122. package/dist/infrastructure/deployment/services/app-manager.service.js +193 -0
  123. package/dist/infrastructure/deployment/services/base-helm-deployment.service.js +163 -0
  124. package/dist/infrastructure/deployment/services/helm-registry.service.js +126 -0
  125. package/dist/infrastructure/deployment/services/infra-manager.service.js +130 -0
  126. package/dist/infrastructure/deployment/services/k8s-job-runner.service.js +194 -0
  127. package/dist/infrastructure/deployment/services/mssql-database-init.service.js +139 -0
  128. package/dist/infrastructure/deployment/services/mssql-helm-deployment.service.js +100 -0
  129. package/dist/infrastructure/deployment/services/mssql-storage.service.js +54 -0
  130. package/dist/infrastructure/deployment/services/mssql-user-manager.service.js +66 -0
  131. package/dist/infrastructure/deployment/services/oci-artifact.service.js +289 -0
  132. package/dist/infrastructure/deployment/services/secret-manager.service.js +179 -0
  133. package/dist/infrastructure/deployment/services/service-manager.service.js +82 -0
  134. package/dist/infrastructure/deployment/services/version-compatibility.service.js +291 -0
  135. package/dist/infrastructure/environment/interfaces/hardware-info.interface.js +3 -0
  136. package/dist/infrastructure/environment/interfaces/network-checker.interface.js +3 -0
  137. package/dist/infrastructure/environment/services/hardware-info.service.js +135 -0
  138. package/dist/infrastructure/environment/services/network-checker.service.js +142 -0
  139. package/dist/infrastructure/environment/windows-environment.adapter.js +162 -0
  140. package/dist/infrastructure/errors/app-error.js +73 -0
  141. package/dist/infrastructure/errors/error-handler.interface.js +3 -0
  142. package/dist/infrastructure/errors/error-handler.js +218 -0
  143. package/dist/infrastructure/errors/exit-codes.js +27 -0
  144. package/dist/infrastructure/errors/index.js +25 -0
  145. package/dist/infrastructure/execution/builders/base-command.builder.js +122 -0
  146. package/dist/infrastructure/execution/builders/host-command.builder.js +58 -0
  147. package/dist/infrastructure/execution/builders/windows-host-command.builder.js +50 -0
  148. package/dist/infrastructure/execution/builders/wsl-command.builder.js +29 -0
  149. package/dist/infrastructure/execution/command-builder.js +252 -0
  150. package/dist/infrastructure/execution/command-executor.service.js +230 -0
  151. package/dist/infrastructure/execution/environments/wsl-execution.environment.js +70 -0
  152. package/dist/infrastructure/execution/execution-environment.factory.js +53 -0
  153. package/dist/infrastructure/execution/index.js +25 -0
  154. package/dist/infrastructure/execution/interfaces/command-builder.interface.js +3 -0
  155. package/dist/infrastructure/execution/interfaces/command-executor.interface.js +3 -0
  156. package/dist/infrastructure/execution/interfaces/execution-environment-factory.interface.js +3 -0
  157. package/dist/infrastructure/execution/interfaces/execution-environment.interface.js +7 -0
  158. package/dist/infrastructure/execution/interfaces/host-command-builder.interface.js +3 -0
  159. package/dist/infrastructure/execution/interfaces/index.js +23 -0
  160. package/dist/infrastructure/execution/interfaces/script-executor.interface.js +3 -0
  161. package/dist/infrastructure/execution/script-executor.service.js +171 -0
  162. package/dist/infrastructure/http/http-client.service.js +176 -0
  163. package/dist/infrastructure/http/index.js +18 -0
  164. package/dist/infrastructure/http/interfaces/http-client.interface.js +3 -0
  165. package/dist/infrastructure/interceptors/index.js +8 -0
  166. package/dist/infrastructure/interceptors/interceptor.factory.js +44 -0
  167. package/dist/infrastructure/interceptors/interceptor.interface.js +3 -0
  168. package/dist/infrastructure/interceptors/logging.interceptor.js +171 -0
  169. package/dist/infrastructure/logger/correlation-id.adapter.js +68 -0
  170. package/dist/infrastructure/logger/index.js +23 -0
  171. package/dist/infrastructure/logger/interfaces/index.js +22 -0
  172. package/dist/infrastructure/logger/interfaces/log-reader.repository.interface.js +7 -0
  173. package/dist/infrastructure/logger/interfaces/log-writer.repository.interface.js +7 -0
  174. package/dist/infrastructure/logger/logger.adapter.js +274 -0
  175. package/dist/infrastructure/logger/services/file-log-reader.repository.js +148 -0
  176. package/dist/infrastructure/logger/services/file-log-writer.repository.js +307 -0
  177. package/dist/infrastructure/logger/services/index.js +22 -0
  178. package/dist/infrastructure/persistence/index.js +25 -0
  179. package/dist/infrastructure/persistence/instance-metadata.adapter.js +100 -0
  180. package/dist/infrastructure/persistence/instance-storage.adapter.js +64 -0
  181. package/dist/infrastructure/persistence/interfaces/config.repository.interface.js +3 -0
  182. package/dist/infrastructure/persistence/interfaces/index.js +22 -0
  183. package/dist/infrastructure/persistence/interfaces/instance.repository.interface.js +3 -0
  184. package/dist/infrastructure/persistence/services/file-system-config.repository.js +168 -0
  185. package/dist/infrastructure/persistence/services/file-system-instance.repository.js +170 -0
  186. package/dist/infrastructure/persistence/services/index.js +22 -0
  187. package/dist/infrastructure/persistence/user-settings.adapter.js +55 -0
  188. package/dist/infrastructure/platform-detector.js +71 -0
  189. package/dist/infrastructure/platforms/windows/interfaces/microk8s.interface.js +3 -0
  190. package/dist/infrastructure/platforms/windows/interfaces/rootfs-manager.interface.js +3 -0
  191. package/dist/infrastructure/platforms/windows/interfaces/windows-features.interface.js +3 -0
  192. package/dist/infrastructure/platforms/windows/interfaces/windows-info.interface.js +3 -0
  193. package/dist/infrastructure/platforms/windows/interfaces/wsl-config.interface.js +3 -0
  194. package/dist/infrastructure/platforms/windows/interfaces/wsl-info.interface.js +3 -0
  195. package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-inspection.interface.js +3 -0
  196. package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-lifecycle.interface.js +3 -0
  197. package/dist/infrastructure/platforms/windows/interfaces/wsl-instance-naming.interface.js +3 -0
  198. package/dist/infrastructure/platforms/windows/interfaces/wsl-manager.interface.js +3 -0
  199. package/dist/infrastructure/platforms/windows/interfaces/wsl-resources.interface.js +8 -0
  200. package/dist/infrastructure/platforms/windows/interfaces/wsl-updater.interface.js +3 -0
  201. package/dist/infrastructure/platforms/windows/interfaces/wslconfig-parser.interface.js +3 -0
  202. package/dist/infrastructure/platforms/windows/parsers/wsl-version.parser.js +133 -0
  203. package/dist/infrastructure/platforms/windows/services/microk8s.service.js +168 -0
  204. package/dist/infrastructure/platforms/windows/services/rootfs-manager.service.js +336 -0
  205. package/dist/infrastructure/platforms/windows/services/windows-features.service.js +191 -0
  206. package/dist/infrastructure/platforms/windows/services/windows-info.service.js +138 -0
  207. package/dist/infrastructure/platforms/windows/services/wsl-config.service.js +171 -0
  208. package/dist/infrastructure/platforms/windows/services/wsl-info.service.js +226 -0
  209. package/dist/infrastructure/platforms/windows/services/wsl-instance-inspection.service.js +325 -0
  210. package/dist/infrastructure/platforms/windows/services/wsl-instance-lifecycle.service.js +442 -0
  211. package/dist/infrastructure/platforms/windows/services/wsl-instance-naming.service.js +93 -0
  212. package/dist/infrastructure/platforms/windows/services/wsl-updater.service.js +273 -0
  213. package/dist/infrastructure/platforms/windows/services/wslconfig-parser.service.js +222 -0
  214. package/dist/infrastructure/platforms/windows/wsl-base-image.adapter.js +41 -0
  215. package/dist/infrastructure/platforms/windows/wsl-instance-manager.adapter.js +150 -0
  216. package/dist/infrastructure/utils/error-formatter.util.js +29 -0
  217. package/dist/infrastructure/utils/file-operations.util.js +201 -0
  218. package/dist/infrastructure/utils/input-validator.util.js +152 -0
  219. package/dist/infrastructure/utils/retry.util.js +98 -0
  220. package/dist/presentation/controllers/config.controller.js +146 -0
  221. package/dist/presentation/controllers/credentials.controller.js +105 -0
  222. package/dist/presentation/controllers/index.js +25 -0
  223. package/dist/presentation/controllers/instance.controller.js +363 -0
  224. package/dist/presentation/controllers/logs.controller.js +103 -0
  225. package/dist/presentation/controllers/setup.controller.js +175 -0
  226. package/dist/presentation/interfaces/cli-options.interface.js +8 -0
  227. package/dist/presentation/prompts/acr-credentials.prompt.js +76 -0
  228. package/dist/presentation/prompts/index.js +21 -0
  229. package/dist/presentation/ui/cli-progress.service.js +193 -0
  230. package/dist/presentation/ui/constants/output-symbols.js +42 -0
  231. package/dist/presentation/ui/index.js +27 -0
  232. package/dist/presentation/ui/interaction.service.js +276 -0
  233. package/dist/presentation/ui/interfaces/cli-progress.interface.js +9 -0
  234. package/dist/presentation/ui/interfaces/output-formatter.interface.js +23 -0
  235. package/dist/presentation/ui/log-level.enum.js +66 -0
  236. package/dist/presentation/ui/output-builder.service.js +378 -0
  237. package/dist/presentation/ui/output-formatter.service.js +393 -0
  238. package/package.json +65 -0
@@ -0,0 +1,424 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.InstallInstanceUseCase = void 0;
16
+ const tsyringe_1 = require("tsyringe");
17
+ const value_objects_1 = require("../../../domain/value-objects");
18
+ const app_error_1 = require("../../../infrastructure/errors/app-error");
19
+ const exit_codes_1 = require("../../../infrastructure/errors/exit-codes");
20
+ const entities_1 = require("../../../domain/entities");
21
+ const error_formatter_util_1 = require("../../../infrastructure/utils/error-formatter.util");
22
+ const tokens_1 = require("../../../di/tokens");
23
+ const runtime_environment_port_1 = require("../../../domain/ports/runtime-environment.port");
24
+ const timeouts_1 = require("../../../constants/timeouts");
25
+ /**
26
+ * 安裝實例 Use Case
27
+ *
28
+ * 協調整個 UOFX 開發環境的安裝流程
29
+ * 特點:完全無 UI 依賴,透過 Progress 介面回調通知進度
30
+ */
31
+ let InstallInstanceUseCase = class InstallInstanceUseCase {
32
+ constructor(instanceManager, instanceStorage, deploymentPort, baseImagePort, appConfigPort, errorHandler, runtimeEnvironment, userInteraction, httpClient) {
33
+ this.instanceManager = instanceManager;
34
+ this.instanceStorage = instanceStorage;
35
+ this.deploymentPort = deploymentPort;
36
+ this.baseImagePort = baseImagePort;
37
+ this.appConfigPort = appConfigPort;
38
+ this.errorHandler = errorHandler;
39
+ this.runtimeEnvironment = runtimeEnvironment;
40
+ this.userInteraction = userInteraction;
41
+ this.httpClient = httpClient;
42
+ }
43
+ /**
44
+ * 執行安裝流程
45
+ * @param request - 安裝請求
46
+ * @param output - 輸出介面(可選)
47
+ * @returns 安裝結果
48
+ */
49
+ async execute(request, output) {
50
+ output?.info('Installing UOFX development environment...').newline().flush();
51
+ // [1/14] 檢查 WSL 是否已安裝
52
+ await this.checkWslInstalled(output);
53
+ output?.newline().flush();
54
+ // [2/14] 檢查記憶體
55
+ await this.checkMemoryAvailability(output);
56
+ output?.newline().flush();
57
+ // [3/14] 解析 Chart 版本
58
+ const instanceName = value_objects_1.InstanceName.create(request.name);
59
+ const chartVersion = await this.resolveChartVersion(request.chartVersion, output);
60
+ output?.newline().flush();
61
+ // [4/14] 檢查是否有其他實例正在運行
62
+ await this.ensureNoRunningInstances(output);
63
+ output?.newline().flush();
64
+ // 檢查實例是否已存在
65
+ const exists = await this.instanceManager.exists(instanceName);
66
+ if (exists) {
67
+ throw new Error(`Instance "${request.name}" already exists`);
68
+ }
69
+ // [5/14] 準備憑證
70
+ const acrCredentials = await this.getAcrCredentials(request.pullSecretPath, output);
71
+ output?.newline().flush();
72
+ // [6/14] 準備部署參數
73
+ const deploymentParams = this.prepareDeploymentParameters(instanceName, acrCredentials, output);
74
+ // [7/14] 下載 Rootfs
75
+ output?.section('[7/14] Checking Ubuntu rootfs').flush();
76
+ output?.item('arrow', 'Checking rootfs image...').flush();
77
+ const rootfsPath = await this.baseImagePort.getOrDownload();
78
+ output?.success(`Rootfs ready: ${rootfsPath}`).newline().flush();
79
+ // [8/14] 建立實例
80
+ output?.section('[8/14] Creating instance').flush();
81
+ const installPath = this.appConfigPort.getInstallPath(instanceName.toString());
82
+ const config = {
83
+ rootfsPath,
84
+ installPath,
85
+ chartVersion: chartVersion.toString()
86
+ };
87
+ await this.instanceManager.create(instanceName, config, output);
88
+ output?.success('Instance created').flush();
89
+ // 註冊中斷清理 handler(傳入 output 用於輸出清理訊息)
90
+ this.errorHandler.registerCleanupHandler(async () => {
91
+ await this.cleanupInstance(instanceName, output);
92
+ }, output);
93
+ output?.newline().flush();
94
+ // 6. 儲存 metadata
95
+ const cliVersion = this.appConfigPort.getCliVersion();
96
+ const metadata = entities_1.InstanceMetadata.create(instanceName, cliVersion, 'Ubuntu 22.04', chartVersion);
97
+ await this.instanceStorage.saveInstance(metadata);
98
+ // [9/14] 取得環境版本並初始化系統
99
+ output?.section('[9/14] System initialization').flush();
100
+ const environmentVersion = await this.deploymentPort.getEnvironmentVersion(chartVersion.toString());
101
+ await this.initializeSystem(instanceName, deploymentParams, environmentVersion, output);
102
+ output?.newline().flush();
103
+ // 8. 部署 K8s 資源 (步驟 4-8)
104
+ await this.deployK8sResources(instanceName, deploymentParams, chartVersion, output);
105
+ // 成功完成 - 清除清理 handler
106
+ this.errorHandler.clearCleanupHandler();
107
+ // 取得連線資訊
108
+ const instanceIp = await this.instanceManager.getInstanceIP(instanceName);
109
+ const connectionInfo = value_objects_1.ConnectionInfo.create(instanceIp);
110
+ return {
111
+ instanceName: request.name,
112
+ fullInstanceName: instanceName.fullName,
113
+ chartVersion: chartVersion.toString(),
114
+ installedAt: new Date().toISOString(),
115
+ connectionInfo: connectionInfo.toDto(),
116
+ };
117
+ }
118
+ /**
119
+ * 解析 Chart 版本
120
+ * @param versionInput - 使用者輸入的版本
121
+ * @param output - 輸出介面
122
+ * @returns Chart 版本物件
123
+ */
124
+ async resolveChartVersion(versionInput, output) {
125
+ output?.section('[3/14] Resolving chart version').flush();
126
+ if (versionInput) {
127
+ // 使用者提供版本,直接驗證
128
+ output?.item('arrow', 'Validating specified version...').flush();
129
+ const version = value_objects_1.ChartVersion.create(versionInput);
130
+ const isValid = await this.deploymentPort.validateChartVersion('uofx', version);
131
+ if (!isValid) {
132
+ throw new Error(`Chart version "${versionInput}" does not exist`);
133
+ }
134
+ output?.success(`Using version: ${version.toString()}`).flush();
135
+ return version;
136
+ }
137
+ else {
138
+ // 未提供版本,取得最新相容版本
139
+ output?.item('arrow', 'Resolving latest compatible version...').flush();
140
+ const latestVersion = await this.deploymentPort.getLatestCompatibleVersion('uofx');
141
+ output?.success(`Using version: ${latestVersion.toString()}`).flush();
142
+ return latestVersion;
143
+ }
144
+ }
145
+ /**
146
+ * 取得 ACR 憑證
147
+ * @param pullSecretPath - Pull secret 檔案路徑(可選)
148
+ * @param output - 輸出介面
149
+ * @returns ACR 憑證
150
+ */
151
+ async getAcrCredentials(pullSecretPath, output) {
152
+ output?.section('[5/14] Checking ACR credentials').flush();
153
+ output?.item('arrow', 'Loading credentials...').flush();
154
+ // 載入可用的憑證來源
155
+ const fileCredentials = pullSecretPath
156
+ ? this.appConfigPort.loadAcrCredentialsFromFile(pullSecretPath)
157
+ : null;
158
+ const configCredentials = this.appConfigPort.getAcrCredentials();
159
+ // 使用 CredentialsResolver 決定優先級
160
+ const resolver = entities_1.CredentialsResolver.create(fileCredentials, configCredentials, pullSecretPath);
161
+ // 解析憑證(會依優先級選擇)
162
+ const credentials = resolver.resolveOrThrow();
163
+ // 顯示憑證來源
164
+ if (resolver.source === 'file') {
165
+ output
166
+ ?.item('bullet', `Reading from: ${pullSecretPath}`)
167
+ .success('Credentials loaded from file')
168
+ .flush();
169
+ }
170
+ else {
171
+ output?.success('Using credentials from config').flush();
172
+ }
173
+ output?.item('bullet', `ACR: ${credentials.name}`).flush();
174
+ return credentials;
175
+ }
176
+ /**
177
+ * 準備部署參數
178
+ * @param instanceName - 實例名稱
179
+ * @param acrCredentials - ACR 憑證
180
+ * @param output - 輸出介面
181
+ * @returns 部署參數
182
+ */
183
+ prepareDeploymentParameters(instanceName, acrCredentials, output) {
184
+ output?.section('[6/14] Preparing deployment parameters').flush();
185
+ output?.item('arrow', 'Generating security credentials...').flush();
186
+ const params = entities_1.DeploymentParameters.createWithGeneratedCredentials(instanceName, acrCredentials);
187
+ output?.success('Deployment parameters ready').newline().flush();
188
+ return params;
189
+ }
190
+ /**
191
+ * 部署 Kubernetes 資源
192
+ * @param instanceName - 實例名稱
193
+ * @param params - 部署參數
194
+ * @param chartVersion - Chart 版本
195
+ * @param output - 輸出介面
196
+ */
197
+ async deployK8sResources(instanceName, params, chartVersion, output) {
198
+ const fullName = instanceName.fullName;
199
+ const version = chartVersion.toString();
200
+ // [10/14] 建立全域設定 Secret
201
+ output?.section('[10/14] Configuring Kubernetes secrets').flush();
202
+ output?.item('arrow', 'Creating global config secret...').flush();
203
+ await this.deploymentPort.createGlobalConfigSecret(fullName, params);
204
+ output?.success('Secrets configured').newline().flush();
205
+ // [11/14] 部署 MSSQL
206
+ output?.section('[11/14] Deploying MSSQL').flush();
207
+ await this.deploymentPort.deployMssql(fullName, params, version, output);
208
+ output?.success('MSSQL deployed').newline().flush();
209
+ // [12/14] 部署基礎設施服務
210
+ output?.section('[12/14] Deploying Infrastructure Services').flush();
211
+ await this.deploymentPort.deployInfra(fullName, params, version, output);
212
+ output?.success('Infrastructure deployed').newline().flush();
213
+ // [13/14] 部署 Service Definition
214
+ output?.section('[13/14] Deploying Service Definition').flush();
215
+ await this.deploymentPort.deployService(fullName, params, version, output);
216
+ output?.success('Service deployed').newline().flush();
217
+ // [14/14] 部署 UofX App
218
+ output?.section('[14/14] Deploying UofX App').flush();
219
+ await this.deploymentPort.deployApp(fullName, params, version, output);
220
+ // 初始化 D-Bus session(防止 WSL 自動停止)
221
+ output?.item('arrow', 'Initializing D-Bus session...').flush();
222
+ await this.instanceManager.initDBusSession(instanceName, output);
223
+ // 等待所有 pods 就緒
224
+ output?.item('arrow', 'Waiting for all pods to be ready...').flush();
225
+ const podsReady = await this.instanceManager.waitForPodsReady(instanceName);
226
+ if (podsReady) {
227
+ output?.success('All pods are ready').flush();
228
+ }
229
+ else {
230
+ output?.warning('Some pods may not be ready yet').flush();
231
+ }
232
+ // 執行後台健康檢查
233
+ await this.checkAdminHealthWithRetry(instanceName, output);
234
+ output?.success('App deployed').newline().flush();
235
+ }
236
+ /**
237
+ * 初始化系統
238
+ * @param instanceName - 實例名稱
239
+ * @param params - 部署參數(包含 ACR 憑證)
240
+ * @param environmentVersion - ubuntu-environment 版本
241
+ * @param output - 輸出介面
242
+ */
243
+ async initializeSystem(instanceName, params, environmentVersion, output) {
244
+ output?.item('arrow', 'Executing environment setup script...').flush();
245
+ output?.item('bullet', `Using ubuntu-environment v${environmentVersion}`).flush();
246
+ try {
247
+ await this.instanceManager.initialize(instanceName, params, environmentVersion, output);
248
+ output?.success('System initialized').flush();
249
+ }
250
+ catch (error) {
251
+ output?.warning('Environment script execution completed with warnings').flush();
252
+ throw error;
253
+ }
254
+ }
255
+ /**
256
+ * 確保沒有其他實例正在運行
257
+ * 安裝新實例前必須停止所有現有實例
258
+ * @param output - 輸出介面
259
+ * @throws AppError 若有運行中的實例
260
+ */
261
+ async ensureNoRunningInstances(output) {
262
+ output?.section('[4/14] Checking for running instances').flush();
263
+ output?.item('arrow', 'Scanning running instances...').flush();
264
+ const runningInstances = await this.instanceManager.listRunningInstances();
265
+ if (runningInstances.length > 0) {
266
+ // 將全名轉換為顯示名稱(移除 uofx- 前綴)
267
+ const displayNames = runningInstances.map(name => name.replace('uofx-', ''));
268
+ const instanceList = displayNames
269
+ .map(name => ` - ${name}`)
270
+ .join('\n');
271
+ const stopCommand = displayNames.length === 1
272
+ ? `uofx env stop --name ${displayNames[0]}`
273
+ : 'uofx env stop --name <instance-name>';
274
+ throw new app_error_1.AppError(`Cannot install: ${runningInstances.length} instance(s) currently running:\n${instanceList}`, {
275
+ exitCode: exit_codes_1.EXIT_CODES.BUSINESS_ERROR,
276
+ solution: `Stop all running instances before installing a new one.\nRun: ${stopCommand}`,
277
+ context: { runningInstances }
278
+ });
279
+ }
280
+ output?.success('No running instances detected').flush();
281
+ }
282
+ /**
283
+ * 清理未完成安裝的實體
284
+ * 用於安裝中斷時的清理
285
+ * @param instanceName - 實例名稱
286
+ * @param output - 輸出介面
287
+ */
288
+ async cleanupInstance(instanceName, output) {
289
+ output?.item('arrow', `Removing incomplete instance: ${instanceName.toString()}`).flush();
290
+ try {
291
+ // 嘗試停止實體(如果正在運行)
292
+ const status = await this.instanceManager.getStatus(instanceName);
293
+ if (status.isRunning) {
294
+ output?.indent().item('arrow', 'Stopping instance...').outdent().flush();
295
+ await this.instanceManager.stop(instanceName, true);
296
+ output?.indent().success('Instance stopped').outdent().flush();
297
+ }
298
+ }
299
+ catch (error) {
300
+ output?.indent().warning(`Failed to stop instance: ${(0, error_formatter_util_1.formatErrorMessage)(error)}`).outdent().flush();
301
+ }
302
+ try {
303
+ // 刪除實體
304
+ output?.indent().item('arrow', 'Deleting instance...').outdent().flush();
305
+ await this.instanceManager.delete(instanceName);
306
+ output?.indent().success('Instance deleted').outdent().flush();
307
+ }
308
+ catch (error) {
309
+ output?.indent().warning(`Failed to delete instance: ${(0, error_formatter_util_1.formatErrorMessage)(error)}`).outdent().flush();
310
+ }
311
+ try {
312
+ // 刪除 metadata
313
+ output?.indent().item('arrow', 'Deleting metadata...').outdent().flush();
314
+ await this.instanceStorage.deleteInstance(instanceName);
315
+ output?.indent().success('Metadata deleted').outdent().flush();
316
+ }
317
+ catch (error) {
318
+ output?.indent().warning(`Failed to delete metadata: ${(0, error_formatter_util_1.formatErrorMessage)(error)}`).outdent().flush();
319
+ }
320
+ }
321
+ /**
322
+ * 檢查 WSL 是否已安裝
323
+ * @param output - 輸出介面
324
+ * @throws AppError 若 WSL 未安裝
325
+ */
326
+ async checkWslInstalled(output) {
327
+ output?.section('[1/14] Checking WSL installation').flush();
328
+ output?.item('arrow', 'Verifying runtime environment...').flush();
329
+ const runtimeInfo = await this.runtimeEnvironment.getRuntimeInfo();
330
+ if (!runtimeInfo.isInstalled) {
331
+ throw new app_error_1.AppError(`${runtimeInfo.type} is not installed on this system`, {
332
+ exitCode: exit_codes_1.EXIT_CODES.ENVIRONMENT_ERROR,
333
+ solution: 'Please install WSL first. Run: uofx env setup',
334
+ });
335
+ }
336
+ output?.success(`${runtimeInfo.type} is installed (${runtimeInfo.version})`).flush();
337
+ }
338
+ /**
339
+ * 檢查記憶體可用性
340
+ * 綜合考量系統剩餘記憶體、WSL 限制、WSL 目前使用量
341
+ * @param output - 輸出介面
342
+ */
343
+ async checkMemoryAvailability(output) {
344
+ output?.section('[2/14] Checking remaining memory').flush();
345
+ const memoryInfo = await this.runtimeEnvironment.getMemoryAvailability();
346
+ // 顯示詳細資訊
347
+ for (const detail of memoryInfo.details) {
348
+ const suffix = detail.suffix ? ` (${detail.suffix})` : '';
349
+ output?.item('bullet', `${detail.label}: ${detail.valueGB.toFixed(2)} GB${suffix}`).flush();
350
+ }
351
+ if (memoryInfo.availableGB < runtime_environment_port_1.UOFX_REQUIRED_MEMORY_GB) {
352
+ output?.newline().flush();
353
+ output?.warning(`Available memory (${memoryInfo.availableGB.toFixed(2)} GB) is below recommended ${runtime_environment_port_1.UOFX_REQUIRED_MEMORY_GB} GB`).flush();
354
+ output?.info('Insufficient memory may cause:').flush();
355
+ output?.item('bullet', 'Kubernetes pods failing to start').flush();
356
+ output?.item('bullet', 'Services becoming unresponsive or crashing').flush();
357
+ output?.newline().flush();
358
+ const shouldContinue = await this.userInteraction.confirm('Do you want to continue installation anyway?', false);
359
+ if (!shouldContinue) {
360
+ throw new app_error_1.AppError('Installation cancelled due to insufficient memory', {
361
+ exitCode: exit_codes_1.EXIT_CODES.BUSINESS_ERROR,
362
+ solution: 'Close other applications or increase memory available for the runtime environment'
363
+ });
364
+ }
365
+ }
366
+ else {
367
+ output?.success('Memory check passed').flush();
368
+ }
369
+ }
370
+ /**
371
+ * 執行後台健康檢查(含重試機制)
372
+ * 超時後顯示警告但不中斷安裝
373
+ * @param instanceName - 實例名稱
374
+ * @param output - 輸出介面
375
+ */
376
+ async checkAdminHealthWithRetry(instanceName, output) {
377
+ output?.item('arrow', 'Checking admin interface health...').flush();
378
+ // 取得實例 IP 與 Admin URL
379
+ const ip = await this.instanceManager.getInstanceIP(instanceName);
380
+ if (!ip) {
381
+ output?.warning('Could not determine instance IP for health check').flush();
382
+ return;
383
+ }
384
+ const connectionInfo = value_objects_1.ConnectionInfo.create(ip);
385
+ if (!connectionInfo.adminUrl) {
386
+ output?.warning('Could not determine admin URL for health check').flush();
387
+ return;
388
+ }
389
+ const adminUrl = connectionInfo.adminUrl;
390
+ const startTime = Date.now();
391
+ const timeout = timeouts_1.TIMEOUTS.ADMIN_HEALTH_CHECK_TIMEOUT;
392
+ const retryInterval = timeouts_1.TIMEOUTS.HEALTH_CHECK_RETRY_INTERVAL;
393
+ while (Date.now() - startTime < timeout) {
394
+ const isHealthy = await this.httpClient.checkHealth(adminUrl);
395
+ if (isHealthy) {
396
+ output?.success(`Admin interface is healthy: ${adminUrl}`).flush();
397
+ return;
398
+ }
399
+ // 等待後重試
400
+ await new Promise(resolve => setTimeout(resolve, retryInterval));
401
+ }
402
+ // 超時 - 顯示警告但不拋出錯誤
403
+ output
404
+ ?.warning(`Admin interface health check timed out after ${timeout / 60000} minutes. ` +
405
+ `The interface may not be fully ready yet.`)
406
+ .flush();
407
+ output?.info(`You can manually check: ${adminUrl}`).flush();
408
+ }
409
+ };
410
+ exports.InstallInstanceUseCase = InstallInstanceUseCase;
411
+ exports.InstallInstanceUseCase = InstallInstanceUseCase = __decorate([
412
+ (0, tsyringe_1.injectable)(),
413
+ __param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.IInstanceManagerPort)),
414
+ __param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.IInstanceStoragePort)),
415
+ __param(2, (0, tsyringe_1.inject)(tokens_1.TOKENS.IDeploymentPort)),
416
+ __param(3, (0, tsyringe_1.inject)(tokens_1.TOKENS.IBaseImagePort)),
417
+ __param(4, (0, tsyringe_1.inject)(tokens_1.TOKENS.IAppConfigPort)),
418
+ __param(5, (0, tsyringe_1.inject)(tokens_1.TOKENS.IErrorHandlerPort)),
419
+ __param(6, (0, tsyringe_1.inject)(tokens_1.TOKENS.IRuntimeEnvironmentPort)),
420
+ __param(7, (0, tsyringe_1.inject)(tokens_1.TOKENS.IUserInteractionPort)),
421
+ __param(8, (0, tsyringe_1.inject)(tokens_1.TOKENS.Internal.IHttpClient)),
422
+ __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object, Object, Object, Object])
423
+ ], InstallInstanceUseCase);
424
+ //# sourceMappingURL=install-instance.use-case.js.map
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ListChartsUseCase = void 0;
16
+ const tsyringe_1 = require("tsyringe");
17
+ const tokens_1 = require("../../../di/tokens");
18
+ /**
19
+ * 列出可用 Chart 版本 Use Case
20
+ *
21
+ * 從 Helm Registry 取得可用的 UOFX Chart 版本列表
22
+ */
23
+ let ListChartsUseCase = class ListChartsUseCase {
24
+ constructor(deploymentPort) {
25
+ this.deploymentPort = deploymentPort;
26
+ }
27
+ /**
28
+ * 執行列出 Chart 版本
29
+ * @param request - 列出請求
30
+ * @returns Chart 版本陣列
31
+ */
32
+ async execute(request) {
33
+ const { chartName = 'uofx' } = request;
34
+ return await this.deploymentPort.listChartVersions(chartName);
35
+ }
36
+ };
37
+ exports.ListChartsUseCase = ListChartsUseCase;
38
+ exports.ListChartsUseCase = ListChartsUseCase = __decorate([
39
+ (0, tsyringe_1.injectable)(),
40
+ __param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.IDeploymentPort)),
41
+ __metadata("design:paramtypes", [Object])
42
+ ], ListChartsUseCase);
43
+ //# sourceMappingURL=list-charts.use-case.js.map
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ListInstancesUseCase = void 0;
16
+ const tsyringe_1 = require("tsyringe");
17
+ const tokens_1 = require("../../../di/tokens");
18
+ const instance_list_aggregator_entity_1 = require("../../../domain/entities/instance-list-aggregator.entity");
19
+ const instance_name_value_object_1 = require("../../../domain/value-objects/instance-name.value-object");
20
+ /**
21
+ * 列出實例 Use Case
22
+ */
23
+ let ListInstancesUseCase = class ListInstancesUseCase {
24
+ constructor(instanceManager, instanceStorage) {
25
+ this.instanceManager = instanceManager;
26
+ this.instanceStorage = instanceStorage;
27
+ }
28
+ async execute() {
29
+ // 取得所有 UOFX 實例
30
+ const instanceInfos = await this.instanceManager.listUofx();
31
+ // 取得所有 metadata
32
+ const metadataList = await this.instanceStorage.listInstances();
33
+ // 取得運行中實例的 IP
34
+ const ipMap = new Map();
35
+ for (const info of instanceInfos) {
36
+ if (info.state === 'Running') {
37
+ const instanceName = instance_name_value_object_1.InstanceName.createFromFullName(info.name);
38
+ const ip = await this.instanceManager.getInstanceIP(instanceName);
39
+ ipMap.set(info.name, ip);
40
+ }
41
+ else {
42
+ ipMap.set(info.name, null);
43
+ }
44
+ }
45
+ // 使用 Entity 聚合資訊並計算統計
46
+ const { instances, statistics } = instance_list_aggregator_entity_1.InstanceListAggregator.aggregateWithStatistics(instanceInfos, metadataList, ipMap);
47
+ return {
48
+ instances,
49
+ total: statistics.total,
50
+ runningCount: statistics.runningCount,
51
+ stoppedCount: statistics.stoppedCount,
52
+ };
53
+ }
54
+ };
55
+ exports.ListInstancesUseCase = ListInstancesUseCase;
56
+ exports.ListInstancesUseCase = ListInstancesUseCase = __decorate([
57
+ (0, tsyringe_1.injectable)(),
58
+ __param(0, (0, tsyringe_1.inject)(tokens_1.TOKENS.IInstanceManagerPort)),
59
+ __param(1, (0, tsyringe_1.inject)(tokens_1.TOKENS.IInstanceStoragePort)),
60
+ __metadata("design:paramtypes", [Object, Object])
61
+ ], ListInstancesUseCase);
62
+ //# sourceMappingURL=list-instances.use-case.js.map