@gravito/launchpad 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/debug-launch.ts +4 -1
- package/dist/index.d.mts +76 -2
- package/dist/index.d.ts +76 -2
- package/dist/index.js +14 -13
- package/dist/index.mjs +14 -13
- package/package.json +1 -1
- package/src/Application/MissionControl.ts +9 -0
- package/src/Application/PayloadInjector.ts +9 -0
- package/src/Application/PoolManager.ts +10 -1
- package/src/Application/RefurbishUnit.ts +10 -0
- package/src/Domain/Events.ts +24 -0
- package/src/Domain/Mission.ts +9 -0
- package/src/Domain/Rocket.ts +10 -0
- package/src/Infrastructure/Docker/DockerAdapter.ts +10 -1
- package/src/Infrastructure/Git/ShellGitAdapter.ts +10 -1
- package/src/Infrastructure/GitHub/OctokitGitHubAdapter.ts +9 -0
- package/src/Infrastructure/Persistence/CachedRocketRepository.ts +9 -0
- package/src/Infrastructure/Persistence/InMemoryRocketRepository.ts +9 -0
- package/src/Infrastructure/Router/BunProxyAdapter.ts +9 -0
- package/src/index.ts +24 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @gravito/launchpad
|
|
2
2
|
|
|
3
|
+
## 1.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 905588f: fix: replace insecure Math.random() with crypto.randomUUID() for ID and temporary path generation (CWE-330)
|
|
8
|
+
|
|
9
|
+
## 1.2.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies
|
|
14
|
+
- @gravito/core@1.2.1
|
|
15
|
+
- @gravito/enterprise@1.0.3
|
|
16
|
+
- @gravito/ripple@3.0.1
|
|
17
|
+
- @gravito/stasis@3.0.1
|
|
18
|
+
|
|
3
19
|
## 1.2.0
|
|
4
20
|
|
|
5
21
|
### Minor Changes
|
package/debug-launch.ts
CHANGED
|
@@ -21,7 +21,10 @@ async function run() {
|
|
|
21
21
|
console.log('🧹 Cleaning up old containers...')
|
|
22
22
|
await runtime.spawn(['docker', 'rm', '-f', ...containers.trim().split('\n')]).exited
|
|
23
23
|
}
|
|
24
|
-
} catch
|
|
24
|
+
} catch {
|
|
25
|
+
// Intentionally ignored: Container cleanup is best-effort.
|
|
26
|
+
// Failure could mean containers don't exist or Docker is unavailable, which is acceptable.
|
|
27
|
+
}
|
|
25
28
|
|
|
26
29
|
// 2. Start Server
|
|
27
30
|
const config = await bootstrapLaunchpad()
|
package/dist/index.d.mts
CHANGED
|
@@ -9,6 +9,15 @@ interface MissionProps {
|
|
|
9
9
|
branch: string;
|
|
10
10
|
commitSha: string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Mission represents a deployment task for a specific code version.
|
|
14
|
+
*
|
|
15
|
+
* It is a Value Object containing information about the source repository,
|
|
16
|
+
* branch, and commit to be deployed.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
* @since 3.0.0
|
|
20
|
+
*/
|
|
12
21
|
declare class Mission extends ValueObject<MissionProps> {
|
|
13
22
|
get id(): string;
|
|
14
23
|
get repoUrl(): string;
|
|
@@ -28,6 +37,16 @@ declare enum RocketStatus {
|
|
|
28
37
|
DECOMMISSIONED = "DECOMMISSIONED"
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Rocket represents a managed application container within Launchpad.
|
|
42
|
+
*
|
|
43
|
+
* It acts as an Aggregate Root in the domain, maintaining its lifecycle state
|
|
44
|
+
* (IDLE, PREPARING, ORBITING, etc.) and emitting domain events when
|
|
45
|
+
* state transitions occur.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
* @since 3.0.0
|
|
49
|
+
*/
|
|
31
50
|
declare class Rocket extends AggregateRoot<string> {
|
|
32
51
|
private _status;
|
|
33
52
|
private _currentMission;
|
|
@@ -140,6 +159,15 @@ interface IRocketRepository {
|
|
|
140
159
|
delete(id: string): Promise<void>;
|
|
141
160
|
}
|
|
142
161
|
|
|
162
|
+
/**
|
|
163
|
+
* PayloadInjector is responsible for deploying application code into Rocket containers.
|
|
164
|
+
*
|
|
165
|
+
* It clones the repository, copies files into the container, installs
|
|
166
|
+
* dependencies (using Bun), and starts the application.
|
|
167
|
+
*
|
|
168
|
+
* @public
|
|
169
|
+
* @since 3.0.0
|
|
170
|
+
*/
|
|
143
171
|
declare class PayloadInjector {
|
|
144
172
|
private docker;
|
|
145
173
|
private git;
|
|
@@ -147,6 +175,16 @@ declare class PayloadInjector {
|
|
|
147
175
|
deploy(rocket: Rocket): Promise<void>;
|
|
148
176
|
}
|
|
149
177
|
|
|
178
|
+
/**
|
|
179
|
+
* RefurbishUnit handles the cleaning and restoration of Rocket containers.
|
|
180
|
+
*
|
|
181
|
+
* After a mission is complete, this unit resets the container environment
|
|
182
|
+
* (clearing files, stopping processes) so the container can be returned
|
|
183
|
+
* to the idle pool for future reuse.
|
|
184
|
+
*
|
|
185
|
+
* @public
|
|
186
|
+
* @since 3.0.0
|
|
187
|
+
*/
|
|
150
188
|
declare class RefurbishUnit {
|
|
151
189
|
private docker;
|
|
152
190
|
constructor(docker: IDockerAdapter);
|
|
@@ -156,6 +194,15 @@ declare class RefurbishUnit {
|
|
|
156
194
|
refurbish(rocket: Rocket): Promise<void>;
|
|
157
195
|
}
|
|
158
196
|
|
|
197
|
+
/**
|
|
198
|
+
* PoolManager handles the lifecycle and pooling of Rocket containers.
|
|
199
|
+
*
|
|
200
|
+
* It manages a pool of pre-warmed containers to ensure fast deployment of
|
|
201
|
+
* new missions. It also handles rocket assignment and recycling (refurbishment).
|
|
202
|
+
*
|
|
203
|
+
* @public
|
|
204
|
+
* @since 3.0.0
|
|
205
|
+
*/
|
|
159
206
|
declare class PoolManager {
|
|
160
207
|
private dockerAdapter;
|
|
161
208
|
private rocketRepository;
|
|
@@ -176,6 +223,15 @@ declare class PoolManager {
|
|
|
176
223
|
recycle(missionId: string): Promise<void>;
|
|
177
224
|
}
|
|
178
225
|
|
|
226
|
+
/**
|
|
227
|
+
* MissionControl orchestrates the high-level process of launching missions.
|
|
228
|
+
*
|
|
229
|
+
* it coordinates between the `PoolManager`, `PayloadInjector`, and `DockerAdapter`
|
|
230
|
+
* to deploy code into containers, assign domains, and monitor execution.
|
|
231
|
+
*
|
|
232
|
+
* @public
|
|
233
|
+
* @since 3.0.0
|
|
234
|
+
*/
|
|
179
235
|
declare class MissionControl {
|
|
180
236
|
private poolManager;
|
|
181
237
|
private injector;
|
|
@@ -186,15 +242,33 @@ declare class MissionControl {
|
|
|
186
242
|
}
|
|
187
243
|
|
|
188
244
|
/**
|
|
189
|
-
*
|
|
245
|
+
* LaunchpadOrbit provides automated deployment and preview capabilities for Gravito.
|
|
246
|
+
* It integrates with Docker and GitHub to provide "Preview Deployments" for Pull Requests.
|
|
247
|
+
*
|
|
248
|
+
* @public
|
|
190
249
|
*/
|
|
191
250
|
declare class LaunchpadOrbit implements GravitoOrbit {
|
|
192
251
|
private ripple;
|
|
252
|
+
/**
|
|
253
|
+
* Create a new LaunchpadOrbit instance.
|
|
254
|
+
* @param ripple - Ripple instance for real-time telemetry communication.
|
|
255
|
+
*/
|
|
193
256
|
constructor(ripple: OrbitRipple);
|
|
257
|
+
/**
|
|
258
|
+
* Install the Launchpad orbit into PlanetCore.
|
|
259
|
+
* Registers the service provider and sets up webhook routes for deployment.
|
|
260
|
+
*
|
|
261
|
+
* @param core - The PlanetCore instance.
|
|
262
|
+
*/
|
|
194
263
|
install(core: PlanetCore): Promise<void>;
|
|
195
264
|
}
|
|
196
265
|
/**
|
|
197
|
-
*
|
|
266
|
+
* Bootstrap the Launchpad application.
|
|
267
|
+
* Initializes PlanetCore with necessary orbits (Cache, Ripple, Launchpad)
|
|
268
|
+
* and returns the configuration for the server entry point.
|
|
269
|
+
*
|
|
270
|
+
* @returns Server configuration object including port and fetch handler.
|
|
271
|
+
* @public
|
|
198
272
|
*/
|
|
199
273
|
declare function bootstrapLaunchpad(): Promise<{
|
|
200
274
|
port: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ interface MissionProps {
|
|
|
9
9
|
branch: string;
|
|
10
10
|
commitSha: string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Mission represents a deployment task for a specific code version.
|
|
14
|
+
*
|
|
15
|
+
* It is a Value Object containing information about the source repository,
|
|
16
|
+
* branch, and commit to be deployed.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
* @since 3.0.0
|
|
20
|
+
*/
|
|
12
21
|
declare class Mission extends ValueObject<MissionProps> {
|
|
13
22
|
get id(): string;
|
|
14
23
|
get repoUrl(): string;
|
|
@@ -28,6 +37,16 @@ declare enum RocketStatus {
|
|
|
28
37
|
DECOMMISSIONED = "DECOMMISSIONED"
|
|
29
38
|
}
|
|
30
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Rocket represents a managed application container within Launchpad.
|
|
42
|
+
*
|
|
43
|
+
* It acts as an Aggregate Root in the domain, maintaining its lifecycle state
|
|
44
|
+
* (IDLE, PREPARING, ORBITING, etc.) and emitting domain events when
|
|
45
|
+
* state transitions occur.
|
|
46
|
+
*
|
|
47
|
+
* @public
|
|
48
|
+
* @since 3.0.0
|
|
49
|
+
*/
|
|
31
50
|
declare class Rocket extends AggregateRoot<string> {
|
|
32
51
|
private _status;
|
|
33
52
|
private _currentMission;
|
|
@@ -140,6 +159,15 @@ interface IRocketRepository {
|
|
|
140
159
|
delete(id: string): Promise<void>;
|
|
141
160
|
}
|
|
142
161
|
|
|
162
|
+
/**
|
|
163
|
+
* PayloadInjector is responsible for deploying application code into Rocket containers.
|
|
164
|
+
*
|
|
165
|
+
* It clones the repository, copies files into the container, installs
|
|
166
|
+
* dependencies (using Bun), and starts the application.
|
|
167
|
+
*
|
|
168
|
+
* @public
|
|
169
|
+
* @since 3.0.0
|
|
170
|
+
*/
|
|
143
171
|
declare class PayloadInjector {
|
|
144
172
|
private docker;
|
|
145
173
|
private git;
|
|
@@ -147,6 +175,16 @@ declare class PayloadInjector {
|
|
|
147
175
|
deploy(rocket: Rocket): Promise<void>;
|
|
148
176
|
}
|
|
149
177
|
|
|
178
|
+
/**
|
|
179
|
+
* RefurbishUnit handles the cleaning and restoration of Rocket containers.
|
|
180
|
+
*
|
|
181
|
+
* After a mission is complete, this unit resets the container environment
|
|
182
|
+
* (clearing files, stopping processes) so the container can be returned
|
|
183
|
+
* to the idle pool for future reuse.
|
|
184
|
+
*
|
|
185
|
+
* @public
|
|
186
|
+
* @since 3.0.0
|
|
187
|
+
*/
|
|
150
188
|
declare class RefurbishUnit {
|
|
151
189
|
private docker;
|
|
152
190
|
constructor(docker: IDockerAdapter);
|
|
@@ -156,6 +194,15 @@ declare class RefurbishUnit {
|
|
|
156
194
|
refurbish(rocket: Rocket): Promise<void>;
|
|
157
195
|
}
|
|
158
196
|
|
|
197
|
+
/**
|
|
198
|
+
* PoolManager handles the lifecycle and pooling of Rocket containers.
|
|
199
|
+
*
|
|
200
|
+
* It manages a pool of pre-warmed containers to ensure fast deployment of
|
|
201
|
+
* new missions. It also handles rocket assignment and recycling (refurbishment).
|
|
202
|
+
*
|
|
203
|
+
* @public
|
|
204
|
+
* @since 3.0.0
|
|
205
|
+
*/
|
|
159
206
|
declare class PoolManager {
|
|
160
207
|
private dockerAdapter;
|
|
161
208
|
private rocketRepository;
|
|
@@ -176,6 +223,15 @@ declare class PoolManager {
|
|
|
176
223
|
recycle(missionId: string): Promise<void>;
|
|
177
224
|
}
|
|
178
225
|
|
|
226
|
+
/**
|
|
227
|
+
* MissionControl orchestrates the high-level process of launching missions.
|
|
228
|
+
*
|
|
229
|
+
* it coordinates between the `PoolManager`, `PayloadInjector`, and `DockerAdapter`
|
|
230
|
+
* to deploy code into containers, assign domains, and monitor execution.
|
|
231
|
+
*
|
|
232
|
+
* @public
|
|
233
|
+
* @since 3.0.0
|
|
234
|
+
*/
|
|
179
235
|
declare class MissionControl {
|
|
180
236
|
private poolManager;
|
|
181
237
|
private injector;
|
|
@@ -186,15 +242,33 @@ declare class MissionControl {
|
|
|
186
242
|
}
|
|
187
243
|
|
|
188
244
|
/**
|
|
189
|
-
*
|
|
245
|
+
* LaunchpadOrbit provides automated deployment and preview capabilities for Gravito.
|
|
246
|
+
* It integrates with Docker and GitHub to provide "Preview Deployments" for Pull Requests.
|
|
247
|
+
*
|
|
248
|
+
* @public
|
|
190
249
|
*/
|
|
191
250
|
declare class LaunchpadOrbit implements GravitoOrbit {
|
|
192
251
|
private ripple;
|
|
252
|
+
/**
|
|
253
|
+
* Create a new LaunchpadOrbit instance.
|
|
254
|
+
* @param ripple - Ripple instance for real-time telemetry communication.
|
|
255
|
+
*/
|
|
193
256
|
constructor(ripple: OrbitRipple);
|
|
257
|
+
/**
|
|
258
|
+
* Install the Launchpad orbit into PlanetCore.
|
|
259
|
+
* Registers the service provider and sets up webhook routes for deployment.
|
|
260
|
+
*
|
|
261
|
+
* @param core - The PlanetCore instance.
|
|
262
|
+
*/
|
|
194
263
|
install(core: PlanetCore): Promise<void>;
|
|
195
264
|
}
|
|
196
265
|
/**
|
|
197
|
-
*
|
|
266
|
+
* Bootstrap the Launchpad application.
|
|
267
|
+
* Initializes PlanetCore with necessary orbits (Cache, Ripple, Launchpad)
|
|
268
|
+
* and returns the configuration for the server entry point.
|
|
269
|
+
*
|
|
270
|
+
* @returns Server configuration object including port and fetch handler.
|
|
271
|
+
* @public
|
|
198
272
|
*/
|
|
199
273
|
declare function bootstrapLaunchpad(): Promise<{
|
|
200
274
|
port: number;
|
package/dist/index.js
CHANGED
|
@@ -298,7 +298,7 @@ var PoolManager = class {
|
|
|
298
298
|
console.log(`[LaunchPad] \u6B63\u5728\u71B1\u6A5F\uFF0C\u6E96\u5099\u767C\u5C04 ${needed} \u67B6\u65B0\u706B\u7BAD...`);
|
|
299
299
|
for (let i = 0; i < needed; i++) {
|
|
300
300
|
const containerId = await this.dockerAdapter.createBaseContainer();
|
|
301
|
-
const rocketId = `rocket-${
|
|
301
|
+
const rocketId = `rocket-${crypto.randomUUID()}`;
|
|
302
302
|
const rocket = new Rocket(rocketId, containerId);
|
|
303
303
|
await this.rocketRepository.save(rocket);
|
|
304
304
|
}
|
|
@@ -394,7 +394,7 @@ var DockerAdapter = class {
|
|
|
394
394
|
baseImage = "oven/bun:1.0-slim";
|
|
395
395
|
runtime = (0, import_core.getRuntimeAdapter)();
|
|
396
396
|
async createBaseContainer() {
|
|
397
|
-
const rocketId = `rocket-${
|
|
397
|
+
const rocketId = `rocket-${crypto.randomUUID()}`;
|
|
398
398
|
const proc = this.runtime.spawn([
|
|
399
399
|
"docker",
|
|
400
400
|
"run",
|
|
@@ -509,7 +509,7 @@ var import_core2 = require("@gravito/core");
|
|
|
509
509
|
var ShellGitAdapter = class {
|
|
510
510
|
baseDir = "/tmp/gravito-launchpad-git";
|
|
511
511
|
async clone(repoUrl, branch) {
|
|
512
|
-
const dirName = `${Date.now()}-${
|
|
512
|
+
const dirName = `${Date.now()}-${crypto.randomUUID()}`;
|
|
513
513
|
const targetDir = `${this.baseDir}/${dirName}`;
|
|
514
514
|
await (0, import_promises.mkdir)(this.baseDir, { recursive: true });
|
|
515
515
|
const runtime = (0, import_core2.getRuntimeAdapter)();
|
|
@@ -648,10 +648,6 @@ var BunProxyAdapter = class {
|
|
|
648
648
|
var LaunchpadServiceProvider = class extends import_core4.ServiceProvider {
|
|
649
649
|
register(container) {
|
|
650
650
|
if (!container.has("cache")) {
|
|
651
|
-
const cacheFromServices = this.core?.services?.get("cache");
|
|
652
|
-
if (cacheFromServices) {
|
|
653
|
-
container.instance("cache", cacheFromServices);
|
|
654
|
-
}
|
|
655
651
|
}
|
|
656
652
|
container.singleton("launchpad.docker", () => new DockerAdapter());
|
|
657
653
|
container.singleton("launchpad.git", () => new ShellGitAdapter());
|
|
@@ -708,9 +704,19 @@ var LaunchpadServiceProvider = class extends import_core4.ServiceProvider {
|
|
|
708
704
|
}
|
|
709
705
|
};
|
|
710
706
|
var LaunchpadOrbit = class {
|
|
707
|
+
/**
|
|
708
|
+
* Create a new LaunchpadOrbit instance.
|
|
709
|
+
* @param ripple - Ripple instance for real-time telemetry communication.
|
|
710
|
+
*/
|
|
711
711
|
constructor(ripple) {
|
|
712
712
|
this.ripple = ripple;
|
|
713
713
|
}
|
|
714
|
+
/**
|
|
715
|
+
* Install the Launchpad orbit into PlanetCore.
|
|
716
|
+
* Registers the service provider and sets up webhook routes for deployment.
|
|
717
|
+
*
|
|
718
|
+
* @param core - The PlanetCore instance.
|
|
719
|
+
*/
|
|
714
720
|
async install(core) {
|
|
715
721
|
core.register(new LaunchpadServiceProvider());
|
|
716
722
|
core.router.post("/launch", async (c) => {
|
|
@@ -784,12 +790,7 @@ async function bootstrapLaunchpad() {
|
|
|
784
790
|
PORT: 4e3,
|
|
785
791
|
CACHE_DRIVER: "file"
|
|
786
792
|
},
|
|
787
|
-
orbits: [
|
|
788
|
-
new import_stasis.OrbitCache(),
|
|
789
|
-
ripple,
|
|
790
|
-
new LaunchpadOrbit(ripple)
|
|
791
|
-
// 傳入實例
|
|
792
|
-
]
|
|
793
|
+
orbits: [new import_stasis.OrbitCache(), ripple, new LaunchpadOrbit(ripple)]
|
|
793
794
|
});
|
|
794
795
|
await core.bootstrap();
|
|
795
796
|
const liftoffConfig = core.liftoff();
|
package/dist/index.mjs
CHANGED
|
@@ -266,7 +266,7 @@ var PoolManager = class {
|
|
|
266
266
|
console.log(`[LaunchPad] \u6B63\u5728\u71B1\u6A5F\uFF0C\u6E96\u5099\u767C\u5C04 ${needed} \u67B6\u65B0\u706B\u7BAD...`);
|
|
267
267
|
for (let i = 0; i < needed; i++) {
|
|
268
268
|
const containerId = await this.dockerAdapter.createBaseContainer();
|
|
269
|
-
const rocketId = `rocket-${
|
|
269
|
+
const rocketId = `rocket-${crypto.randomUUID()}`;
|
|
270
270
|
const rocket = new Rocket(rocketId, containerId);
|
|
271
271
|
await this.rocketRepository.save(rocket);
|
|
272
272
|
}
|
|
@@ -362,7 +362,7 @@ var DockerAdapter = class {
|
|
|
362
362
|
baseImage = "oven/bun:1.0-slim";
|
|
363
363
|
runtime = getRuntimeAdapter();
|
|
364
364
|
async createBaseContainer() {
|
|
365
|
-
const rocketId = `rocket-${
|
|
365
|
+
const rocketId = `rocket-${crypto.randomUUID()}`;
|
|
366
366
|
const proc = this.runtime.spawn([
|
|
367
367
|
"docker",
|
|
368
368
|
"run",
|
|
@@ -477,7 +477,7 @@ import { getRuntimeAdapter as getRuntimeAdapter2 } from "@gravito/core";
|
|
|
477
477
|
var ShellGitAdapter = class {
|
|
478
478
|
baseDir = "/tmp/gravito-launchpad-git";
|
|
479
479
|
async clone(repoUrl, branch) {
|
|
480
|
-
const dirName = `${Date.now()}-${
|
|
480
|
+
const dirName = `${Date.now()}-${crypto.randomUUID()}`;
|
|
481
481
|
const targetDir = `${this.baseDir}/${dirName}`;
|
|
482
482
|
await mkdir(this.baseDir, { recursive: true });
|
|
483
483
|
const runtime = getRuntimeAdapter2();
|
|
@@ -616,10 +616,6 @@ var BunProxyAdapter = class {
|
|
|
616
616
|
var LaunchpadServiceProvider = class extends ServiceProvider {
|
|
617
617
|
register(container) {
|
|
618
618
|
if (!container.has("cache")) {
|
|
619
|
-
const cacheFromServices = this.core?.services?.get("cache");
|
|
620
|
-
if (cacheFromServices) {
|
|
621
|
-
container.instance("cache", cacheFromServices);
|
|
622
|
-
}
|
|
623
619
|
}
|
|
624
620
|
container.singleton("launchpad.docker", () => new DockerAdapter());
|
|
625
621
|
container.singleton("launchpad.git", () => new ShellGitAdapter());
|
|
@@ -676,9 +672,19 @@ var LaunchpadServiceProvider = class extends ServiceProvider {
|
|
|
676
672
|
}
|
|
677
673
|
};
|
|
678
674
|
var LaunchpadOrbit = class {
|
|
675
|
+
/**
|
|
676
|
+
* Create a new LaunchpadOrbit instance.
|
|
677
|
+
* @param ripple - Ripple instance for real-time telemetry communication.
|
|
678
|
+
*/
|
|
679
679
|
constructor(ripple) {
|
|
680
680
|
this.ripple = ripple;
|
|
681
681
|
}
|
|
682
|
+
/**
|
|
683
|
+
* Install the Launchpad orbit into PlanetCore.
|
|
684
|
+
* Registers the service provider and sets up webhook routes for deployment.
|
|
685
|
+
*
|
|
686
|
+
* @param core - The PlanetCore instance.
|
|
687
|
+
*/
|
|
682
688
|
async install(core) {
|
|
683
689
|
core.register(new LaunchpadServiceProvider());
|
|
684
690
|
core.router.post("/launch", async (c) => {
|
|
@@ -752,12 +758,7 @@ async function bootstrapLaunchpad() {
|
|
|
752
758
|
PORT: 4e3,
|
|
753
759
|
CACHE_DRIVER: "file"
|
|
754
760
|
},
|
|
755
|
-
orbits: [
|
|
756
|
-
new OrbitCache(),
|
|
757
|
-
ripple,
|
|
758
|
-
new LaunchpadOrbit(ripple)
|
|
759
|
-
// 傳入實例
|
|
760
|
-
]
|
|
761
|
+
orbits: [new OrbitCache(), ripple, new LaunchpadOrbit(ripple)]
|
|
761
762
|
});
|
|
762
763
|
await core.bootstrap();
|
|
763
764
|
const liftoffConfig = core.liftoff();
|
package/package.json
CHANGED
|
@@ -3,6 +3,15 @@ import type { Mission } from '../Domain/Mission'
|
|
|
3
3
|
import type { PayloadInjector } from './PayloadInjector'
|
|
4
4
|
import type { PoolManager } from './PoolManager'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* MissionControl orchestrates the high-level process of launching missions.
|
|
8
|
+
*
|
|
9
|
+
* it coordinates between the `PoolManager`, `PayloadInjector`, and `DockerAdapter`
|
|
10
|
+
* to deploy code into containers, assign domains, and monitor execution.
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
* @since 3.0.0
|
|
14
|
+
*/
|
|
6
15
|
export class MissionControl {
|
|
7
16
|
constructor(
|
|
8
17
|
private poolManager: PoolManager,
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import type { IDockerAdapter, IGitAdapter } from '../Domain/Interfaces'
|
|
2
2
|
import type { Rocket } from '../Domain/Rocket'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* PayloadInjector is responsible for deploying application code into Rocket containers.
|
|
6
|
+
*
|
|
7
|
+
* It clones the repository, copies files into the container, installs
|
|
8
|
+
* dependencies (using Bun), and starts the application.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
* @since 3.0.0
|
|
12
|
+
*/
|
|
4
13
|
export class PayloadInjector {
|
|
5
14
|
constructor(
|
|
6
15
|
private docker: IDockerAdapter,
|
|
@@ -3,6 +3,15 @@ import type { Mission } from '../Domain/Mission'
|
|
|
3
3
|
import { Rocket } from '../Domain/Rocket'
|
|
4
4
|
import type { RefurbishUnit } from './RefurbishUnit'
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* PoolManager handles the lifecycle and pooling of Rocket containers.
|
|
8
|
+
*
|
|
9
|
+
* It manages a pool of pre-warmed containers to ensure fast deployment of
|
|
10
|
+
* new missions. It also handles rocket assignment and recycling (refurbishment).
|
|
11
|
+
*
|
|
12
|
+
* @public
|
|
13
|
+
* @since 3.0.0
|
|
14
|
+
*/
|
|
6
15
|
export class PoolManager {
|
|
7
16
|
constructor(
|
|
8
17
|
private dockerAdapter: IDockerAdapter,
|
|
@@ -26,7 +35,7 @@ export class PoolManager {
|
|
|
26
35
|
|
|
27
36
|
for (let i = 0; i < needed; i++) {
|
|
28
37
|
const containerId = await this.dockerAdapter.createBaseContainer()
|
|
29
|
-
const rocketId = `rocket-${
|
|
38
|
+
const rocketId = `rocket-${crypto.randomUUID()}`
|
|
30
39
|
const rocket = new Rocket(rocketId, containerId)
|
|
31
40
|
await this.rocketRepository.save(rocket)
|
|
32
41
|
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { IDockerAdapter } from '../Domain/Interfaces'
|
|
2
2
|
import type { Rocket } from '../Domain/Rocket'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* RefurbishUnit handles the cleaning and restoration of Rocket containers.
|
|
6
|
+
*
|
|
7
|
+
* After a mission is complete, this unit resets the container environment
|
|
8
|
+
* (clearing files, stopping processes) so the container can be returned
|
|
9
|
+
* to the idle pool for future reuse.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
* @since 3.0.0
|
|
13
|
+
*/
|
|
4
14
|
export class RefurbishUnit {
|
|
5
15
|
constructor(private docker: IDockerAdapter) {}
|
|
6
16
|
|
package/src/Domain/Events.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { DomainEvent } from '@gravito/enterprise'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Event emitted when a mission is assigned to a rocket.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
* @since 3.0.0
|
|
8
|
+
*/
|
|
3
9
|
export class MissionAssigned extends DomainEvent {
|
|
4
10
|
constructor(
|
|
5
11
|
public readonly rocketId: string,
|
|
@@ -9,18 +15,36 @@ export class MissionAssigned extends DomainEvent {
|
|
|
9
15
|
}
|
|
10
16
|
}
|
|
11
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Event emitted when a rocket starts its mission (application started).
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
* @since 3.0.0
|
|
23
|
+
*/
|
|
12
24
|
export class RocketIgnited extends DomainEvent {
|
|
13
25
|
constructor(public readonly rocketId: string) {
|
|
14
26
|
super()
|
|
15
27
|
}
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Event emitted when a rocket mission is completed or terminated.
|
|
32
|
+
*
|
|
33
|
+
* @public
|
|
34
|
+
* @since 3.0.0
|
|
35
|
+
*/
|
|
18
36
|
export class RocketSplashedDown extends DomainEvent {
|
|
19
37
|
constructor(public readonly rocketId: string) {
|
|
20
38
|
super()
|
|
21
39
|
}
|
|
22
40
|
}
|
|
23
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Event emitted when a rocket has finished the refurbishment process.
|
|
44
|
+
*
|
|
45
|
+
* @public
|
|
46
|
+
* @since 3.0.0
|
|
47
|
+
*/
|
|
24
48
|
export class RefurbishmentCompleted extends DomainEvent {
|
|
25
49
|
constructor(public readonly rocketId: string) {
|
|
26
50
|
super()
|
package/src/Domain/Mission.ts
CHANGED
|
@@ -7,6 +7,15 @@ interface MissionProps {
|
|
|
7
7
|
commitSha: string // Commit Hash
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Mission represents a deployment task for a specific code version.
|
|
12
|
+
*
|
|
13
|
+
* It is a Value Object containing information about the source repository,
|
|
14
|
+
* branch, and commit to be deployed.
|
|
15
|
+
*
|
|
16
|
+
* @public
|
|
17
|
+
* @since 3.0.0
|
|
18
|
+
*/
|
|
10
19
|
export class Mission extends ValueObject<MissionProps> {
|
|
11
20
|
get id() {
|
|
12
21
|
return this.props.id
|
package/src/Domain/Rocket.ts
CHANGED
|
@@ -8,6 +8,16 @@ import {
|
|
|
8
8
|
import type { Mission } from './Mission'
|
|
9
9
|
import { RocketStatus } from './RocketStatus'
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Rocket represents a managed application container within Launchpad.
|
|
13
|
+
*
|
|
14
|
+
* It acts as an Aggregate Root in the domain, maintaining its lifecycle state
|
|
15
|
+
* (IDLE, PREPARING, ORBITING, etc.) and emitting domain events when
|
|
16
|
+
* state transitions occur.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
* @since 3.0.0
|
|
20
|
+
*/
|
|
11
21
|
export class Rocket extends AggregateRoot<string> {
|
|
12
22
|
private _status: RocketStatus = RocketStatus.IDLE
|
|
13
23
|
private _currentMission: Mission | null = null
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { getRuntimeAdapter } from '@gravito/core'
|
|
2
2
|
import type { IDockerAdapter } from '../../Domain/Interfaces'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* DockerAdapter implements the `IDockerAdapter` interface using the Docker CLI.
|
|
6
|
+
*
|
|
7
|
+
* It manages the lifecycle of Docker containers used as "Rockets" in Launchpad,
|
|
8
|
+
* including creation, command execution, file copying, and metrics collection.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
* @since 3.0.0
|
|
12
|
+
*/
|
|
4
13
|
export class DockerAdapter implements IDockerAdapter {
|
|
5
14
|
private baseImage = 'oven/bun:1.0-slim'
|
|
6
15
|
private runtime = getRuntimeAdapter()
|
|
7
16
|
|
|
8
17
|
async createBaseContainer(): Promise<string> {
|
|
9
|
-
const rocketId = `rocket-${
|
|
18
|
+
const rocketId = `rocket-${crypto.randomUUID()}`
|
|
10
19
|
|
|
11
20
|
const proc = this.runtime.spawn([
|
|
12
21
|
'docker',
|
|
@@ -2,11 +2,20 @@ import { mkdir } from 'node:fs/promises'
|
|
|
2
2
|
import { getRuntimeAdapter } from '@gravito/core'
|
|
3
3
|
import type { IGitAdapter } from '../../Domain/Interfaces'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* ShellGitAdapter implements the `IGitAdapter` interface using the Git CLI.
|
|
7
|
+
*
|
|
8
|
+
* It provides methods for cloning repositories to a temporary directory
|
|
9
|
+
* for deployment into Rocket containers.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
* @since 3.0.0
|
|
13
|
+
*/
|
|
5
14
|
export class ShellGitAdapter implements IGitAdapter {
|
|
6
15
|
private baseDir = '/tmp/gravito-launchpad-git'
|
|
7
16
|
|
|
8
17
|
async clone(repoUrl: string, branch: string): Promise<string> {
|
|
9
|
-
const dirName = `${Date.now()}-${
|
|
18
|
+
const dirName = `${Date.now()}-${crypto.randomUUID()}`
|
|
10
19
|
const targetDir = `${this.baseDir}/${dirName}`
|
|
11
20
|
|
|
12
21
|
await mkdir(this.baseDir, { recursive: true })
|
|
@@ -2,6 +2,15 @@ import { createHmac, timingSafeEqual } from 'node:crypto'
|
|
|
2
2
|
import { Octokit } from '@octokit/rest'
|
|
3
3
|
import type { IGitHubAdapter } from '../../Domain/Interfaces'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* OctokitGitHubAdapter implements the `IGitHubAdapter` interface using the Octokit library.
|
|
7
|
+
*
|
|
8
|
+
* It provides methods for verifying GitHub webhook signatures and interacting with
|
|
9
|
+
* the GitHub API, such as posting comments on Pull Requests.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
* @since 3.0.0
|
|
13
|
+
*/
|
|
5
14
|
export class OctokitGitHubAdapter implements IGitHubAdapter {
|
|
6
15
|
private octokit: Octokit
|
|
7
16
|
|
|
@@ -2,6 +2,15 @@ import type { CacheService } from '@gravito/core'
|
|
|
2
2
|
import type { IRocketRepository } from '../../Domain/Interfaces'
|
|
3
3
|
import { Rocket } from '../../Domain/Rocket'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* CachedRocketRepository implements the `IRocketRepository` interface using Gravito's `CacheService`.
|
|
7
|
+
*
|
|
8
|
+
* It provides a persistent (depending on cache driver) storage for Rocket
|
|
9
|
+
* metadata, allowing Launchpad to track container states across requests.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
* @since 3.0.0
|
|
13
|
+
*/
|
|
5
14
|
export class CachedRocketRepository implements IRocketRepository {
|
|
6
15
|
private CACHE_KEY = 'launchpad:rockets'
|
|
7
16
|
|
|
@@ -2,6 +2,15 @@ import type { IRocketRepository } from '../../Domain/Interfaces'
|
|
|
2
2
|
import type { Rocket } from '../../Domain/Rocket'
|
|
3
3
|
import { RocketStatus } from '../../Domain/RocketStatus'
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* InMemoryRocketRepository implements the `IRocketRepository` interface using an in-memory Map.
|
|
7
|
+
*
|
|
8
|
+
* This repository is suitable for testing or development environments where
|
|
9
|
+
* persistence across restarts is not required.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
* @since 3.0.0
|
|
13
|
+
*/
|
|
5
14
|
export class InMemoryRocketRepository implements IRocketRepository {
|
|
6
15
|
private rockets = new Map<string, Rocket>()
|
|
7
16
|
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import { getRuntimeAdapter } from '@gravito/core'
|
|
2
2
|
import type { IRouterAdapter } from '../../Domain/Interfaces'
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* BunProxyAdapter implements the `IRouterAdapter` interface using Bun's native HTTP server.
|
|
6
|
+
*
|
|
7
|
+
* It provides a dynamic reverse proxy that routes requests to specific Rocket
|
|
8
|
+
* containers based on the `Host` header.
|
|
9
|
+
*
|
|
10
|
+
* @public
|
|
11
|
+
* @since 3.0.0
|
|
12
|
+
*/
|
|
4
13
|
export class BunProxyAdapter implements IRouterAdapter {
|
|
5
14
|
private routes = new Map<string, string>() // domain -> targetUrl
|
|
6
15
|
|
package/src/index.ts
CHANGED
|
@@ -26,10 +26,8 @@ export * from './Domain/RocketStatus'
|
|
|
26
26
|
class LaunchpadServiceProvider extends ServiceProvider {
|
|
27
27
|
register(container: Container): void {
|
|
28
28
|
if (!container.has('cache')) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
container.instance('cache', cacheFromServices)
|
|
32
|
-
}
|
|
29
|
+
// 在新的架構中,如果 core 有預設綁定,可以直接通過 container.make 獲取
|
|
30
|
+
// 這裡如果只是想從父容器繼承,通常不需要額外操作,除非是在不同的 instance 中。
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
container.singleton('launchpad.docker', () => new DockerAdapter())
|
|
@@ -101,11 +99,24 @@ class LaunchpadServiceProvider extends ServiceProvider {
|
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
/**
|
|
104
|
-
*
|
|
102
|
+
* LaunchpadOrbit provides automated deployment and preview capabilities for Gravito.
|
|
103
|
+
* It integrates with Docker and GitHub to provide "Preview Deployments" for Pull Requests.
|
|
104
|
+
*
|
|
105
|
+
* @public
|
|
105
106
|
*/
|
|
106
107
|
export class LaunchpadOrbit implements GravitoOrbit {
|
|
108
|
+
/**
|
|
109
|
+
* Create a new LaunchpadOrbit instance.
|
|
110
|
+
* @param ripple - Ripple instance for real-time telemetry communication.
|
|
111
|
+
*/
|
|
107
112
|
constructor(private ripple: OrbitRipple) {}
|
|
108
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Install the Launchpad orbit into PlanetCore.
|
|
116
|
+
* Registers the service provider and sets up webhook routes for deployment.
|
|
117
|
+
*
|
|
118
|
+
* @param core - The PlanetCore instance.
|
|
119
|
+
*/
|
|
109
120
|
async install(core: PlanetCore): Promise<void> {
|
|
110
121
|
core.register(new LaunchpadServiceProvider())
|
|
111
122
|
|
|
@@ -139,7 +150,7 @@ export class LaunchpadOrbit implements GravitoOrbit {
|
|
|
139
150
|
|
|
140
151
|
const ctrl = core.container.make<MissionControl>('launchpad.ctrl')
|
|
141
152
|
const rocketId = await ctrl.launch(mission, (type, data) => {
|
|
142
|
-
//
|
|
153
|
+
// Correctly broadcast telemetry data via Ripple
|
|
143
154
|
this.ripple.getServer().broadcast('telemetry', 'telemetry.data', { type, data })
|
|
144
155
|
})
|
|
145
156
|
|
|
@@ -188,7 +199,12 @@ export class LaunchpadOrbit implements GravitoOrbit {
|
|
|
188
199
|
}
|
|
189
200
|
|
|
190
201
|
/**
|
|
191
|
-
*
|
|
202
|
+
* Bootstrap the Launchpad application.
|
|
203
|
+
* Initializes PlanetCore with necessary orbits (Cache, Ripple, Launchpad)
|
|
204
|
+
* and returns the configuration for the server entry point.
|
|
205
|
+
*
|
|
206
|
+
* @returns Server configuration object including port and fetch handler.
|
|
207
|
+
* @public
|
|
192
208
|
*/
|
|
193
209
|
export async function bootstrapLaunchpad() {
|
|
194
210
|
const ripple = new OrbitRipple({ path: '/ws' })
|
|
@@ -199,11 +215,7 @@ export async function bootstrapLaunchpad() {
|
|
|
199
215
|
PORT: 4000,
|
|
200
216
|
CACHE_DRIVER: 'file',
|
|
201
217
|
},
|
|
202
|
-
orbits: [
|
|
203
|
-
new OrbitCache(),
|
|
204
|
-
ripple,
|
|
205
|
-
new LaunchpadOrbit(ripple), // 傳入實例
|
|
206
|
-
],
|
|
218
|
+
orbits: [new OrbitCache(), ripple, new LaunchpadOrbit(ripple)],
|
|
207
219
|
})
|
|
208
220
|
|
|
209
221
|
await core.bootstrap()
|