@gravito/launchpad 1.2.1 → 1.3.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/CHANGELOG.md +55 -0
- package/README.md +51 -43
- package/README.zh-TW.md +110 -0
- package/dist/index.d.mts +324 -7
- package/dist/index.d.ts +324 -7
- package/dist/index.js +407 -29
- package/dist/index.mjs +395 -29
- package/package.json +7 -5
- package/scripts/check-coverage.ts +64 -0
- package/src/Application/MissionQueue.ts +143 -0
- package/src/Application/PoolManager.ts +170 -18
- package/src/Application/RefurbishUnit.ts +24 -12
- package/src/Domain/Events.ts +46 -0
- package/src/Domain/Interfaces.ts +82 -0
- package/src/Domain/Rocket.ts +18 -0
- package/src/Infrastructure/Docker/DockerAdapter.ts +80 -3
- package/src/Infrastructure/Git/ShellGitAdapter.ts +1 -1
- package/src/index.ts +3 -0
- package/tests/Deployment.test.ts +3 -1
- package/tests/pool-manager.test.ts +3 -1
- package/tests/resource-limits.test.ts +241 -0
- package/tsconfig.json +14 -26
- package/.turbo/turbo-build.log +0 -18
- package/.turbo/turbo-test$colon$coverage.log +0 -183
- package/.turbo/turbo-test.log +0 -100
- package/.turbo/turbo-typecheck.log +0 -1
|
@@ -14,8 +14,18 @@ export class DockerAdapter implements IDockerAdapter {
|
|
|
14
14
|
private baseImage = 'oven/bun:1.0-slim'
|
|
15
15
|
private runtime = getRuntimeAdapter()
|
|
16
16
|
|
|
17
|
+
// 快取目錄配置
|
|
18
|
+
private readonly cacheConfig = {
|
|
19
|
+
hostCachePath: process.env.BUN_CACHE_PATH || `${process.env.HOME}/.bun/install/cache`,
|
|
20
|
+
containerCachePathRoot: '/root/.bun/install/cache',
|
|
21
|
+
containerCachePathBun: '/home/bun/.bun/install/cache',
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
async createBaseContainer(): Promise<string> {
|
|
18
|
-
const rocketId = `rocket-${
|
|
25
|
+
const rocketId = `rocket-${crypto.randomUUID()}`
|
|
26
|
+
|
|
27
|
+
// 確保宿主機快取目錄存在
|
|
28
|
+
await this.ensureCacheDirectory()
|
|
19
29
|
|
|
20
30
|
const proc = this.runtime.spawn([
|
|
21
31
|
'docker',
|
|
@@ -27,10 +37,39 @@ export class DockerAdapter implements IDockerAdapter {
|
|
|
27
37
|
'gravito-origin=launchpad',
|
|
28
38
|
'-p',
|
|
29
39
|
'3000', // 讓 Docker 分配隨機宿主機埠
|
|
40
|
+
// === 快取掛載(關鍵) ===
|
|
30
41
|
'-v',
|
|
31
|
-
`${
|
|
42
|
+
`${this.cacheConfig.hostCachePath}:${this.cacheConfig.containerCachePathRoot}:rw`,
|
|
32
43
|
'-v',
|
|
33
|
-
`${
|
|
44
|
+
`${this.cacheConfig.hostCachePath}:${this.cacheConfig.containerCachePathBun}:rw`,
|
|
45
|
+
// === 環境變數配置(確保 bun 使用快取) ===
|
|
46
|
+
'-e',
|
|
47
|
+
`BUN_INSTALL_CACHE_DIR=${this.cacheConfig.containerCachePathBun}`,
|
|
48
|
+
'-e',
|
|
49
|
+
'BUN_INSTALL_CACHE=shared',
|
|
50
|
+
'-e',
|
|
51
|
+
'BUN_INSTALL_PREFER_OFFLINE=true',
|
|
52
|
+
// === 效能相關環境變數 ===
|
|
53
|
+
'-e',
|
|
54
|
+
'NODE_ENV=development',
|
|
55
|
+
// === 資源限制(防止單一容器占用過多資源) ===
|
|
56
|
+
'--memory',
|
|
57
|
+
'1g',
|
|
58
|
+
'--memory-swap',
|
|
59
|
+
'1g',
|
|
60
|
+
'--cpus',
|
|
61
|
+
'1.0',
|
|
62
|
+
// === 安全設定 ===
|
|
63
|
+
'--security-opt',
|
|
64
|
+
'no-new-privileges:true',
|
|
65
|
+
'--cap-drop',
|
|
66
|
+
'ALL',
|
|
67
|
+
'--cap-add',
|
|
68
|
+
'CHOWN',
|
|
69
|
+
'--cap-add',
|
|
70
|
+
'SETUID',
|
|
71
|
+
'--cap-add',
|
|
72
|
+
'SETGID',
|
|
34
73
|
this.baseImage,
|
|
35
74
|
'tail',
|
|
36
75
|
'-f',
|
|
@@ -42,6 +81,8 @@ export class DockerAdapter implements IDockerAdapter {
|
|
|
42
81
|
const exitCode = await proc.exited
|
|
43
82
|
|
|
44
83
|
if (containerId.length === 64 && /^[0-9a-f]+$/.test(containerId)) {
|
|
84
|
+
// 驗證快取是否正確掛載
|
|
85
|
+
await this.verifyCacheMount(containerId)
|
|
45
86
|
return containerId
|
|
46
87
|
}
|
|
47
88
|
|
|
@@ -53,6 +94,42 @@ export class DockerAdapter implements IDockerAdapter {
|
|
|
53
94
|
return containerId
|
|
54
95
|
}
|
|
55
96
|
|
|
97
|
+
/**
|
|
98
|
+
* 確保快取目錄存在
|
|
99
|
+
*
|
|
100
|
+
* @private
|
|
101
|
+
* @since 1.3.0
|
|
102
|
+
*/
|
|
103
|
+
private async ensureCacheDirectory(): Promise<void> {
|
|
104
|
+
const cachePath = this.cacheConfig.hostCachePath
|
|
105
|
+
const proc = this.runtime.spawn(['mkdir', '-p', cachePath])
|
|
106
|
+
await proc.exited
|
|
107
|
+
|
|
108
|
+
// 設定適當的權限
|
|
109
|
+
const chmodProc = this.runtime.spawn(['chmod', '777', cachePath])
|
|
110
|
+
await chmodProc.exited
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 驗證快取是否正確掛載
|
|
115
|
+
*
|
|
116
|
+
* @private
|
|
117
|
+
* @since 1.3.0
|
|
118
|
+
*/
|
|
119
|
+
private async verifyCacheMount(containerId: string): Promise<void> {
|
|
120
|
+
const result = await this.executeCommand(containerId, [
|
|
121
|
+
'sh',
|
|
122
|
+
'-c',
|
|
123
|
+
`ls -la ${this.cacheConfig.containerCachePathBun} 2>/dev/null || echo 'NOT_MOUNTED'`,
|
|
124
|
+
])
|
|
125
|
+
|
|
126
|
+
if (result.stdout.includes('NOT_MOUNTED')) {
|
|
127
|
+
console.warn(`[DockerAdapter] 警告: 容器 ${containerId} 的快取目錄可能未正確掛載`)
|
|
128
|
+
} else {
|
|
129
|
+
console.log(`[DockerAdapter] 快取目錄已確認掛載: ${containerId}`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
56
133
|
async getExposedPort(containerId: string, containerPort = 3000): Promise<number> {
|
|
57
134
|
const proc = this.runtime.spawn(['docker', 'port', containerId, containerPort.toString()])
|
|
58
135
|
const stdout = await new Response(proc.stdout ?? null).text()
|
|
@@ -15,7 +15,7 @@ export class ShellGitAdapter implements IGitAdapter {
|
|
|
15
15
|
private baseDir = '/tmp/gravito-launchpad-git'
|
|
16
16
|
|
|
17
17
|
async clone(repoUrl: string, branch: string): Promise<string> {
|
|
18
|
-
const dirName = `${Date.now()}-${
|
|
18
|
+
const dirName = `${Date.now()}-${crypto.randomUUID()}`
|
|
19
19
|
const targetDir = `${this.baseDir}/${dirName}`
|
|
20
20
|
|
|
21
21
|
await mkdir(this.baseDir, { recursive: true })
|
package/src/index.ts
CHANGED
|
@@ -13,9 +13,12 @@ import { CachedRocketRepository } from './Infrastructure/Persistence/CachedRocke
|
|
|
13
13
|
import { BunProxyAdapter } from './Infrastructure/Router/BunProxyAdapter'
|
|
14
14
|
|
|
15
15
|
export * from './Application/MissionControl'
|
|
16
|
+
export * from './Application/MissionQueue'
|
|
16
17
|
export * from './Application/PayloadInjector'
|
|
17
18
|
export * from './Application/PoolManager'
|
|
18
19
|
export * from './Application/RefurbishUnit'
|
|
20
|
+
export * from './Domain/Events'
|
|
21
|
+
export * from './Domain/Interfaces'
|
|
19
22
|
export * from './Domain/Mission'
|
|
20
23
|
export * from './Domain/Rocket'
|
|
21
24
|
export * from './Domain/RocketStatus'
|
package/tests/Deployment.test.ts
CHANGED
|
@@ -23,7 +23,9 @@ describe('Payload Injection Flow', () => {
|
|
|
23
23
|
findAll: mock(() => Promise.resolve([])),
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
const pool = new PoolManager(mockDocker, mockRepo
|
|
26
|
+
const pool = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
|
|
27
|
+
exhaustionStrategy: 'dynamic',
|
|
28
|
+
})
|
|
27
29
|
const injector = new PayloadInjector(mockDocker, mockGit)
|
|
28
30
|
|
|
29
31
|
it('應該能從指派任務到成功點火', async () => {
|
|
@@ -39,7 +39,9 @@ describe('PoolManager', () => {
|
|
|
39
39
|
test('assigns missions to idle rockets or creates new ones', async () => {
|
|
40
40
|
const docker = new FakeDocker()
|
|
41
41
|
const repo = new InMemoryRocketRepository()
|
|
42
|
-
const pool = new PoolManager(docker as any, repo
|
|
42
|
+
const pool = new PoolManager(docker as any, repo, undefined, undefined, {
|
|
43
|
+
exhaustionStrategy: 'dynamic',
|
|
44
|
+
})
|
|
43
45
|
|
|
44
46
|
const mission = Mission.create({
|
|
45
47
|
id: 'mission-2',
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from 'bun:test'
|
|
2
|
+
import { MissionQueue, PoolExhaustedException } from '../src/Application/MissionQueue'
|
|
3
|
+
import { PoolManager } from '../src/Application/PoolManager'
|
|
4
|
+
import { Mission } from '../src/Domain/Mission'
|
|
5
|
+
import type { Rocket } from '../src/Domain/Rocket'
|
|
6
|
+
import { RocketStatus } from '../src/Domain/RocketStatus'
|
|
7
|
+
|
|
8
|
+
describe('MissionQueue', () => {
|
|
9
|
+
let queue: MissionQueue
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
queue = new MissionQueue(3, 1000)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('should enqueue missions and track length', () => {
|
|
16
|
+
const mission = Mission.create({
|
|
17
|
+
id: 'pr-1',
|
|
18
|
+
repoUrl: 'https://github.com/test/repo',
|
|
19
|
+
branch: 'main',
|
|
20
|
+
commitSha: 'abc123',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const promise = queue.enqueue(mission)
|
|
24
|
+
expect(queue.length).toBe(1)
|
|
25
|
+
expect(promise).toBeInstanceOf(Promise)
|
|
26
|
+
|
|
27
|
+
// 清理:dequeue 或 catch 錯誤以避免 unhandled rejection
|
|
28
|
+
promise.catch(() => {})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test('should reject when queue is full', async () => {
|
|
32
|
+
const promises = []
|
|
33
|
+
for (let i = 0; i < 3; i++) {
|
|
34
|
+
const promise = queue.enqueue(
|
|
35
|
+
Mission.create({
|
|
36
|
+
id: `pr-${i}`,
|
|
37
|
+
repoUrl: 'https://github.com/test/repo',
|
|
38
|
+
branch: 'main',
|
|
39
|
+
commitSha: 'abc123',
|
|
40
|
+
})
|
|
41
|
+
)
|
|
42
|
+
// 捕獲超時錯誤以避免 unhandled rejection
|
|
43
|
+
promise.catch(() => {})
|
|
44
|
+
promises.push(promise)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const mission = Mission.create({
|
|
48
|
+
id: 'pr-4',
|
|
49
|
+
repoUrl: 'https://github.com/test/repo',
|
|
50
|
+
branch: 'main',
|
|
51
|
+
commitSha: 'abc123',
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
await expect(queue.enqueue(mission)).rejects.toThrow(PoolExhaustedException)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
test('should dequeue in FIFO order', () => {
|
|
58
|
+
const m1 = Mission.create({
|
|
59
|
+
id: 'pr-1',
|
|
60
|
+
repoUrl: 'https://github.com/test/repo',
|
|
61
|
+
branch: 'main',
|
|
62
|
+
commitSha: 'abc123',
|
|
63
|
+
})
|
|
64
|
+
const m2 = Mission.create({
|
|
65
|
+
id: 'pr-2',
|
|
66
|
+
repoUrl: 'https://github.com/test/repo',
|
|
67
|
+
branch: 'main',
|
|
68
|
+
commitSha: 'abc123',
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const p1 = queue.enqueue(m1)
|
|
72
|
+
const p2 = queue.enqueue(m2)
|
|
73
|
+
|
|
74
|
+
const first = queue.dequeue()
|
|
75
|
+
expect(first?.mission.id).toBe('pr-1')
|
|
76
|
+
|
|
77
|
+
const second = queue.dequeue()
|
|
78
|
+
expect(second?.mission.id).toBe('pr-2')
|
|
79
|
+
|
|
80
|
+
const empty = queue.dequeue()
|
|
81
|
+
expect(empty).toBeNull()
|
|
82
|
+
|
|
83
|
+
// 清理未使用的 promises
|
|
84
|
+
p1.catch(() => {})
|
|
85
|
+
p2.catch(() => {})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('should provide stats', async () => {
|
|
89
|
+
const promise = queue.enqueue(
|
|
90
|
+
Mission.create({
|
|
91
|
+
id: 'pr-1',
|
|
92
|
+
repoUrl: 'https://github.com/test/repo',
|
|
93
|
+
branch: 'main',
|
|
94
|
+
commitSha: 'abc123',
|
|
95
|
+
})
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// 等待一點時間讓時間戳有差異
|
|
99
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
100
|
+
|
|
101
|
+
const stats = queue.getStats()
|
|
102
|
+
expect(stats.length).toBe(1)
|
|
103
|
+
expect(stats.maxSize).toBe(3)
|
|
104
|
+
expect(stats.oldestWaitMs).toBeGreaterThanOrEqual(0)
|
|
105
|
+
|
|
106
|
+
// 清理
|
|
107
|
+
promise.catch(() => {})
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
describe('PoolManager Resource Limits', () => {
|
|
112
|
+
let mockDocker: any
|
|
113
|
+
let mockRepo: any
|
|
114
|
+
let rockets: Rocket[]
|
|
115
|
+
|
|
116
|
+
beforeEach(() => {
|
|
117
|
+
rockets = []
|
|
118
|
+
mockDocker = {
|
|
119
|
+
createBaseContainer: mock(() => Promise.resolve(`container-${Date.now()}`)),
|
|
120
|
+
removeContainer: mock(() => Promise.resolve()),
|
|
121
|
+
executeCommand: mock(() => Promise.resolve({ exitCode: 0, stdout: '', stderr: '' })),
|
|
122
|
+
getExposedPort: mock(() => Promise.resolve(3000)),
|
|
123
|
+
removeContainerByLabel: mock(() => Promise.resolve()),
|
|
124
|
+
streamLogs: mock(() => {}),
|
|
125
|
+
getStats: mock(() => Promise.resolve({ cpu: '0%', memory: '0MB' })),
|
|
126
|
+
}
|
|
127
|
+
mockRepo = {
|
|
128
|
+
save: mock((r: Rocket) => {
|
|
129
|
+
const index = rockets.findIndex((rocket) => rocket.id === r.id)
|
|
130
|
+
if (index >= 0) {
|
|
131
|
+
rockets[index] = r
|
|
132
|
+
} else {
|
|
133
|
+
rockets.push(r)
|
|
134
|
+
}
|
|
135
|
+
return Promise.resolve()
|
|
136
|
+
}),
|
|
137
|
+
findAll: mock(() => Promise.resolve(rockets)),
|
|
138
|
+
findIdle: mock(() =>
|
|
139
|
+
Promise.resolve(rockets.find((r) => r.status === RocketStatus.IDLE) || null)
|
|
140
|
+
),
|
|
141
|
+
findById: mock((id: string) => Promise.resolve(rockets.find((r) => r.id === id) || null)),
|
|
142
|
+
delete: mock(() => Promise.resolve()),
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('should reject when maxRockets reached with reject strategy', async () => {
|
|
147
|
+
const manager = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
|
|
148
|
+
maxRockets: 2,
|
|
149
|
+
exhaustionStrategy: 'reject',
|
|
150
|
+
warmupCount: 2,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
await manager.warmup()
|
|
154
|
+
expect(rockets.length).toBe(2)
|
|
155
|
+
|
|
156
|
+
// 使用全部火箭
|
|
157
|
+
const m1 = Mission.create({
|
|
158
|
+
id: 'pr-1',
|
|
159
|
+
repoUrl: 'https://github.com/test/repo',
|
|
160
|
+
branch: 'main',
|
|
161
|
+
commitSha: 'abc1',
|
|
162
|
+
})
|
|
163
|
+
const m2 = Mission.create({
|
|
164
|
+
id: 'pr-2',
|
|
165
|
+
repoUrl: 'https://github.com/test/repo',
|
|
166
|
+
branch: 'main',
|
|
167
|
+
commitSha: 'abc2',
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
await manager.assignMission(m1)
|
|
171
|
+
await manager.assignMission(m2)
|
|
172
|
+
|
|
173
|
+
// 第三個請求應該被拒絕
|
|
174
|
+
const m3 = Mission.create({
|
|
175
|
+
id: 'pr-3',
|
|
176
|
+
repoUrl: 'https://github.com/test/repo',
|
|
177
|
+
branch: 'main',
|
|
178
|
+
commitSha: 'abc3',
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
await expect(manager.assignMission(m3)).rejects.toThrow(PoolExhaustedException)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test('should respect maxRockets limit', async () => {
|
|
185
|
+
const manager = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
|
|
186
|
+
maxRockets: 5,
|
|
187
|
+
warmupCount: 3,
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
await manager.warmup()
|
|
191
|
+
|
|
192
|
+
const status = await manager.getPoolStatus()
|
|
193
|
+
expect(status.total).toBe(3)
|
|
194
|
+
expect(status.idle).toBe(3)
|
|
195
|
+
expect(status.maxRockets).toBe(5)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('should queue missions when pool is exhausted', async () => {
|
|
199
|
+
const manager = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
|
|
200
|
+
maxRockets: 1,
|
|
201
|
+
exhaustionStrategy: 'queue',
|
|
202
|
+
queueTimeoutMs: 5000,
|
|
203
|
+
warmupCount: 1,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
await manager.warmup()
|
|
207
|
+
|
|
208
|
+
const m1 = Mission.create({
|
|
209
|
+
id: 'pr-1',
|
|
210
|
+
repoUrl: 'https://github.com/test/repo',
|
|
211
|
+
branch: 'main',
|
|
212
|
+
commitSha: 'abc1',
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const rocket1 = await manager.assignMission(m1)
|
|
216
|
+
expect(rocket1.status).toBe(RocketStatus.PREPARING)
|
|
217
|
+
|
|
218
|
+
// 第二個任務應該進入隊列
|
|
219
|
+
const m2 = Mission.create({
|
|
220
|
+
id: 'pr-2',
|
|
221
|
+
repoUrl: 'https://github.com/test/repo',
|
|
222
|
+
branch: 'main',
|
|
223
|
+
commitSha: 'abc2',
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
const promise = manager.assignMission(m2)
|
|
227
|
+
|
|
228
|
+
// 等待 promise 開始處理
|
|
229
|
+
await new Promise((resolve) => setTimeout(resolve, 10))
|
|
230
|
+
|
|
231
|
+
const stats = manager.getQueueStats()
|
|
232
|
+
expect(stats.length).toBe(1)
|
|
233
|
+
|
|
234
|
+
// 模擬回收
|
|
235
|
+
rocket1.ignite()
|
|
236
|
+
await manager.recycle('pr-1')
|
|
237
|
+
|
|
238
|
+
const rocket2 = await promise
|
|
239
|
+
expect(rocket2.currentMission?.id).toBe('pr-2')
|
|
240
|
+
})
|
|
241
|
+
})
|
package/tsconfig.json
CHANGED
|
@@ -1,27 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
]
|
|
17
|
-
}
|
|
18
|
-
},
|
|
19
|
-
"include": [
|
|
20
|
-
"src/**/*"
|
|
21
|
-
],
|
|
22
|
-
"exclude": [
|
|
23
|
-
"node_modules",
|
|
24
|
-
"dist",
|
|
25
|
-
"**/*.test.ts"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"skipLibCheck": true,
|
|
6
|
+
"types": ["bun-types"],
|
|
7
|
+
"baseUrl": ".",
|
|
8
|
+
"paths": {
|
|
9
|
+
"@gravito/core": ["../../packages/core/src/index.ts"],
|
|
10
|
+
"@gravito/*": ["../../packages/*/src/index.ts"]
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*"],
|
|
14
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
15
|
+
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
[0m[2m[35m$[0m [2m[1mtsup src/index.ts --format cjs,esm --dts[0m
|
|
3
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
4
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
5
|
-
[34mCLI[39m tsup v8.5.1
|
|
6
|
-
[34mCLI[39m Target: esnext
|
|
7
|
-
[34mCJS[39m Build start
|
|
8
|
-
[34mESM[39m Build start
|
|
9
|
-
[33mCJS[39m [33mYou have emitDecoratorMetadata enabled but @swc/core was not installed, skipping swc plugin[39m
|
|
10
|
-
[33mESM[39m [33mYou have emitDecoratorMetadata enabled but @swc/core was not installed, skipping swc plugin[39m
|
|
11
|
-
[32mCJS[39m [1mdist/index.js [22m[32m27.01 KB[39m
|
|
12
|
-
[32mCJS[39m ⚡️ Build success in 24ms
|
|
13
|
-
[32mESM[39m [1mdist/index.mjs [22m[32m25.50 KB[39m
|
|
14
|
-
[32mESM[39m ⚡️ Build success in 24ms
|
|
15
|
-
DTS Build start
|
|
16
|
-
DTS ⚡️ Build success in 1979ms
|
|
17
|
-
DTS dist/index.d.ts 5.14 KB
|
|
18
|
-
DTS dist/index.d.mts 5.14 KB
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
$ bun test --coverage --coverage-threshold=80
|
|
2
|
-
bun test v1.3.4 (5eb2145b)
|
|
3
|
-
|
|
4
|
-
tests/mission.test.ts:
|
|
5
|
-
(pass) Mission > exposes mission properties [0.31ms]
|
|
6
|
-
|
|
7
|
-
tests/Telemetry.test.ts:
|
|
8
|
-
[MissionControl] 準備發射任務: pr-1
|
|
9
|
-
[MissionControl] 任務 pr-1 映射端口: 3000
|
|
10
|
-
(pass) MissionControl Telemetry > 發射時應該能正確啟動日誌串流與效能監控 [6.79ms]
|
|
11
|
-
|
|
12
|
-
tests/mission-control.test.ts:
|
|
13
|
-
[MissionControl] 準備發射任務: mission-telemetry
|
|
14
|
-
[MissionControl] 任務 mission-telemetry 映射端口: 3000
|
|
15
|
-
[MissionControl] 任務 mission-telemetry TTL 已到期,執行自動回收...
|
|
16
|
-
(pass) MissionControl timers > emits stats and schedules recycle [2.17ms]
|
|
17
|
-
|
|
18
|
-
tests/pool-manager.test.ts:
|
|
19
|
-
[LaunchPad] 正在熱機,準備發射 2 架新火箭...
|
|
20
|
-
(pass) PoolManager > warms up the pool with base containers [1.23ms]
|
|
21
|
-
[LaunchPad] 正在熱機,準備發射 1 架新火箭...
|
|
22
|
-
[LaunchPad] 資源吃緊,正在緊急呼叫後援火箭...
|
|
23
|
-
(pass) PoolManager > assigns missions to idle rockets or creates new ones [1.90ms]
|
|
24
|
-
[LaunchPad] 正在熱機,準備發射 1 架新火箭...
|
|
25
|
-
(pass) PoolManager > recycles rockets through refurbish flow [2.99ms]
|
|
26
|
-
|
|
27
|
-
tests/enterprise-coverage.test.ts:
|
|
28
|
-
(pass) Launchpad enterprise coverage > covers core enterprise primitives [0.56ms]
|
|
29
|
-
|
|
30
|
-
tests/repository.test.ts:
|
|
31
|
-
(pass) InMemoryRocketRepository > stores and retrieves rockets [0.75ms]
|
|
32
|
-
(pass) InMemoryRocketRepository > finds idle rockets [0.19ms]
|
|
33
|
-
|
|
34
|
-
tests/Rocket.test.ts:
|
|
35
|
-
(pass) Rocket > transitions through mission lifecycle and emits events [0.17ms]
|
|
36
|
-
(pass) Rocket > guards invalid transitions [0.19ms]
|
|
37
|
-
(pass) Rocket > assigns domain and serializes/deserializes [0.15ms]
|
|
38
|
-
(pass) Rocket > rejects assigning mission when not idle [0.06ms]
|
|
39
|
-
|
|
40
|
-
tests/Refurbishment.test.ts:
|
|
41
|
-
(pass) RefurbishUnit (Rocket Recovery) > 應該能執行深度清理並將火箭重置為 IDLE [1.11ms]
|
|
42
|
-
[RefurbishUnit] 正在翻新火箭: r1 (容器: c1)
|
|
43
|
-
[RefurbishUnit] 火箭 r1 翻新完成,已進入 IDLE 狀態。
|
|
44
|
-
[RefurbishUnit] 正在翻新火箭: r2 (容器: c2)
|
|
45
|
-
[RefurbishUnit] 清理失敗: Disk Full
|
|
46
|
-
(pass) RefurbishUnit (Rocket Recovery) > 如果清理失敗應該將火箭除役 [0.28ms]
|
|
47
|
-
|
|
48
|
-
tests/payload-injector.test.ts:
|
|
49
|
-
(pass) PayloadInjector > throws when rocket has no mission [1.02ms]
|
|
50
|
-
[PayloadInjector] 正在拉取代碼: https://example.com/repo.git (main)
|
|
51
|
-
[PayloadInjector] 正在注入載荷至容器: container-10
|
|
52
|
-
[PayloadInjector] 正在安裝依賴...
|
|
53
|
-
[PayloadInjector] 點火!
|
|
54
|
-
(pass) PayloadInjector > deploys payload and ignites rocket [0.78ms]
|
|
55
|
-
[PayloadInjector] 正在拉取代碼: https://example.com/repo.git (main)
|
|
56
|
-
[PayloadInjector] 正在注入載荷至容器: container-11
|
|
57
|
-
[PayloadInjector] 正在安裝依賴...
|
|
58
|
-
(pass) PayloadInjector > throws when install fails [4.37ms]
|
|
59
|
-
[PayloadInjector] 正在拉取代碼: https://example.com/repo.git (main)
|
|
60
|
-
[PayloadInjector] 正在注入載荷至容器: container-12
|
|
61
|
-
[PayloadInjector] 正在安裝依賴...
|
|
62
|
-
[PayloadInjector] 點火!
|
|
63
|
-
(pass) PayloadInjector > logs when run command fails but still ignites [8.24ms]
|
|
64
|
-
|
|
65
|
-
tests/docker-adapter.test.ts:
|
|
66
|
-
(pass) DockerAdapter > creates base container when stdout has container id [8.90ms]
|
|
67
|
-
(pass) DockerAdapter > returns container id even when exit code is zero but id is invalid [0.28ms]
|
|
68
|
-
(pass) DockerAdapter > throws when container creation fails [0.70ms]
|
|
69
|
-
(pass) DockerAdapter > getExposedPort parses the first line [1.64ms]
|
|
70
|
-
(pass) DockerAdapter > getExposedPort throws on empty output [6.46ms]
|
|
71
|
-
(pass) DockerAdapter > getExposedPort throws on invalid output [4.70ms]
|
|
72
|
-
(pass) DockerAdapter > copyFiles throws on non-zero exit code [0.54ms]
|
|
73
|
-
(pass) DockerAdapter > removeContainerByLabel removes containers when ids exist [0.72ms]
|
|
74
|
-
(pass) DockerAdapter > executeCommand returns stdout and stderr [3.41ms]
|
|
75
|
-
(pass) DockerAdapter > removeContainer executes docker rm [1.66ms]
|
|
76
|
-
(pass) DockerAdapter > getStats parses cpu and memory output [0.39ms]
|
|
77
|
-
(pass) DockerAdapter > streamLogs forwards stdout and stderr [43.53ms]
|
|
78
|
-
|
|
79
|
-
tests/Integration.test.ts:
|
|
80
|
-
[LaunchPad] 正在熱機,準備發射 1 架新火箭...
|
|
81
|
-
[Test] 容器內 Bun 版本: 1.0.36
|
|
82
|
-
(pass) LaunchPad 集成測試 (真實 Docker) > 應該能成功熱機並在容器內執行指令 [3201.00ms]
|
|
83
|
-
|
|
84
|
-
tests/Deployment.test.ts:
|
|
85
|
-
[LaunchPad] 資源吃緊,正在緊急呼叫後援火箭...
|
|
86
|
-
[PayloadInjector] 正在拉取代碼: http://git (dev)
|
|
87
|
-
[PayloadInjector] 正在注入載荷至容器: cid-1
|
|
88
|
-
[PayloadInjector] 正在安裝依賴...
|
|
89
|
-
[PayloadInjector] 點火!
|
|
90
|
-
(pass) Payload Injection Flow > 應該能從指派任務到成功點火 [1.02ms]
|
|
91
|
-
|
|
92
|
-
tests/PoolManager.test.ts:
|
|
93
|
-
(pass) PoolManager (Application Service) > 應該能正確熱機並指派任務 [1.54ms]
|
|
94
|
-
------------------------------------------------------------|---------|---------|-------------------
|
|
95
|
-
File | % Funcs | % Lines | Uncovered Line #s
|
|
96
|
-
[LaunchPad] 正在熱機,準備發射 2 架新火箭...
|
|
97
|
-
------------------------------------------------------------|---------|---------|-------------------
|
|
98
|
-
All files | 30.25 | 42.00 |
|
|
99
|
-
../core/src/Application.ts | 0.00 | 8.87 | 139-313,325-326,336-337,348,358,368,375,382
|
|
100
|
-
../core/src/ConfigManager.ts | 0.00 | 14.04 | 16-22,29-34,42-75,82
|
|
101
|
-
../core/src/Container.ts | 0.00 | 28.95 | 26,34,41,48-67,74,81-82
|
|
102
|
-
../core/src/ErrorHandler.ts | 0.00 | 3.45 | 23-41,49-75,101-297,304-325,332-357,365-381
|
|
103
|
-
../core/src/Event.ts | 100.00 | 100.00 |
|
|
104
|
-
../core/src/EventManager.ts | 0.00 | 7.01 | 78,85,108-126,136-243,250-256,263-271
|
|
105
|
-
../core/src/GlobalErrorHandlers.ts | 0.00 | 1.19 | 58-72,78-88,92-101,106-186,191-210,214-228,239-252
|
|
106
|
-
../core/src/GravitoServer.ts | 0.00 | 8.77 | 25-76
|
|
107
|
-
../core/src/HookManager.ts | 0.00 | 8.33 | 37-73,93-121
|
|
108
|
-
../core/src/Logger.ts | 0.00 | 55.56 | 17,21,25
|
|
109
|
-
../core/src/PlanetCore.ts | 0.00 | 10.53 | 117-131,162-208,218-227,237-269,273-390,394-443,453-468,485-501
|
|
110
|
-
../core/src/Route.ts | 0.00 | 44.44 | 18-22,28-29,44,55,66,77,88,92,96
|
|
111
|
-
../core/src/Router.ts | 0.00 | 7.68 | 68-121,131-154,182-184,190-193,201-205,212,224-232,243-251,262-270,281-289,300-308,312-354,365-380,414-459,466-471,478-505,512,522-524,531,538-553,557-600,607,614,622,636-644,658-666,680-688,702-710,724-732,743-758,765-803,810-892,899-913
|
|
112
|
-
../core/src/ServiceProvider.ts | 0.00 | 15.07 | 58,91,114-150,179-188,198-209
|
|
113
|
-
../core/src/adapters/GravitoEngineAdapter.ts | 0.00 | 30.43 | 27-31,35-44,48-50,54,58,62-65,69,73,78-81,85-86
|
|
114
|
-
../core/src/adapters/PhotonAdapter.ts | 0.00 | 13.93 | 46,53-55,62-108,112,116,120,124,130-163,167-168,191-195,203-207,214-257,261-262,266-269,273-276,280-283,287-298,302-309,313-318,322,326,330,334,341-355,359,363,367-446,462-467,471-473,487-492,501-512,521-528,564-584,592,596-612,616-626,630-635,639,643-651,656,660-675,683-695,708-710
|
|
115
|
-
../core/src/adapters/bun/BunContext.ts | 0.00 | 12.58 | 38-44,52-75,80-86,90-96,100-106,110-116,120-125,129-134,138,142,146,150-182,189-205,209,214,218-223
|
|
116
|
-
../core/src/adapters/bun/BunNativeAdapter.ts | 0.00 | 10.23 | 21-23,32-36,40-46,50,54,58-87,91,95,99-207
|
|
117
|
-
../core/src/adapters/bun/BunRequest.ts | 0.00 | 10.62 | 13-17,22-34,39,43,47-55,59-62,68-108,114,118-131,135-146
|
|
118
|
-
../core/src/adapters/bun/RadixNode.ts | 0.00 | 17.65 | 39-40,44-52,56-72
|
|
119
|
-
../core/src/adapters/bun/RadixRouter.ts | 0.00 | 8.13 | 17,24-62,69-84,88-148,152-162,169-175,182-193
|
|
120
|
-
../core/src/adapters/bun/types.ts | 100.00 | 100.00 |
|
|
121
|
-
../core/src/adapters/types.ts | 0.00 | 0.00 | 280-287
|
|
122
|
-
../core/src/engine/AOTRouter.ts | 0.00 | 10.78 | 67-85,93-133,144-145,157-159,172-208,215,228-272,279,294-306,320-327,334-345
|
|
123
|
-
../core/src/engine/FastContext.ts | 0.00 | 19.09 | 30,37-45,52-61,65-84,88-89,93-94,98-101,105-109,113-129,133-134,138-169,192-198,208-212,219-223,231-236,240-245,249-254,258-263,267-271,275-280,284,288,292,296-319,329-334,338,349,353,361-363
|
|
124
|
-
../core/src/engine/Gravito.ts | 0.00 | 14.67 | 38-52,82-103,118,125,132,139,146,153,160,167-171,184-197,208-214,225-226,233-255,293-322,330-367,374-391,398-410,417-421,428-456,463-519
|
|
125
|
-
../core/src/engine/MinimalContext.ts | 0.00 | 21.57 | 23-37,41,45,52-64,68,72-84,88,92-112,130,139-142,146-149,153-156,160-163,167-170,174-177,183-187,195-198,202,206,210,214-225,229,235-237
|
|
126
|
-
../core/src/engine/analyzer.ts | 0.00 | 2.08 | 25-49,56-77
|
|
127
|
-
../core/src/engine/constants.ts | 100.00 | 100.00 |
|
|
128
|
-
../core/src/engine/index.ts | 100.00 | 100.00 |
|
|
129
|
-
../core/src/engine/path.ts | 0.00 | 2.63 | 22-43,51-65
|
|
130
|
-
../core/src/engine/pool.ts | 0.00 | 16.67 | 44-46,58-64,76-79,89-103,114-119
|
|
131
|
-
../core/src/exceptions/AuthenticationException.ts | 0.00 | 42.86 | 8-11
|
|
132
|
-
../core/src/exceptions/AuthorizationException.ts | 0.00 | 42.86 | 8-11
|
|
133
|
-
../core/src/exceptions/GravitoException.ts | 0.00 | 19.05 | 24-34,39-44
|
|
134
|
-
../core/src/exceptions/HttpException.ts | 0.00 | 60.00 | 9-10
|
|
135
|
-
../core/src/exceptions/ModelNotFoundException.ts | 0.00 | 25.00 | 11-19
|
|
136
|
-
../core/src/exceptions/ValidationException.ts | 0.00 | 35.71 | 22-26,30-31,35-36
|
|
137
|
-
../core/src/exceptions/index.ts | 100.00 | 100.00 |
|
|
138
|
-
../core/src/helpers.ts | 0.00 | 23.14 | 19,54-59,81-82,107-108,133-139,143-147,176-196,218,222-228,248-253,272-277,306-308,323,335,356-359,381-384,403,417,438-441,461-463,483-489
|
|
139
|
-
../core/src/helpers/Arr.ts | 0.00 | 14.58 | 9-13,17,21,25-28,32-41,45-54,58-64,68-75,79-92,96-109,113-120
|
|
140
|
-
../core/src/helpers/Str.ts | 0.00 | 18.56 | 5-10,14-17,27,31,35-41,45-51,55-61,65-66,70-71,75-77,81-89,93-95,99-105,109-117,121-124,128-134
|
|
141
|
-
../core/src/helpers/data.ts | 0.00 | 2.17 | 13-30,34-47,51-64,68-88,96-113,121-134,142-177
|
|
142
|
-
../core/src/helpers/errors.ts | 0.00 | 7.69 | 26-43,51-53,61-63
|
|
143
|
-
../core/src/helpers/response.ts | 0.00 | 12.50 | 29,37-44,52-56,64-70
|
|
144
|
-
../core/src/http/CookieJar.ts | 0.00 | 12.33 | 33-46,53-74,81,88,95-117,124-126
|
|
145
|
-
../core/src/http/middleware/BodySizeLimit.ts | 0.00 | 2.70 | 20-55
|
|
146
|
-
../core/src/http/middleware/Cors.ts | 0.00 | 1.64 | 23-35,43-89
|
|
147
|
-
../core/src/http/middleware/Csrf.ts | 0.00 | 5.05 | 19-24,28-34,38,42-67,75-91,99-135
|
|
148
|
-
../core/src/http/middleware/HeaderTokenGate.ts | 0.00 | 4.55 | 25-35,45-54
|
|
149
|
-
../core/src/http/middleware/SecurityHeaders.ts | 0.00 | 1.67 | 28,32-35,39-46,54-99
|
|
150
|
-
../core/src/http/middleware/ThrottleRequests.ts | 0.00 | 8.51 | 31-73
|
|
151
|
-
../core/src/index.ts | 0.00 | 96.97 |
|
|
152
|
-
../core/src/runtime.ts | 21.05 | 11.87 | 118-131,138-145,149-166,175,188-190,193-216,221-225,230-325,330-420,425-448,467-470,481-518,526-533
|
|
153
|
-
../core/src/security/Encrypter.ts | 0.00 | 13.70 | 21-35,42-59,66-82,86-88,92-97,102-103,110-111
|
|
154
|
-
../core/src/security/Hasher.ts | 0.00 | 9.09 | 25-43
|
|
155
|
-
../core/src/testing/HttpTester.ts | 0.00 | 4.27 | 11-121
|
|
156
|
-
../core/src/testing/TestResponse.ts | 0.00 | 10.92 | 16-17,24,31,38,45,52,59-124,131-132,139-169
|
|
157
|
-
../core/src/testing/index.ts | 100.00 | 100.00 |
|
|
158
|
-
../core/src/types/events.ts | 0.00 | 14.29 | 91-93,101-104,111-125,132-139
|
|
159
|
-
../enterprise/src/Application/Command.ts | 100.00 | 100.00 |
|
|
160
|
-
../enterprise/src/Application/Query.ts | 100.00 | 100.00 |
|
|
161
|
-
../enterprise/src/Application/UseCase.ts | 100.00 | 100.00 |
|
|
162
|
-
../enterprise/src/Domain/AggregateRoot.ts | 50.00 | 55.56 | 1-8
|
|
163
|
-
../enterprise/src/Domain/DomainEvent.ts | 50.00 | 55.56 | 16-19
|
|
164
|
-
../enterprise/src/Domain/Entity.ts | 100.00 | 100.00 |
|
|
165
|
-
../enterprise/src/Domain/Repository.ts | 100.00 | 100.00 |
|
|
166
|
-
../enterprise/src/Domain/ValueObject.ts | 100.00 | 100.00 |
|
|
167
|
-
../enterprise/src/index.ts | 100.00 | 100.00 |
|
|
168
|
-
src/Application/MissionControl.ts | 100.00 | 100.00 |
|
|
169
|
-
src/Application/PayloadInjector.ts | 100.00 | 100.00 |
|
|
170
|
-
src/Application/PoolManager.ts | 100.00 | 100.00 |
|
|
171
|
-
src/Application/RefurbishUnit.ts | 100.00 | 100.00 |
|
|
172
|
-
src/Domain/Events.ts | 100.00 | 100.00 |
|
|
173
|
-
src/Domain/Mission.ts | 83.33 | 100.00 |
|
|
174
|
-
src/Domain/Rocket.ts | 100.00 | 100.00 |
|
|
175
|
-
src/Domain/RocketStatus.ts | 100.00 | 100.00 |
|
|
176
|
-
src/Infrastructure/Docker/DockerAdapter.ts | 94.44 | 100.00 |
|
|
177
|
-
src/Infrastructure/Persistence/InMemoryRocketRepository.ts | 90.91 | 100.00 |
|
|
178
|
-
------------------------------------------------------------|---------|---------|-------------------
|
|
179
|
-
|
|
180
|
-
34 pass
|
|
181
|
-
0 fail
|
|
182
|
-
99 expect() calls
|
|
183
|
-
Ran 34 tests across 13 files. [4.34s]
|