@gravito/launchpad 1.0.0-beta.1 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravito/launchpad",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0",
4
4
  "description": "Container lifecycle management system for flash deployments",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -10,17 +10,26 @@
10
10
  "test": "bun test",
11
11
  "test:coverage": "bun test --coverage --coverage-threshold=80",
12
12
  "test:ci": "bun test --coverage --coverage-threshold=80",
13
- "typecheck": "tsc --noEmit"
13
+ "typecheck": "bun tsc -p tsconfig.json --noEmit --skipLibCheck"
14
14
  },
15
15
  "dependencies": {
16
16
  "@gravito/enterprise": "workspace:*",
17
17
  "@gravito/ripple": "workspace:*",
18
18
  "@gravito/stasis": "workspace:*",
19
19
  "@octokit/rest": "^22.0.1",
20
- "gravito-core": "workspace:*"
20
+ "@gravito/core": "workspace:*"
21
21
  },
22
22
  "devDependencies": {
23
+ "bun-types": "^1.3.5",
23
24
  "tsup": "^8.0.0",
24
- "typescript": "^5.0.0"
25
+ "typescript": "^5.9.3"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/gravito-framework/gravito.git",
33
+ "directory": "packages/launchpad"
25
34
  }
26
35
  }
package/server.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import { getRuntimeAdapter } from '@gravito/core'
1
2
  import { bootstrapLaunchpad } from './src/index'
2
3
 
3
4
  const config = await bootstrapLaunchpad()
4
- Bun.serve(config)
5
+ const runtime = getRuntimeAdapter()
6
+ runtime.serve(config)
5
7
 
6
8
  console.log(`🚀 Launchpad Command Center active at: http://localhost:${config.port}`)
7
9
  console.log(`📡 Telemetry WebSocket channel: ws://localhost:${config.port}/ws`)
@@ -23,8 +23,11 @@ export class PayloadInjector {
23
23
 
24
24
  console.log(`[PayloadInjector] 正在安裝依賴...`)
25
25
 
26
- // 寫入一個強制覆蓋的 bunfig.toml
27
- const bunfigContent = `[install]\nfrozenLockfile = false\n`
26
+ const registry = process.env.LAUNCHPAD_NPM_REGISTRY || process.env.NPM_CONFIG_REGISTRY || ''
27
+ const baseInstallConfig = ['[install]', 'frozenLockfile = false']
28
+ const bunfigContent = registry
29
+ ? `${baseInstallConfig.join('\n')}\nregistry = "${registry}"\n`
30
+ : `${baseInstallConfig.join('\n')}\n`
28
31
  await this.docker.executeCommand(containerId, [
29
32
  'sh',
30
33
  '-c',
@@ -35,7 +38,7 @@ export class PayloadInjector {
35
38
  await this.docker.executeCommand(containerId, ['rm', '-f', '/app/bun.lockb'])
36
39
 
37
40
  // 安裝 (跳過腳本以避免編譯原生模組失敗)
38
- const installRes = await this.docker.executeCommand(containerId, [
41
+ let installRes = await this.docker.executeCommand(containerId, [
39
42
  'bun',
40
43
  'install',
41
44
  '--cwd',
@@ -44,6 +47,24 @@ export class PayloadInjector {
44
47
  '--ignore-scripts',
45
48
  ])
46
49
 
50
+ if (installRes.exitCode !== 0 && !registry) {
51
+ const fallbackRegistry = 'https://registry.npmmirror.com'
52
+ const fallbackBunfig = `${baseInstallConfig.join('\n')}\nregistry = "${fallbackRegistry}"\n`
53
+ await this.docker.executeCommand(containerId, [
54
+ 'sh',
55
+ '-c',
56
+ `echo "${fallbackBunfig}" > /app/bunfig.toml`,
57
+ ])
58
+ installRes = await this.docker.executeCommand(containerId, [
59
+ 'bun',
60
+ 'install',
61
+ '--cwd',
62
+ '/app',
63
+ '--no-save',
64
+ '--ignore-scripts',
65
+ ])
66
+ }
67
+
47
68
  if (installRes.exitCode !== 0) {
48
69
  throw new Error(`安裝依賴失敗: ${installRes.stderr}`)
49
70
  }
@@ -1,12 +1,14 @@
1
+ import { getRuntimeAdapter } from '@gravito/core'
1
2
  import type { IDockerAdapter } from '../../Domain/Interfaces'
2
3
 
3
4
  export class DockerAdapter implements IDockerAdapter {
4
5
  private baseImage = 'oven/bun:1.0-slim'
6
+ private runtime = getRuntimeAdapter()
5
7
 
6
8
  async createBaseContainer(): Promise<string> {
7
9
  const rocketId = `rocket-${Math.random().toString(36).substring(2, 9)}`
8
10
 
9
- const proc = Bun.spawn([
11
+ const proc = this.runtime.spawn([
10
12
  'docker',
11
13
  'run',
12
14
  '-d',
@@ -26,15 +28,16 @@ export class DockerAdapter implements IDockerAdapter {
26
28
  '/dev/null',
27
29
  ])
28
30
 
29
- const stdout = await new Response(proc.stdout).text()
31
+ const stdout = await new Response(proc.stdout ?? null).text()
30
32
  const containerId = stdout.trim()
33
+ const exitCode = await proc.exited
31
34
 
32
35
  if (containerId.length === 64 && /^[0-9a-f]+$/.test(containerId)) {
33
36
  return containerId
34
37
  }
35
38
 
36
- if (proc.exitCode !== 0) {
37
- const stderr = await new Response(proc.stderr).text()
39
+ if (exitCode !== 0) {
40
+ const stderr = await new Response(proc.stderr ?? null).text()
38
41
  throw new Error(`Docker 容器建立失敗: ${stderr || stdout}`)
39
42
  }
40
43
 
@@ -42,8 +45,8 @@ export class DockerAdapter implements IDockerAdapter {
42
45
  }
43
46
 
44
47
  async getExposedPort(containerId: string, containerPort = 3000): Promise<number> {
45
- const proc = Bun.spawn(['docker', 'port', containerId, containerPort.toString()])
46
- const stdout = await new Response(proc.stdout).text()
48
+ const proc = this.runtime.spawn(['docker', 'port', containerId, containerPort.toString()])
49
+ const stdout = await new Response(proc.stdout ?? null).text()
47
50
  // 輸出格式可能包含多行,如 0.0.0.0:32768 和 [::]:32768
48
51
  // 我們取第一行並提取端口
49
52
  const firstLine = stdout.split('\n')[0] ?? ''
@@ -58,10 +61,10 @@ export class DockerAdapter implements IDockerAdapter {
58
61
  }
59
62
 
60
63
  async copyFiles(containerId: string, sourcePath: string, targetPath: string): Promise<void> {
61
- const proc = Bun.spawn(['docker', 'cp', sourcePath, `${containerId}:${targetPath}`])
62
- await proc.exited
63
- if (proc.exitCode && proc.exitCode !== 0) {
64
- const stderr = await new Response(proc.stderr).text()
64
+ const proc = this.runtime.spawn(['docker', 'cp', sourcePath, `${containerId}:${targetPath}`])
65
+ const exitCode = await proc.exited
66
+ if (exitCode && exitCode !== 0) {
67
+ const stderr = await new Response(proc.stderr ?? null).text()
65
68
  throw new Error(stderr || 'Docker copy failed')
66
69
  }
67
70
  }
@@ -70,33 +73,36 @@ export class DockerAdapter implements IDockerAdapter {
70
73
  containerId: string,
71
74
  command: string[]
72
75
  ): Promise<{ stdout: string; stderr: string; exitCode: number }> {
73
- const proc = Bun.spawn(['docker', 'exec', '-u', '0', containerId, ...command])
74
- const stdout = await new Response(proc.stdout).text()
75
- const stderr = await new Response(proc.stderr).text()
76
- await proc.exited
77
- return { stdout, stderr, exitCode: proc.exitCode ?? -1 }
76
+ const proc = this.runtime.spawn(['docker', 'exec', '-u', '0', containerId, ...command])
77
+ const stdout = await new Response(proc.stdout ?? null).text()
78
+ const stderr = await new Response(proc.stderr ?? null).text()
79
+ const exitCode = await proc.exited
80
+ return { stdout, stderr, exitCode }
78
81
  }
79
82
 
80
83
  async removeContainer(containerId: string): Promise<void> {
81
- await Bun.spawn(['docker', 'rm', '-f', containerId]).exited
84
+ await this.runtime.spawn(['docker', 'rm', '-f', containerId]).exited
82
85
  }
83
86
 
84
87
  async removeContainerByLabel(label: string): Promise<void> {
85
- const listProc = Bun.spawn(['docker', 'ps', '-aq', '--filter', `label=${label}`])
86
- const ids = await new Response(listProc.stdout).text()
88
+ const listProc = this.runtime.spawn(['docker', 'ps', '-aq', '--filter', `label=${label}`])
89
+ const ids = await new Response(listProc.stdout ?? null).text()
87
90
 
88
91
  if (ids.trim()) {
89
92
  const idList = ids.trim().split('\n')
90
- await Bun.spawn(['docker', 'rm', '-f', ...idList]).exited
93
+ await this.runtime.spawn(['docker', 'rm', '-f', ...idList]).exited
91
94
  }
92
95
  }
93
96
 
94
97
  streamLogs(containerId: string, onData: (data: string) => void): void {
95
- const proc = Bun.spawn(['docker', 'logs', '-f', containerId], {
98
+ const proc = this.runtime.spawn(['docker', 'logs', '-f', containerId], {
96
99
  stdout: 'pipe',
97
100
  stderr: 'pipe',
98
101
  })
99
- const read = async (stream: ReadableStream) => {
102
+ const read = async (stream?: ReadableStream | null) => {
103
+ if (!stream) {
104
+ return
105
+ }
100
106
  const reader = stream.getReader()
101
107
  const decoder = new TextDecoder()
102
108
  while (true) {
@@ -112,7 +118,7 @@ export class DockerAdapter implements IDockerAdapter {
112
118
  }
113
119
 
114
120
  async getStats(containerId: string): Promise<{ cpu: string; memory: string }> {
115
- const proc = Bun.spawn([
121
+ const proc = this.runtime.spawn([
116
122
  'docker',
117
123
  'stats',
118
124
  containerId,
@@ -120,7 +126,7 @@ export class DockerAdapter implements IDockerAdapter {
120
126
  '--format',
121
127
  '{{.CPUPerc}},{{.MemUsage}}',
122
128
  ])
123
- const stdout = await new Response(proc.stdout).text()
129
+ const stdout = await new Response(proc.stdout ?? null).text()
124
130
  const [cpu, memory] = stdout.trim().split(',')
125
131
  return { cpu: cpu || '0%', memory: memory || '0B / 0B' }
126
132
  }
@@ -1,4 +1,5 @@
1
1
  import { mkdir } from 'node:fs/promises'
2
+ import { getRuntimeAdapter } from '@gravito/core'
2
3
  import type { IGitAdapter } from '../../Domain/Interfaces'
3
4
 
4
5
  export class ShellGitAdapter implements IGitAdapter {
@@ -10,10 +11,20 @@ export class ShellGitAdapter implements IGitAdapter {
10
11
 
11
12
  await mkdir(this.baseDir, { recursive: true })
12
13
 
13
- const proc = Bun.spawn(['git', 'clone', '--depth', '1', '--branch', branch, repoUrl, targetDir])
14
+ const runtime = getRuntimeAdapter()
15
+ const proc = runtime.spawn([
16
+ 'git',
17
+ 'clone',
18
+ '--depth',
19
+ '1',
20
+ '--branch',
21
+ branch,
22
+ repoUrl,
23
+ targetDir,
24
+ ])
14
25
 
15
- await proc.exited
16
- if (proc.exitCode !== 0) {
26
+ const exitCode = await proc.exited
27
+ if (exitCode !== 0) {
17
28
  throw new Error('Git Clone Failed')
18
29
  }
19
30
 
@@ -1,4 +1,4 @@
1
- import type { CacheService } from 'gravito-core'
1
+ import type { CacheService } from '@gravito/core'
2
2
  import type { IRocketRepository } from '../../Domain/Interfaces'
3
3
  import { Rocket } from '../../Domain/Rocket'
4
4
 
@@ -1,3 +1,4 @@
1
+ import { getRuntimeAdapter } from '@gravito/core'
1
2
  import type { IRouterAdapter } from '../../Domain/Interfaces'
2
3
 
3
4
  export class BunProxyAdapter implements IRouterAdapter {
@@ -15,8 +16,9 @@ export class BunProxyAdapter implements IRouterAdapter {
15
16
 
16
17
  start(port: number): void {
17
18
  const self = this
19
+ const runtime = getRuntimeAdapter()
18
20
 
19
- Bun.serve({
21
+ runtime.serve({
20
22
  port,
21
23
  async fetch(request) {
22
24
  const host = request.headers.get('host')?.split(':')[0]?.toLowerCase()
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
+ import { type Container, type GravitoOrbit, PlanetCore, ServiceProvider } from '@gravito/core'
1
2
  import { OrbitRipple } from '@gravito/ripple'
2
3
  import { OrbitCache } from '@gravito/stasis'
3
- import { type Container, type GravitoOrbit, PlanetCore, ServiceProvider } from 'gravito-core'
4
4
  import { MissionControl } from './Application/MissionControl'
5
5
  import { PayloadInjector } from './Application/PayloadInjector'
6
6
  import { PoolManager } from './Application/PoolManager'
@@ -16,7 +16,7 @@ describe('LaunchPad 集成測試 (真實 Docker)', () => {
16
16
  }
17
17
  })
18
18
 
19
- it('應該能成功熱機並在容器內執行指令', async () => {
19
+ it('應該能成功熱機並在容器內執行指令', { timeout: 20_000 }, async () => {
20
20
  try {
21
21
  // 1. Warmup
22
22
  await manager.warmup(1)
@@ -46,4 +46,39 @@ describe('Rocket', () => {
46
46
  rocket.decommission()
47
47
  expect(rocket.status).toBe(RocketStatus.DECOMMISSIONED)
48
48
  })
49
+
50
+ test('assigns domain and serializes/deserializes', () => {
51
+ const mission = Mission.create({
52
+ id: 'mission-2',
53
+ repoUrl: 'https://example.com/repo.git',
54
+ branch: 'main',
55
+ commitSha: 'def456',
56
+ })
57
+ const rocket = new Rocket('rocket-3', 'container-3')
58
+
59
+ rocket.assignDomain('rocket-3.dev.local')
60
+ rocket.assignMission(mission)
61
+
62
+ const snapshot = rocket.toJSON()
63
+ const restored = Rocket.fromJSON(snapshot)
64
+
65
+ expect(rocket.containerId).toBe('container-3')
66
+ expect(rocket.assignedDomain).toBe('rocket-3.dev.local')
67
+ expect(restored.status).toBe(RocketStatus.PREPARING)
68
+ expect(restored.currentMission).toBe(mission)
69
+ expect(restored.assignedDomain).toBe('rocket-3.dev.local')
70
+ })
71
+
72
+ test('rejects assigning mission when not idle', () => {
73
+ const mission = Mission.create({
74
+ id: 'mission-3',
75
+ repoUrl: 'https://example.com/repo.git',
76
+ branch: 'main',
77
+ commitSha: 'ghi789',
78
+ })
79
+ const rocket = new Rocket('rocket-4', 'container-4')
80
+
81
+ rocket.assignMission(mission)
82
+ expect(() => rocket.assignMission(mission)).toThrow('非 IDLE')
83
+ })
49
84
  })
@@ -8,7 +8,7 @@ function makeProcess(stdoutText: string, stderrText: string, exitCode = 0) {
8
8
  stdout: makeStream(stdoutText),
9
9
  stderr: makeStream(stderrText),
10
10
  exitCode,
11
- exited: Promise.resolve(),
11
+ exited: Promise.resolve(exitCode),
12
12
  }
13
13
  }
14
14
 
@@ -28,6 +28,20 @@ describe('DockerAdapter', () => {
28
28
  }
29
29
  })
30
30
 
31
+ test('returns container id even when exit code is zero but id is invalid', async () => {
32
+ const adapter = new DockerAdapter()
33
+ const originalSpawn = Bun.spawn
34
+
35
+ Bun.spawn = () => makeProcess('short-id', '', 0) as any
36
+
37
+ try {
38
+ const result = await adapter.createBaseContainer()
39
+ expect(result).toBe('short-id')
40
+ } finally {
41
+ Bun.spawn = originalSpawn
42
+ }
43
+ })
44
+
31
45
  test('throws when container creation fails', async () => {
32
46
  const adapter = new DockerAdapter()
33
47
  const originalSpawn = Bun.spawn
@@ -41,6 +55,46 @@ describe('DockerAdapter', () => {
41
55
  }
42
56
  })
43
57
 
58
+ test('getExposedPort parses the first line', async () => {
59
+ const adapter = new DockerAdapter()
60
+ const originalSpawn = Bun.spawn
61
+
62
+ Bun.spawn = () => makeProcess('0.0.0.0:32768\n[::]:32768\n', '', 0) as any
63
+
64
+ try {
65
+ const port = await adapter.getExposedPort('cid')
66
+ expect(port).toBe(32768)
67
+ } finally {
68
+ Bun.spawn = originalSpawn
69
+ }
70
+ })
71
+
72
+ test('getExposedPort throws on empty output', async () => {
73
+ const adapter = new DockerAdapter()
74
+ const originalSpawn = Bun.spawn
75
+
76
+ Bun.spawn = () => makeProcess('', '', 0) as any
77
+
78
+ try {
79
+ await expect(adapter.getExposedPort('cid')).rejects.toThrow('無法獲取容器映射端口')
80
+ } finally {
81
+ Bun.spawn = originalSpawn
82
+ }
83
+ })
84
+
85
+ test('getExposedPort throws on invalid output', async () => {
86
+ const adapter = new DockerAdapter()
87
+ const originalSpawn = Bun.spawn
88
+
89
+ Bun.spawn = () => makeProcess('nonsense', '', 0) as any
90
+
91
+ try {
92
+ await expect(adapter.getExposedPort('cid')).rejects.toThrow('無法獲取容器映射端口')
93
+ } finally {
94
+ Bun.spawn = originalSpawn
95
+ }
96
+ })
97
+
44
98
  test('copyFiles throws on non-zero exit code', async () => {
45
99
  const adapter = new DockerAdapter()
46
100
  const originalSpawn = Bun.spawn
@@ -54,13 +108,37 @@ describe('DockerAdapter', () => {
54
108
  }
55
109
  })
56
110
 
57
- test('executeCommand returns stdout and stderr', async () => {
111
+ test('removeContainerByLabel removes containers when ids exist', async () => {
58
112
  const adapter = new DockerAdapter()
59
113
  const originalSpawn = Bun.spawn
114
+ const calls: string[][] = []
115
+
116
+ Bun.spawn = ((args: string[]) => {
117
+ calls.push(args)
118
+ if (args[1] === 'ps') {
119
+ return makeProcess('id-1\nid-2\n', '', 0) as any
120
+ }
121
+ return makeProcess('', '', 0) as any
122
+ }) as any
123
+
124
+ try {
125
+ await adapter.removeContainerByLabel('gravito-origin=launchpad')
126
+ const rmCall = calls.find((call) => call[1] === 'rm')
127
+ expect(rmCall).toBeTruthy()
128
+ expect(rmCall).toContain('id-1')
129
+ expect(rmCall).toContain('id-2')
130
+ } finally {
131
+ Bun.spawn = originalSpawn
132
+ }
133
+ })
134
+
135
+ test('executeCommand returns stdout and stderr', async () => {
136
+ const originalSpawn = Bun.spawn
60
137
 
61
138
  Bun.spawn = () => makeProcess('ok', 'warn', 0) as any
62
139
 
63
140
  try {
141
+ const adapter = new DockerAdapter()
64
142
  const result = await adapter.executeCommand('cid', ['echo', 'ok'])
65
143
  expect(result.stdout).toBe('ok')
66
144
  expect(result.stderr).toBe('warn')
@@ -70,6 +148,26 @@ describe('DockerAdapter', () => {
70
148
  }
71
149
  })
72
150
 
151
+ test('removeContainer executes docker rm', async () => {
152
+ const originalSpawn = Bun.spawn
153
+ const calls: string[][] = []
154
+
155
+ Bun.spawn = ((args: string[]) => {
156
+ calls.push(args)
157
+ return makeProcess('', '', 0) as any
158
+ }) as any
159
+
160
+ try {
161
+ const adapter = new DockerAdapter()
162
+ await adapter.removeContainer('cid-1')
163
+ const rmCall = calls.find((call) => call[1] === 'rm')
164
+ expect(rmCall).toBeTruthy()
165
+ expect(rmCall).toContain('cid-1')
166
+ } finally {
167
+ Bun.spawn = originalSpawn
168
+ }
169
+ })
170
+
73
171
  test('getStats parses cpu and memory output', async () => {
74
172
  const adapter = new DockerAdapter()
75
173
  const originalSpawn = Bun.spawn
@@ -61,4 +61,77 @@ describe('PayloadInjector', () => {
61
61
  expect(docker.commands.length).toBeGreaterThanOrEqual(4)
62
62
  expect(rocket.status).toBe(RocketStatus.ORBITING)
63
63
  })
64
+
65
+ test('throws when install fails', async () => {
66
+ const git = new FakeGit()
67
+ const docker = {
68
+ copyCalls: [] as Array<{ containerId: string; source: string; target: string }>,
69
+ commands: [] as string[][],
70
+ async copyFiles(containerId: string, source: string, target: string) {
71
+ this.copyCalls.push({ containerId, source, target })
72
+ },
73
+ async executeCommand(containerId: string, command: string[]) {
74
+ this.commands.push([containerId, ...command])
75
+ if (command[0] === 'bun' && command[1] === 'install') {
76
+ return { stdout: '', stderr: 'boom', exitCode: 1 }
77
+ }
78
+ return { stdout: '', stderr: '', exitCode: 0 }
79
+ },
80
+ }
81
+
82
+ const injector = new PayloadInjector(docker as any, git as any)
83
+ const rocket = new Rocket('rocket-11', 'container-11')
84
+ const mission = Mission.create({
85
+ id: 'mission-11',
86
+ repoUrl: 'https://example.com/repo.git',
87
+ branch: 'main',
88
+ commitSha: 'abc999',
89
+ })
90
+ rocket.assignMission(mission)
91
+
92
+ await expect(injector.deploy(rocket)).rejects.toThrow('安裝依賴失敗')
93
+ })
94
+
95
+ test('logs when run command fails but still ignites', async () => {
96
+ const docker = {
97
+ copyCalls: [] as Array<{ containerId: string; source: string; target: string }>,
98
+ commands: [] as string[][],
99
+ async copyFiles(containerId: string, source: string, target: string) {
100
+ this.copyCalls.push({ containerId, source, target })
101
+ },
102
+ async executeCommand(containerId: string, command: string[]) {
103
+ this.commands.push([containerId, ...command])
104
+ if (command[0] === 'bun' && command[1] === 'run') {
105
+ throw new Error('boom')
106
+ }
107
+ return { stdout: '', stderr: '', exitCode: 0 }
108
+ },
109
+ }
110
+ const git = new FakeGit()
111
+ const injector = new PayloadInjector(docker as any, git as any)
112
+ const rocket = new Rocket('rocket-12', 'container-12')
113
+ const mission = Mission.create({
114
+ id: 'mission-12',
115
+ repoUrl: 'https://example.com/repo.git',
116
+ branch: 'main',
117
+ commitSha: 'abc888',
118
+ })
119
+ rocket.assignMission(mission)
120
+
121
+ const originalError = console.error
122
+ const errorCalls: string[] = []
123
+ console.error = (...args: any[]) => {
124
+ errorCalls.push(args.join(' '))
125
+ }
126
+
127
+ try {
128
+ await injector.deploy(rocket)
129
+ await new Promise((resolve) => setTimeout(resolve, 0))
130
+ } finally {
131
+ console.error = originalError
132
+ }
133
+
134
+ expect(errorCalls.join(' ')).toContain('運行異常')
135
+ expect(rocket.status).toBe(RocketStatus.ORBITING)
136
+ })
64
137
  })
package/tsconfig.json CHANGED
@@ -1,8 +1,27 @@
1
1
  {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist"
5
- },
6
- "include": ["src/**/*"],
7
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
8
- }
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "skipLibCheck": true,
6
+ "types": [
7
+ "bun-types"
8
+ ],
9
+ "baseUrl": ".",
10
+ "paths": {
11
+ "@gravito/core": [
12
+ "../../packages/core/src/index.ts"
13
+ ],
14
+ "@gravito/*": [
15
+ "../../packages/*/src/index.ts"
16
+ ]
17
+ }
18
+ },
19
+ "include": [
20
+ "src/**/*"
21
+ ],
22
+ "exclude": [
23
+ "node_modules",
24
+ "dist",
25
+ "**/*.test.ts"
26
+ ]
27
+ }
@@ -1,82 +0,0 @@
1
- $ bun test
2
- bun test v1.3.4 (5eb2145b)
3
-
4
- tests/mission.test.ts:
5
- (pass) Mission > exposes mission properties [0.18ms]
6
-
7
- tests/Telemetry.test.ts:
8
- [MissionControl] 準備發射任務: pr-1
9
- [MissionControl] 任務 pr-1 映射端口: 3000
10
- (pass) MissionControl Telemetry > 發射時應該能正確啟動日誌串流與效能監控 [1.22ms]
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 [0.73ms]
17
-
18
- tests/pool-manager.test.ts:
19
- [LaunchPad] 正在熱機,準備發射 2 架新火箭...
20
- [LaunchPad] 正在熱機,準備發射 1 架新火箭...
21
- (pass) PoolManager > warms up the pool with base containers [1.12ms]
22
- [LaunchPad] 資源吃緊,正在緊急呼叫後援火箭...
23
- [LaunchPad] 正在熱機,準備發射 1 架新火箭...
24
- (pass) PoolManager > assigns missions to idle rockets or creates new ones [1.55ms]
25
- (pass) PoolManager > recycles rockets through refurbish flow [0.77ms]
26
-
27
- tests/enterprise-coverage.test.ts:
28
- (pass) Launchpad enterprise coverage > covers core enterprise primitives [0.46ms]
29
-
30
- tests/repository.test.ts:
31
- (pass) InMemoryRocketRepository > stores and retrieves rockets [0.27ms]
32
- (pass) InMemoryRocketRepository > finds idle rockets [0.26ms]
33
-
34
- tests/Rocket.test.ts:
35
- (pass) Rocket > transitions through mission lifecycle and emits events [0.20ms]
36
- (pass) Rocket > guards invalid transitions [0.22ms]
37
-
38
- tests/Refurbishment.test.ts:
39
- [RefurbishUnit] 正在翻新火箭: r1 (容器: c1)
40
- [RefurbishUnit] 火箭 r1 翻新完成,已進入 IDLE 狀態。
41
- (pass) RefurbishUnit (Rocket Recovery) > 應該能執行深度清理並將火箭重置為 IDLE [0.63ms]
42
- [RefurbishUnit] 正在翻新火箭: r2 (容器: c2)
43
- [RefurbishUnit] 清理失敗: Disk Full
44
- (pass) RefurbishUnit (Rocket Recovery) > 如果清理失敗應該將火箭除役 [0.19ms]
45
-
46
- tests/payload-injector.test.ts:
47
- [PayloadInjector] 正在拉取代碼: https://example.com/repo.git (main)
48
- (pass) PayloadInjector > throws when rocket has no mission [1.06ms]
49
- [PayloadInjector] 正在注入載荷至容器: container-10
50
- [PayloadInjector] 正在安裝依賴...
51
- [PayloadInjector] 點火!
52
- (pass) PayloadInjector > deploys payload and ignites rocket [0.94ms]
53
-
54
- tests/docker-adapter.test.ts:
55
- (pass) DockerAdapter > creates base container when stdout has container id [4.59ms]
56
- (pass) DockerAdapter > throws when container creation fails [0.28ms]
57
- (pass) DockerAdapter > copyFiles throws on non-zero exit code [0.26ms]
58
- (pass) DockerAdapter > executeCommand returns stdout and stderr [0.22ms]
59
- (pass) DockerAdapter > getStats parses cpu and memory output [0.13ms]
60
- (pass) DockerAdapter > streamLogs forwards stdout and stderr [3.67ms]
61
-
62
- tests/Integration.test.ts:
63
- [LaunchPad] 正在熱機,準備發射 1 架新火箭...
64
- [Test] 容器內 Bun 版本: 1.0.36
65
- (pass) LaunchPad 集成測試 (真實 Docker) > 應該能成功熱機並在容器內執行指令 [829.72ms]
66
-
67
- tests/Deployment.test.ts:
68
- [LaunchPad] 資源吃緊,正在緊急呼叫後援火箭...
69
- [PayloadInjector] 正在拉取代碼: http://git (dev)
70
- [PayloadInjector] 正在注入載荷至容器: cid-1
71
- [PayloadInjector] 正在安裝依賴...
72
- [PayloadInjector] 點火!
73
- (pass) Payload Injection Flow > 應該能從指派任務到成功點火 [0.56ms]
74
-
75
- tests/PoolManager.test.ts:
76
- [LaunchPad] 正在熱機,準備發射 2 架新火箭...
77
- (pass) PoolManager (Application Service) > 應該能正確熱機並指派任務 [0.56ms]
78
-
79
- 24 pass
80
- 0 fail
81
- 81 expect() calls
82
- Ran 24 tests across 13 files. [1174.00ms]