@docker-harpoon/core 0.1.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/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +10 -0
- package/dist/api/promise.d.ts +179 -0
- package/dist/api/promise.d.ts.map +1 -0
- package/dist/api/promise.js +282 -0
- package/dist/bindings/index.d.ts +8 -0
- package/dist/bindings/index.d.ts.map +1 -0
- package/dist/bindings/index.js +6 -0
- package/dist/bindings/types.d.ts +116 -0
- package/dist/bindings/types.d.ts.map +1 -0
- package/dist/bindings/types.js +46 -0
- package/dist/build-strategies/index.d.ts +30 -0
- package/dist/build-strategies/index.d.ts.map +1 -0
- package/dist/build-strategies/index.js +47 -0
- package/dist/build-strategies/standard.d.ts +16 -0
- package/dist/build-strategies/standard.d.ts.map +1 -0
- package/dist/build-strategies/standard.js +39 -0
- package/dist/build-strategies/types.d.ts +81 -0
- package/dist/build-strategies/types.d.ts.map +1 -0
- package/dist/build-strategies/types.js +25 -0
- package/dist/config-patchers/index.d.ts +33 -0
- package/dist/config-patchers/index.d.ts.map +1 -0
- package/dist/config-patchers/index.js +75 -0
- package/dist/config-patchers/types.d.ts +89 -0
- package/dist/config-patchers/types.d.ts.map +1 -0
- package/dist/config-patchers/types.js +25 -0
- package/dist/dockerfile-transformers/core.d.ts +59 -0
- package/dist/dockerfile-transformers/core.d.ts.map +1 -0
- package/dist/dockerfile-transformers/core.js +271 -0
- package/dist/dockerfile-transformers/index.d.ts +42 -0
- package/dist/dockerfile-transformers/index.d.ts.map +1 -0
- package/dist/dockerfile-transformers/index.js +67 -0
- package/dist/dockerfile-transformers/types.d.ts +116 -0
- package/dist/dockerfile-transformers/types.d.ts.map +1 -0
- package/dist/dockerfile-transformers/types.js +29 -0
- package/dist/errors.d.ts +75 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +119 -0
- package/dist/helpers/database.d.ts +54 -0
- package/dist/helpers/database.d.ts.map +1 -0
- package/dist/helpers/database.js +108 -0
- package/dist/helpers/index.d.ts +8 -0
- package/dist/helpers/index.d.ts.map +1 -0
- package/dist/helpers/index.js +6 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/resources/container.d.ts +42 -0
- package/dist/resources/container.d.ts.map +1 -0
- package/dist/resources/container.js +256 -0
- package/dist/resources/image.d.ts +37 -0
- package/dist/resources/image.d.ts.map +1 -0
- package/dist/resources/image.js +113 -0
- package/dist/resources/index.d.ts +12 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +8 -0
- package/dist/resources/network.d.ts +21 -0
- package/dist/resources/network.d.ts.map +1 -0
- package/dist/resources/network.js +86 -0
- package/package.json +28 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API Module
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the Promise-based API for cleaner imports.
|
|
5
|
+
*/
|
|
6
|
+
export { Network, Container, database, Image, setDocker, resetDocker, destroyAll, type NetworkConfig, type NetworkResource, type ContainerConfig, type ContainerResource, type PortMapping, type ShutdownMetadata, type DatabaseConfig, type DatabaseResource, type ImageConfig, type ImageResource, } from './promise';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAEL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,KAAK,EAGL,SAAS,EACT,WAAW,EACX,UAAU,EAGV,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,aAAa,GACnB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promise-based API Layer
|
|
3
|
+
*
|
|
4
|
+
* This module wraps the Effect-based resources with a simple Promise API.
|
|
5
|
+
* Effect is completely hidden from users - they just use async/await.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by Alchemy.run and Pulumi's IAC patterns.
|
|
8
|
+
*/
|
|
9
|
+
import Docker from 'dockerode';
|
|
10
|
+
import type { Binding } from '../bindings/types';
|
|
11
|
+
export interface NetworkConfig {
|
|
12
|
+
/** Network driver (default: 'bridge') */
|
|
13
|
+
driver?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface NetworkResource {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
/** Remove this network */
|
|
19
|
+
destroy(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export interface PortMapping {
|
|
22
|
+
internal: number;
|
|
23
|
+
external: number;
|
|
24
|
+
}
|
|
25
|
+
export interface ShutdownMetadata {
|
|
26
|
+
signalCount: number;
|
|
27
|
+
graceful: boolean;
|
|
28
|
+
timeTakenMs: number;
|
|
29
|
+
signal: string;
|
|
30
|
+
timeoutMs: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ContainerConfig {
|
|
33
|
+
image: string;
|
|
34
|
+
networks?: Array<NetworkResource | {
|
|
35
|
+
name: string;
|
|
36
|
+
}>;
|
|
37
|
+
ports?: PortMapping[];
|
|
38
|
+
env?: Record<string, string>;
|
|
39
|
+
cmd?: string[];
|
|
40
|
+
/** Bindings that provide env vars and lifecycle hooks */
|
|
41
|
+
bindings?: Record<string, Binding>;
|
|
42
|
+
}
|
|
43
|
+
export interface ContainerResource {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly name: string;
|
|
46
|
+
/** Wait for a log pattern to appear */
|
|
47
|
+
waitForLog(pattern: string | RegExp, timeoutMs?: number): Promise<void>;
|
|
48
|
+
/** Stop container gracefully */
|
|
49
|
+
stop(signal?: string, timeoutMs?: number): Promise<ShutdownMetadata>;
|
|
50
|
+
/** Get container IP in a network */
|
|
51
|
+
getIp(networkName: string): Promise<string>;
|
|
52
|
+
/** Remove this container */
|
|
53
|
+
destroy(): Promise<void>;
|
|
54
|
+
}
|
|
55
|
+
export interface DatabaseConfig {
|
|
56
|
+
/** Docker image (e.g., "postgres:15", "mysql:8", "redis:7") */
|
|
57
|
+
image: string;
|
|
58
|
+
/** Override default port for this database type */
|
|
59
|
+
port?: number;
|
|
60
|
+
/** Host port to expose (defaults to same as internal port) */
|
|
61
|
+
hostPort?: number;
|
|
62
|
+
/** Environment variables (e.g., { POSTGRES_PASSWORD: 'secret' }) */
|
|
63
|
+
env?: Record<string, string>;
|
|
64
|
+
/** Networks to connect to */
|
|
65
|
+
networks?: Array<NetworkResource | {
|
|
66
|
+
name: string;
|
|
67
|
+
}>;
|
|
68
|
+
/** Bindings for this database */
|
|
69
|
+
bindings?: Record<string, Binding>;
|
|
70
|
+
}
|
|
71
|
+
export interface DatabaseResource extends ContainerResource {
|
|
72
|
+
/** Connection string for this database */
|
|
73
|
+
readonly connectionString: string;
|
|
74
|
+
/** Internal port the database is listening on */
|
|
75
|
+
readonly port: number;
|
|
76
|
+
/** Host port mapped to the database */
|
|
77
|
+
readonly hostPort: number;
|
|
78
|
+
}
|
|
79
|
+
export interface ImageConfig {
|
|
80
|
+
/** Path to the build context directory */
|
|
81
|
+
context: string;
|
|
82
|
+
/** Path to Dockerfile relative to context */
|
|
83
|
+
dockerfile?: string;
|
|
84
|
+
/** Docker build arguments */
|
|
85
|
+
buildArgs?: Record<string, string>;
|
|
86
|
+
/** Build strategy to use (default: 'standard') */
|
|
87
|
+
buildStrategy?: string;
|
|
88
|
+
/** App name for monorepo strategies */
|
|
89
|
+
monorepoAppName?: string;
|
|
90
|
+
/** Source directory for shared monorepo folders */
|
|
91
|
+
monorepoSource?: string;
|
|
92
|
+
}
|
|
93
|
+
export interface ImageResource {
|
|
94
|
+
readonly tag: string;
|
|
95
|
+
readonly ref: string;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Set a custom Docker client.
|
|
99
|
+
* Useful for testing or custom configurations.
|
|
100
|
+
*/
|
|
101
|
+
export declare function setDocker(docker: Docker): void;
|
|
102
|
+
/**
|
|
103
|
+
* Reset Docker client (mainly for testing).
|
|
104
|
+
*/
|
|
105
|
+
export declare function resetDocker(): void;
|
|
106
|
+
/**
|
|
107
|
+
* Create a Docker network.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const network = await Network("my-app-net");
|
|
112
|
+
* // ... use network
|
|
113
|
+
* await network.destroy();
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare function Network(name: string, config?: NetworkConfig): Promise<NetworkResource>;
|
|
117
|
+
/**
|
|
118
|
+
* Create a Docker container.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const container = await Container("my-app", {
|
|
123
|
+
* image: "node:20-alpine",
|
|
124
|
+
* cmd: ["node", "server.js"],
|
|
125
|
+
* ports: [{ internal: 3000, external: 3000 }],
|
|
126
|
+
* });
|
|
127
|
+
*
|
|
128
|
+
* await container.waitForLog(/listening on/);
|
|
129
|
+
* // ... use container
|
|
130
|
+
* await container.destroy();
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export declare function Container(name: string, config: ContainerConfig): Promise<ContainerResource>;
|
|
134
|
+
/**
|
|
135
|
+
* Create a database container with sensible defaults.
|
|
136
|
+
*
|
|
137
|
+
* Automatically infers database type from image name and provides:
|
|
138
|
+
* - Default port mappings
|
|
139
|
+
* - Connection string generation
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```typescript
|
|
143
|
+
* const db = await database("my-postgres", {
|
|
144
|
+
* image: "postgres:16-alpine",
|
|
145
|
+
* env: { POSTGRES_PASSWORD: "secret", POSTGRES_DB: "mydb" },
|
|
146
|
+
* });
|
|
147
|
+
*
|
|
148
|
+
* console.log(db.connectionString);
|
|
149
|
+
* // postgresql://postgres:secret@localhost:5432/mydb
|
|
150
|
+
*
|
|
151
|
+
* await db.waitForLog(/ready to accept connections/);
|
|
152
|
+
* // ... use database
|
|
153
|
+
* await db.destroy();
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare function database(name: string, config: DatabaseConfig): Promise<DatabaseResource>;
|
|
157
|
+
/**
|
|
158
|
+
* Build a Docker image.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* ```typescript
|
|
162
|
+
* const image = await Image("my-app:latest", {
|
|
163
|
+
* context: "./",
|
|
164
|
+
* dockerfile: "Dockerfile",
|
|
165
|
+
* });
|
|
166
|
+
*
|
|
167
|
+
* const container = await Container("app", {
|
|
168
|
+
* image: image.tag,
|
|
169
|
+
* // ...
|
|
170
|
+
* });
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export declare function Image(tag: string, config: ImageConfig): Promise<ImageResource>;
|
|
174
|
+
/**
|
|
175
|
+
* Clean up all tracked resources.
|
|
176
|
+
* Called automatically on process exit, but can be called manually.
|
|
177
|
+
*/
|
|
178
|
+
export declare function destroyAll(): Promise<void>;
|
|
179
|
+
//# sourceMappingURL=promise.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Promise-based API Layer
|
|
3
|
+
*
|
|
4
|
+
* This module wraps the Effect-based resources with a simple Promise API.
|
|
5
|
+
* Effect is completely hidden from users - they just use async/await.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by Alchemy.run and Pulumi's IAC patterns.
|
|
8
|
+
*/
|
|
9
|
+
import { Effect, Scope, Exit } from 'effect';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import Docker from 'dockerode';
|
|
13
|
+
// Import Effect-based resources (internal only)
|
|
14
|
+
import { Network as EffectNetwork, Container as EffectContainer, Image as EffectImage, } from '../resources';
|
|
15
|
+
import { database as effectDatabase, } from '../helpers';
|
|
16
|
+
// Binding type is used as-is from internal types
|
|
17
|
+
// Pre-built bindings like PrismaBinding handle Effect internally
|
|
18
|
+
// Users creating simple bindings can use createEnvBinding()
|
|
19
|
+
// ============ Docker Client Management ============
|
|
20
|
+
let dockerClient = null;
|
|
21
|
+
/**
|
|
22
|
+
* Detect Docker socket path for the current platform.
|
|
23
|
+
*/
|
|
24
|
+
function detectDockerSocket() {
|
|
25
|
+
const candidates = [
|
|
26
|
+
'/var/run/docker.sock',
|
|
27
|
+
`${homedir()}/.docker/run/docker.sock`,
|
|
28
|
+
];
|
|
29
|
+
for (const path of candidates) {
|
|
30
|
+
if (existsSync(path)) {
|
|
31
|
+
return path;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Windows uses named pipes, let dockerode handle it
|
|
35
|
+
return '/var/run/docker.sock';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get the Docker client, auto-detecting the socket if needed.
|
|
39
|
+
*/
|
|
40
|
+
function getDocker() {
|
|
41
|
+
if (!dockerClient) {
|
|
42
|
+
const socketPath = detectDockerSocket();
|
|
43
|
+
dockerClient = new Docker({ socketPath });
|
|
44
|
+
}
|
|
45
|
+
return dockerClient;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Set a custom Docker client.
|
|
49
|
+
* Useful for testing or custom configurations.
|
|
50
|
+
*/
|
|
51
|
+
export function setDocker(docker) {
|
|
52
|
+
dockerClient = docker;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reset Docker client (mainly for testing).
|
|
56
|
+
*/
|
|
57
|
+
export function resetDocker() {
|
|
58
|
+
dockerClient = null;
|
|
59
|
+
}
|
|
60
|
+
const resources = new Map();
|
|
61
|
+
/**
|
|
62
|
+
* Register cleanup on process exit.
|
|
63
|
+
*/
|
|
64
|
+
let cleanupRegistered = false;
|
|
65
|
+
function registerCleanup() {
|
|
66
|
+
if (cleanupRegistered)
|
|
67
|
+
return;
|
|
68
|
+
cleanupRegistered = true;
|
|
69
|
+
const cleanup = async () => {
|
|
70
|
+
for (const [id, resource] of resources) {
|
|
71
|
+
try {
|
|
72
|
+
await resource.cleanup();
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
console.error(`[Cleanup] Error cleaning up ${id}:`, e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
resources.clear();
|
|
79
|
+
};
|
|
80
|
+
process.on('beforeExit', cleanup);
|
|
81
|
+
process.on('SIGINT', async () => {
|
|
82
|
+
await cleanup();
|
|
83
|
+
process.exit(0);
|
|
84
|
+
});
|
|
85
|
+
process.on('SIGTERM', async () => {
|
|
86
|
+
await cleanup();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// ============ Promise-based Resource Constructors ============
|
|
91
|
+
/**
|
|
92
|
+
* Create a Docker network.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const network = await Network("my-app-net");
|
|
97
|
+
* // ... use network
|
|
98
|
+
* await network.destroy();
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
export async function Network(name, config = {}) {
|
|
102
|
+
registerCleanup();
|
|
103
|
+
const docker = getDocker();
|
|
104
|
+
const scope = Effect.runSync(Scope.make());
|
|
105
|
+
const effectConfig = {
|
|
106
|
+
name,
|
|
107
|
+
...(config.driver !== undefined && { driver: config.driver }),
|
|
108
|
+
};
|
|
109
|
+
const effectResource = await Effect.runPromise(EffectNetwork(name, effectConfig, docker).pipe(Scope.extend(scope)));
|
|
110
|
+
const resourceId = `network:${effectResource.id}`;
|
|
111
|
+
const cleanup = async () => {
|
|
112
|
+
await Effect.runPromise(Scope.close(scope, Exit.void));
|
|
113
|
+
};
|
|
114
|
+
resources.set(resourceId, { scope, cleanup });
|
|
115
|
+
return {
|
|
116
|
+
id: effectResource.id,
|
|
117
|
+
name: effectResource.name,
|
|
118
|
+
async destroy() {
|
|
119
|
+
await cleanup();
|
|
120
|
+
resources.delete(resourceId);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Create a Docker container.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* const container = await Container("my-app", {
|
|
130
|
+
* image: "node:20-alpine",
|
|
131
|
+
* cmd: ["node", "server.js"],
|
|
132
|
+
* ports: [{ internal: 3000, external: 3000 }],
|
|
133
|
+
* });
|
|
134
|
+
*
|
|
135
|
+
* await container.waitForLog(/listening on/);
|
|
136
|
+
* // ... use container
|
|
137
|
+
* await container.destroy();
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export async function Container(name, config) {
|
|
141
|
+
registerCleanup();
|
|
142
|
+
const docker = getDocker();
|
|
143
|
+
const scope = Effect.runSync(Scope.make());
|
|
144
|
+
// Convert NetworkResource to { name: string }
|
|
145
|
+
const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
|
|
146
|
+
const effectConfig = {
|
|
147
|
+
image: config.image,
|
|
148
|
+
...(networks !== undefined && { networks }),
|
|
149
|
+
...(config.ports !== undefined && { ports: config.ports }),
|
|
150
|
+
...(config.env !== undefined && { env: config.env }),
|
|
151
|
+
...(config.cmd !== undefined && { cmd: config.cmd }),
|
|
152
|
+
...(config.bindings !== undefined && { bindings: config.bindings }),
|
|
153
|
+
};
|
|
154
|
+
const effectResource = await Effect.runPromise(EffectContainer(name, effectConfig, docker).pipe(Scope.extend(scope)));
|
|
155
|
+
const resourceId = `container:${effectResource.id}`;
|
|
156
|
+
const cleanup = async () => {
|
|
157
|
+
await Effect.runPromise(Scope.close(scope, Exit.void));
|
|
158
|
+
};
|
|
159
|
+
resources.set(resourceId, { scope, cleanup });
|
|
160
|
+
return {
|
|
161
|
+
id: effectResource.id,
|
|
162
|
+
name: effectResource.name,
|
|
163
|
+
async waitForLog(pattern, timeoutMs = 30000) {
|
|
164
|
+
await Effect.runPromise(effectResource.waitForLog(pattern, timeoutMs));
|
|
165
|
+
},
|
|
166
|
+
async stop(signal = 'SIGTERM', timeoutMs = 10000) {
|
|
167
|
+
return Effect.runPromise(effectResource.stopGracefully(signal, timeoutMs));
|
|
168
|
+
},
|
|
169
|
+
async getIp(networkName) {
|
|
170
|
+
return Effect.runPromise(effectResource.getIp(networkName));
|
|
171
|
+
},
|
|
172
|
+
async destroy() {
|
|
173
|
+
await cleanup();
|
|
174
|
+
resources.delete(resourceId);
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Create a database container with sensible defaults.
|
|
180
|
+
*
|
|
181
|
+
* Automatically infers database type from image name and provides:
|
|
182
|
+
* - Default port mappings
|
|
183
|
+
* - Connection string generation
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* const db = await database("my-postgres", {
|
|
188
|
+
* image: "postgres:16-alpine",
|
|
189
|
+
* env: { POSTGRES_PASSWORD: "secret", POSTGRES_DB: "mydb" },
|
|
190
|
+
* });
|
|
191
|
+
*
|
|
192
|
+
* console.log(db.connectionString);
|
|
193
|
+
* // postgresql://postgres:secret@localhost:5432/mydb
|
|
194
|
+
*
|
|
195
|
+
* await db.waitForLog(/ready to accept connections/);
|
|
196
|
+
* // ... use database
|
|
197
|
+
* await db.destroy();
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
export async function database(name, config) {
|
|
201
|
+
registerCleanup();
|
|
202
|
+
const docker = getDocker();
|
|
203
|
+
const scope = Effect.runSync(Scope.make());
|
|
204
|
+
// Convert NetworkResource to { name: string }
|
|
205
|
+
const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
|
|
206
|
+
const effectConfig = {
|
|
207
|
+
image: config.image,
|
|
208
|
+
...(config.port !== undefined && { port: config.port }),
|
|
209
|
+
...(config.hostPort !== undefined && { hostPort: config.hostPort }),
|
|
210
|
+
...(config.env !== undefined && { env: config.env }),
|
|
211
|
+
...(networks !== undefined && { networks }),
|
|
212
|
+
...(config.bindings !== undefined && { bindings: config.bindings }),
|
|
213
|
+
};
|
|
214
|
+
const effectResource = await Effect.runPromise(effectDatabase(name, effectConfig, docker).pipe(Scope.extend(scope)));
|
|
215
|
+
const resourceId = `database:${effectResource.id}`;
|
|
216
|
+
const cleanup = async () => {
|
|
217
|
+
await Effect.runPromise(Scope.close(scope, Exit.void));
|
|
218
|
+
};
|
|
219
|
+
resources.set(resourceId, { scope, cleanup });
|
|
220
|
+
return {
|
|
221
|
+
id: effectResource.id,
|
|
222
|
+
name: effectResource.name,
|
|
223
|
+
connectionString: effectResource.connectionString,
|
|
224
|
+
port: effectResource.port,
|
|
225
|
+
hostPort: effectResource.hostPort,
|
|
226
|
+
async waitForLog(pattern, timeoutMs = 30000) {
|
|
227
|
+
await Effect.runPromise(effectResource.waitForLog(pattern, timeoutMs));
|
|
228
|
+
},
|
|
229
|
+
async stop(signal = 'SIGTERM', timeoutMs = 10000) {
|
|
230
|
+
return Effect.runPromise(effectResource.stopGracefully(signal, timeoutMs));
|
|
231
|
+
},
|
|
232
|
+
async getIp(networkName) {
|
|
233
|
+
return Effect.runPromise(effectResource.getIp(networkName));
|
|
234
|
+
},
|
|
235
|
+
async destroy() {
|
|
236
|
+
await cleanup();
|
|
237
|
+
resources.delete(resourceId);
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Build a Docker image.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```typescript
|
|
246
|
+
* const image = await Image("my-app:latest", {
|
|
247
|
+
* context: "./",
|
|
248
|
+
* dockerfile: "Dockerfile",
|
|
249
|
+
* });
|
|
250
|
+
*
|
|
251
|
+
* const container = await Container("app", {
|
|
252
|
+
* image: image.tag,
|
|
253
|
+
* // ...
|
|
254
|
+
* });
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
export async function Image(tag, config) {
|
|
258
|
+
const effectConfig = {
|
|
259
|
+
context: config.context,
|
|
260
|
+
...(config.dockerfile !== undefined && { dockerfile: config.dockerfile }),
|
|
261
|
+
...(config.buildArgs !== undefined && { buildArgs: config.buildArgs }),
|
|
262
|
+
...(config.buildStrategy !== undefined && { buildStrategy: config.buildStrategy }),
|
|
263
|
+
...(config.monorepoAppName !== undefined && { monorepoAppName: config.monorepoAppName }),
|
|
264
|
+
...(config.monorepoSource !== undefined && { monorepoSource: config.monorepoSource }),
|
|
265
|
+
};
|
|
266
|
+
return Effect.runPromise(EffectImage(tag, effectConfig));
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Clean up all tracked resources.
|
|
270
|
+
* Called automatically on process exit, but can be called manually.
|
|
271
|
+
*/
|
|
272
|
+
export async function destroyAll() {
|
|
273
|
+
for (const [id, resource] of resources) {
|
|
274
|
+
try {
|
|
275
|
+
await resource.cleanup();
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
console.error(`[Cleanup] Error cleaning up ${id}:`, e);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
resources.clear();
|
|
282
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bindings Module
|
|
3
|
+
*
|
|
4
|
+
* Exports binding types and utilities for creating resource bindings.
|
|
5
|
+
*/
|
|
6
|
+
export type { Binding, BuildBinding, BindingsEnv, FlatBindingsEnv, } from './types';
|
|
7
|
+
export { isBuildBinding, mergeBindingsEnv, createEnvBinding, } from './types';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bindings/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,OAAO,EACP,YAAY,EACZ,WAAW,EACX,eAAe,GAChB,MAAM,SAAS,CAAC;AAEjB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding Types
|
|
3
|
+
*
|
|
4
|
+
* Bindings connect resources to containers, providing:
|
|
5
|
+
* - Environment variables
|
|
6
|
+
* - Lifecycle hooks
|
|
7
|
+
* - Build-time transformations
|
|
8
|
+
*
|
|
9
|
+
* Inspired by CloudFlare Workers bindings pattern.
|
|
10
|
+
*/
|
|
11
|
+
import { Effect } from 'effect';
|
|
12
|
+
import type { DockerfileInstruction, TransformContext } from '../dockerfile-transformers/types';
|
|
13
|
+
import type { PatchContext, PatchResult } from '../config-patchers/types';
|
|
14
|
+
import type { BuildContext, BuildStrategyInput } from '../build-strategies/types';
|
|
15
|
+
/**
|
|
16
|
+
* Base binding interface.
|
|
17
|
+
*
|
|
18
|
+
* Bindings attach to resources and provide environment variables
|
|
19
|
+
* and lifecycle hooks to containers.
|
|
20
|
+
*/
|
|
21
|
+
export interface Binding<TResource = unknown, TEnv extends Record<string, string> = Record<string, string>> {
|
|
22
|
+
/** Unique binding type identifier */
|
|
23
|
+
readonly type: string;
|
|
24
|
+
/** The resource this binding is attached to (optional) */
|
|
25
|
+
readonly resource?: TResource;
|
|
26
|
+
/**
|
|
27
|
+
* Get environment variables this binding provides.
|
|
28
|
+
* These are merged into the container's environment.
|
|
29
|
+
*/
|
|
30
|
+
getEnv(): TEnv;
|
|
31
|
+
/**
|
|
32
|
+
* Called when the binding is attached to a container config.
|
|
33
|
+
* Can modify the container config before creation.
|
|
34
|
+
*/
|
|
35
|
+
onAttach?<TConfig>(config: TConfig): TConfig | Effect.Effect<TConfig, Error>;
|
|
36
|
+
/**
|
|
37
|
+
* Called after the container starts.
|
|
38
|
+
* Use for initialization tasks like running migrations.
|
|
39
|
+
*/
|
|
40
|
+
onStart?<TContainer>(container: TContainer): Effect.Effect<void, Error>;
|
|
41
|
+
/**
|
|
42
|
+
* Called before the container stops.
|
|
43
|
+
* Use for cleanup tasks.
|
|
44
|
+
*/
|
|
45
|
+
onStop?<TContainer>(container: TContainer): Effect.Effect<void, Error>;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build-time binding interface.
|
|
49
|
+
*
|
|
50
|
+
* Extends Binding with build-time transformation capabilities
|
|
51
|
+
* for Dockerfile modifications and config patching.
|
|
52
|
+
*/
|
|
53
|
+
export interface BuildBinding<TEnv extends Record<string, string> = Record<string, string>> extends Binding<undefined, TEnv> {
|
|
54
|
+
/**
|
|
55
|
+
* Transform Dockerfile instructions during image build.
|
|
56
|
+
* Return modified instructions or the same instructions unchanged.
|
|
57
|
+
*/
|
|
58
|
+
transformDockerfile?(instructions: readonly DockerfileInstruction[], ctx: TransformContext): readonly DockerfileInstruction[];
|
|
59
|
+
/**
|
|
60
|
+
* Patch project configuration files before build.
|
|
61
|
+
*/
|
|
62
|
+
patchConfig?(ctx: PatchContext): Effect.Effect<PatchResult, Error>;
|
|
63
|
+
/**
|
|
64
|
+
* Prepare or modify the build context.
|
|
65
|
+
* Called during image build strategy execution.
|
|
66
|
+
*/
|
|
67
|
+
prepareBuildContext?(input: BuildStrategyInput, ctx: BuildContext): Effect.Effect<BuildContext, Error>;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Utility type to extract the combined env type from a bindings record.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const bindings = {
|
|
75
|
+
* DB: PrismaBinding(db), // { DATABASE_URL: string }
|
|
76
|
+
* CACHE: RedisBinding(redis), // { REDIS_URL: string }
|
|
77
|
+
* };
|
|
78
|
+
*
|
|
79
|
+
* type Env = BindingsEnv<typeof bindings>;
|
|
80
|
+
* // { DB: { DATABASE_URL: string }, CACHE: { REDIS_URL: string } }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export type BindingsEnv<B extends Record<string, Binding<unknown, Record<string, string>>>> = {
|
|
84
|
+
[K in keyof B]: B[K] extends Binding<unknown, infer E> ? E : never;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Flatten bindings env into a single record of all env vars.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* type FlatEnv = FlatBindingsEnv<typeof bindings>;
|
|
92
|
+
* // { DATABASE_URL: string, REDIS_URL: string }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export type FlatBindingsEnv<B extends Record<string, Binding<unknown, Record<string, string>>>> = B[keyof B] extends Binding<unknown, infer E> ? E : never;
|
|
96
|
+
/**
|
|
97
|
+
* Check if a binding is a build-time binding.
|
|
98
|
+
*/
|
|
99
|
+
export declare function isBuildBinding(binding: Binding): binding is BuildBinding;
|
|
100
|
+
/**
|
|
101
|
+
* Merge environment variables from multiple bindings.
|
|
102
|
+
*/
|
|
103
|
+
export declare function mergeBindingsEnv(bindings: Record<string, Binding>): Record<string, string>;
|
|
104
|
+
/**
|
|
105
|
+
* Create a simple binding from environment variables.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const configBinding = createEnvBinding('config', {
|
|
110
|
+
* NODE_ENV: 'production',
|
|
111
|
+
* LOG_LEVEL: 'info',
|
|
112
|
+
* });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function createEnvBinding<TEnv extends Record<string, string>>(type: string, env: TEnv): Binding<undefined, TEnv>;
|
|
116
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/bindings/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binding Types
|
|
3
|
+
*
|
|
4
|
+
* Bindings connect resources to containers, providing:
|
|
5
|
+
* - Environment variables
|
|
6
|
+
* - Lifecycle hooks
|
|
7
|
+
* - Build-time transformations
|
|
8
|
+
*
|
|
9
|
+
* Inspired by CloudFlare Workers bindings pattern.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Check if a binding is a build-time binding.
|
|
13
|
+
*/
|
|
14
|
+
export function isBuildBinding(binding) {
|
|
15
|
+
return ('transformDockerfile' in binding ||
|
|
16
|
+
'patchConfig' in binding ||
|
|
17
|
+
'prepareBuildContext' in binding);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Merge environment variables from multiple bindings.
|
|
21
|
+
*/
|
|
22
|
+
export function mergeBindingsEnv(bindings) {
|
|
23
|
+
const env = {};
|
|
24
|
+
for (const binding of Object.values(bindings)) {
|
|
25
|
+
const bindingEnv = binding.getEnv();
|
|
26
|
+
Object.assign(env, bindingEnv);
|
|
27
|
+
}
|
|
28
|
+
return env;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a simple binding from environment variables.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const configBinding = createEnvBinding('config', {
|
|
36
|
+
* NODE_ENV: 'production',
|
|
37
|
+
* LOG_LEVEL: 'info',
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function createEnvBinding(type, env) {
|
|
42
|
+
return {
|
|
43
|
+
type,
|
|
44
|
+
getEnv: () => env,
|
|
45
|
+
};
|
|
46
|
+
}
|