@docker-harpoon/core 0.1.0 → 0.1.3
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/__tests__/bindings.test.d.ts +10 -0
- package/dist/__tests__/bindings.test.d.ts.map +1 -0
- package/dist/__tests__/bindings.test.js +124 -0
- package/dist/__tests__/container.test.d.ts +12 -0
- package/dist/__tests__/container.test.d.ts.map +1 -0
- package/dist/__tests__/container.test.js +115 -0
- package/dist/__tests__/database.test.d.ts +10 -0
- package/dist/__tests__/database.test.d.ts.map +1 -0
- package/dist/__tests__/database.test.js +92 -0
- package/dist/__tests__/network.test.d.ts +10 -0
- package/dist/__tests__/network.test.d.ts.map +1 -0
- package/dist/__tests__/network.test.js +44 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/promise.d.ts +8 -0
- package/dist/api/promise.d.ts.map +1 -1
- package/dist/api/promise.js +109 -4
- package/dist/build-strategies/types.d.ts.map +1 -1
- package/dist/config-patchers/types.d.ts.map +1 -1
- package/dist/dockerfile-transformers/types.d.ts.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/helpers/database.d.ts.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/resources/container.d.ts +10 -0
- package/dist/resources/container.d.ts.map +1 -1
- package/dist/resources/container.js +222 -5
- package/dist/resources/image.d.ts +1 -1
- package/dist/resources/image.d.ts.map +1 -1
- package/dist/resources/index.d.ts +2 -0
- package/dist/resources/index.d.ts.map +1 -1
- package/dist/resources/index.js +2 -0
- package/dist/resources/network.d.ts.map +1 -1
- package/dist/resources/schemas.d.ts +120 -0
- package/dist/resources/schemas.d.ts.map +1 -0
- package/dist/resources/schemas.js +95 -0
- package/package.json +7 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bindings.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bindings.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bindings Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Dogfoods the binding system to verify:
|
|
5
|
+
* - createEnvBinding helper
|
|
6
|
+
* - Env var injection into containers
|
|
7
|
+
* - Binding composition
|
|
8
|
+
*/
|
|
9
|
+
import { describe, test, expect, afterEach, setDefaultTimeout } from 'bun:test';
|
|
10
|
+
import { Container, database, createEnvBinding, destroyAll } from '../index';
|
|
11
|
+
setDefaultTimeout(30_000);
|
|
12
|
+
const waitUntil = (fn, timeout = 5000) => {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const interval = setInterval(() => {
|
|
15
|
+
if (fn()) {
|
|
16
|
+
clearInterval(interval);
|
|
17
|
+
resolve(true);
|
|
18
|
+
}
|
|
19
|
+
}, 100);
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
describe('Bindings', () => {
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
await destroyAll();
|
|
25
|
+
});
|
|
26
|
+
test('createEnvBinding creates a simple env binding', () => {
|
|
27
|
+
const binding = createEnvBinding('config', {
|
|
28
|
+
NODE_ENV: 'test',
|
|
29
|
+
LOG_LEVEL: 'debug',
|
|
30
|
+
});
|
|
31
|
+
expect(binding.type).toBe('config');
|
|
32
|
+
expect(binding.getEnv()).toEqual({
|
|
33
|
+
NODE_ENV: 'test',
|
|
34
|
+
LOG_LEVEL: 'debug',
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
test('container receives env vars from bindings', async () => {
|
|
38
|
+
const configBinding = createEnvBinding('config', {
|
|
39
|
+
APP_NAME: 'test-app',
|
|
40
|
+
APP_VERSION: '1.0.0',
|
|
41
|
+
});
|
|
42
|
+
const container = await Container('test-binding-env', {
|
|
43
|
+
image: 'alpine:latest',
|
|
44
|
+
cmd: ['sleep', '30'],
|
|
45
|
+
bindings: {
|
|
46
|
+
config: configBinding,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
await waitUntil(() => !!container.getIp('test-binding-env'), 5000);
|
|
50
|
+
const result = await container.exec([
|
|
51
|
+
'sh',
|
|
52
|
+
'-c',
|
|
53
|
+
'echo "$APP_NAME:$APP_VERSION"',
|
|
54
|
+
]);
|
|
55
|
+
expect(result.stdout.trim()).toBe('test-app:1.0.0');
|
|
56
|
+
await container.destroy();
|
|
57
|
+
});
|
|
58
|
+
test('multiple bindings merge env vars', async () => {
|
|
59
|
+
const binding1 = createEnvBinding('env1', {
|
|
60
|
+
VAR_A: 'a',
|
|
61
|
+
VAR_B: 'b',
|
|
62
|
+
});
|
|
63
|
+
const binding2 = createEnvBinding('env2', {
|
|
64
|
+
VAR_C: 'c',
|
|
65
|
+
VAR_D: 'd',
|
|
66
|
+
});
|
|
67
|
+
const container = await Container('test-multi-binding', {
|
|
68
|
+
image: 'alpine:latest',
|
|
69
|
+
cmd: ['sleep', '30'],
|
|
70
|
+
bindings: {
|
|
71
|
+
first: binding1,
|
|
72
|
+
second: binding2,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
await waitUntil(() => !!container.getIp('test-multi-binding'), 5000);
|
|
76
|
+
const result = await container.exec([
|
|
77
|
+
'sh',
|
|
78
|
+
'-c',
|
|
79
|
+
'echo "$VAR_A$VAR_B$VAR_C$VAR_D"',
|
|
80
|
+
]);
|
|
81
|
+
expect(result.stdout.trim()).toBe('abcd');
|
|
82
|
+
await container.destroy();
|
|
83
|
+
});
|
|
84
|
+
test('explicit env takes precedence over bindings', async () => {
|
|
85
|
+
const binding = createEnvBinding('config', {
|
|
86
|
+
MY_VAR: 'from-binding',
|
|
87
|
+
});
|
|
88
|
+
const container = await Container('test-env-precedence', {
|
|
89
|
+
image: 'alpine:latest',
|
|
90
|
+
cmd: ['sleep', '30'],
|
|
91
|
+
bindings: {
|
|
92
|
+
config: binding,
|
|
93
|
+
},
|
|
94
|
+
env: {
|
|
95
|
+
MY_VAR: 'from-explicit',
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
await waitUntil(() => !!container.getIp('test-env-precedence'), 5000);
|
|
99
|
+
const result = await container.exec(['sh', '-c', 'echo "$MY_VAR"']);
|
|
100
|
+
expect(result.stdout.trim()).toBe('from-explicit');
|
|
101
|
+
await container.destroy();
|
|
102
|
+
});
|
|
103
|
+
test('database can be used as binding resource', async () => {
|
|
104
|
+
const db = await database('test-db-binding', {
|
|
105
|
+
image: 'redis:7-alpine',
|
|
106
|
+
hostPort: 16390,
|
|
107
|
+
});
|
|
108
|
+
const dbBinding = createEnvBinding('database', {
|
|
109
|
+
REDIS_URL: db.connectionString,
|
|
110
|
+
});
|
|
111
|
+
const container = await Container('test-db-consumer', {
|
|
112
|
+
image: 'alpine:latest',
|
|
113
|
+
cmd: ['sleep', '30'],
|
|
114
|
+
bindings: {
|
|
115
|
+
db: dbBinding,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
await waitUntil(() => !!container.getIp('test-db-consumer'), 5000);
|
|
119
|
+
const result = await container.exec(['sh', '-c', 'echo "$REDIS_URL"']);
|
|
120
|
+
expect(result.stdout.trim()).toBe('redis://localhost:16390');
|
|
121
|
+
await container.destroy();
|
|
122
|
+
await db.destroy();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container Resource Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Dogfoods the Container resource to verify:
|
|
5
|
+
* - Creation with network binding
|
|
6
|
+
* - waitForLog functionality
|
|
7
|
+
* - exec command execution
|
|
8
|
+
* - stats retrieval
|
|
9
|
+
* - Lifecycle (stop, destroy)
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=container.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"container.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/container.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Container Resource Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Dogfoods the Container resource to verify:
|
|
5
|
+
* - Creation with network binding
|
|
6
|
+
* - waitForLog functionality
|
|
7
|
+
* - exec command execution
|
|
8
|
+
* - stats retrieval
|
|
9
|
+
* - Lifecycle (stop, destroy)
|
|
10
|
+
*/
|
|
11
|
+
import { describe, test, expect, afterEach, setDefaultTimeout } from 'bun:test';
|
|
12
|
+
import { Network, Container, destroyAll } from '../index';
|
|
13
|
+
setDefaultTimeout(30_000);
|
|
14
|
+
describe('Container', () => {
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await destroyAll();
|
|
17
|
+
});
|
|
18
|
+
test('creates a container with basic config', async () => {
|
|
19
|
+
const container = await Container('test-container-basic', {
|
|
20
|
+
image: 'alpine:latest',
|
|
21
|
+
cmd: ['sleep', '30'],
|
|
22
|
+
});
|
|
23
|
+
expect(container.id).toBeDefined();
|
|
24
|
+
expect(container.name).toBe('test-container-basic');
|
|
25
|
+
await container.destroy();
|
|
26
|
+
});
|
|
27
|
+
test('creates a container attached to a network', async () => {
|
|
28
|
+
const network = await Network('test-container-net');
|
|
29
|
+
const container = await Container('test-container-networked', {
|
|
30
|
+
image: 'alpine:latest',
|
|
31
|
+
cmd: ['sleep', '30'],
|
|
32
|
+
networks: [network],
|
|
33
|
+
});
|
|
34
|
+
expect(container.id).toBeDefined();
|
|
35
|
+
const ip = await container.getIp('test-container-net');
|
|
36
|
+
expect(ip).toBeDefined();
|
|
37
|
+
await container.destroy();
|
|
38
|
+
await network.destroy();
|
|
39
|
+
});
|
|
40
|
+
test('exec runs commands inside container', async () => {
|
|
41
|
+
const container = await Container('test-container-exec', {
|
|
42
|
+
image: 'alpine:latest',
|
|
43
|
+
cmd: ['sleep', '30'],
|
|
44
|
+
});
|
|
45
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
46
|
+
const result = await container.exec(['echo', 'hello world']);
|
|
47
|
+
expect(result.exitCode).toBe(0);
|
|
48
|
+
expect(result.stdout.trim()).toBe('hello world');
|
|
49
|
+
expect(result.durationMs).toBeGreaterThan(0);
|
|
50
|
+
await container.destroy();
|
|
51
|
+
});
|
|
52
|
+
test('stats returns container resource statistics', async () => {
|
|
53
|
+
const container = await Container('test-container-stats', {
|
|
54
|
+
image: 'alpine:latest',
|
|
55
|
+
cmd: ['sleep', '30'],
|
|
56
|
+
});
|
|
57
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
58
|
+
const stats = await container.stats();
|
|
59
|
+
expect(stats.cpu).toBeDefined();
|
|
60
|
+
expect(stats.cpu.cores).toBeGreaterThan(0);
|
|
61
|
+
expect(stats.memory).toBeDefined();
|
|
62
|
+
expect(stats.memory.limit).toBeGreaterThan(0);
|
|
63
|
+
expect(stats.network).toBeDefined();
|
|
64
|
+
expect(stats.containerId).toBe(container.id);
|
|
65
|
+
await container.destroy();
|
|
66
|
+
});
|
|
67
|
+
test('waitForLog detects log patterns', async () => {
|
|
68
|
+
const container = await Container('test-container-logs', {
|
|
69
|
+
image: 'alpine:latest',
|
|
70
|
+
cmd: ['sh', '-c', 'echo "Server started successfully" && sleep 30'],
|
|
71
|
+
});
|
|
72
|
+
await container.waitForLog('Server started', 5000);
|
|
73
|
+
expect(true).toBe(true);
|
|
74
|
+
await container.destroy();
|
|
75
|
+
});
|
|
76
|
+
test('stop gracefully shuts down container', async () => {
|
|
77
|
+
const container = await Container('test-container-stop', {
|
|
78
|
+
image: 'alpine:latest',
|
|
79
|
+
cmd: ['sh', '-c', 'trap "exit 0" TERM; while true; do sleep 1; done'],
|
|
80
|
+
});
|
|
81
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
82
|
+
const metadata = await container.stop('SIGTERM', 10000);
|
|
83
|
+
expect(metadata.signal).toBe('SIGTERM');
|
|
84
|
+
expect(metadata.signalCount).toBeGreaterThan(0);
|
|
85
|
+
expect(metadata.timeTakenMs).toBeGreaterThan(0);
|
|
86
|
+
await container.destroy();
|
|
87
|
+
});
|
|
88
|
+
test('supports environment variables', async () => {
|
|
89
|
+
const container = await Container('test-container-env', {
|
|
90
|
+
image: 'alpine:latest',
|
|
91
|
+
cmd: ['sleep', '30'],
|
|
92
|
+
env: {
|
|
93
|
+
MY_VAR: 'hello',
|
|
94
|
+
ANOTHER_VAR: 'world',
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
98
|
+
const result = await container.exec([
|
|
99
|
+
'sh',
|
|
100
|
+
'-c',
|
|
101
|
+
'echo $MY_VAR $ANOTHER_VAR',
|
|
102
|
+
]);
|
|
103
|
+
expect(result.stdout.trim()).toBe('hello world');
|
|
104
|
+
await container.destroy();
|
|
105
|
+
});
|
|
106
|
+
test('supports port mappings', async () => {
|
|
107
|
+
const container = await Container('test-container-ports', {
|
|
108
|
+
image: 'alpine:latest',
|
|
109
|
+
cmd: ['sleep', '30'],
|
|
110
|
+
ports: [{ internal: 8080, external: 18080 }],
|
|
111
|
+
});
|
|
112
|
+
expect(container.id).toBeDefined();
|
|
113
|
+
await container.destroy();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/database.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Helper Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Dogfoods the database helper to verify:
|
|
5
|
+
* - Connection string generation
|
|
6
|
+
* - Port mapping
|
|
7
|
+
* - Database-specific defaults
|
|
8
|
+
*/
|
|
9
|
+
import { describe, test, expect, afterEach, setDefaultTimeout } from 'bun:test';
|
|
10
|
+
import { Network, database, destroyAll } from '../index';
|
|
11
|
+
setDefaultTimeout(60_000);
|
|
12
|
+
describe('database', () => {
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await destroyAll();
|
|
15
|
+
});
|
|
16
|
+
test('creates a postgres database with connection string', async () => {
|
|
17
|
+
const db = await database('test-postgres', {
|
|
18
|
+
image: 'postgres:16-alpine',
|
|
19
|
+
env: {
|
|
20
|
+
POSTGRES_PASSWORD: 'testpass',
|
|
21
|
+
POSTGRES_DB: 'testdb',
|
|
22
|
+
},
|
|
23
|
+
hostPort: 15432,
|
|
24
|
+
});
|
|
25
|
+
expect(db.id).toBeDefined();
|
|
26
|
+
expect(db.name).toBe('test-postgres');
|
|
27
|
+
expect(db.port).toBe(5432);
|
|
28
|
+
expect(db.hostPort).toBe(15432);
|
|
29
|
+
expect(db.connectionString).toBe('postgresql://postgres:testpass@localhost:15432/testdb');
|
|
30
|
+
await db.destroy();
|
|
31
|
+
});
|
|
32
|
+
test('creates a redis database with connection string', async () => {
|
|
33
|
+
const db = await database('test-redis', {
|
|
34
|
+
image: 'redis:7-alpine',
|
|
35
|
+
hostPort: 16379,
|
|
36
|
+
});
|
|
37
|
+
expect(db.port).toBe(6379);
|
|
38
|
+
expect(db.hostPort).toBe(16379);
|
|
39
|
+
expect(db.connectionString).toBe('redis://localhost:16379');
|
|
40
|
+
await db.destroy();
|
|
41
|
+
});
|
|
42
|
+
test('creates a redis database with password', async () => {
|
|
43
|
+
const db = await database('test-redis-auth', {
|
|
44
|
+
image: 'redis:7-alpine',
|
|
45
|
+
hostPort: 16380,
|
|
46
|
+
env: {
|
|
47
|
+
REDIS_PASSWORD: 'secret',
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
expect(db.connectionString).toBe('redis://:secret@localhost:16380');
|
|
51
|
+
await db.destroy();
|
|
52
|
+
});
|
|
53
|
+
test('creates a mysql database with connection string', async () => {
|
|
54
|
+
const db = await database('test-mysql', {
|
|
55
|
+
image: 'mysql:8',
|
|
56
|
+
env: {
|
|
57
|
+
MYSQL_ROOT_PASSWORD: 'rootpass',
|
|
58
|
+
MYSQL_DATABASE: 'mydb',
|
|
59
|
+
},
|
|
60
|
+
hostPort: 13306,
|
|
61
|
+
});
|
|
62
|
+
expect(db.port).toBe(3306);
|
|
63
|
+
expect(db.hostPort).toBe(13306);
|
|
64
|
+
expect(db.connectionString).toBe('mysql://root:rootpass@localhost:13306/mydb');
|
|
65
|
+
await db.destroy();
|
|
66
|
+
});
|
|
67
|
+
test('attaches database to network', async () => {
|
|
68
|
+
const network = await Network('test-db-net');
|
|
69
|
+
const db = await database('test-db-networked', {
|
|
70
|
+
image: 'redis:7-alpine',
|
|
71
|
+
networks: [network],
|
|
72
|
+
hostPort: 16381,
|
|
73
|
+
});
|
|
74
|
+
const ip = await db.getIp('test-db-net');
|
|
75
|
+
expect(ip).toBeDefined();
|
|
76
|
+
expect(ip).not.toBe('');
|
|
77
|
+
await db.destroy();
|
|
78
|
+
await network.destroy();
|
|
79
|
+
});
|
|
80
|
+
test('database inherits container methods', async () => {
|
|
81
|
+
const db = await database('test-db-methods', {
|
|
82
|
+
image: 'redis:7-alpine',
|
|
83
|
+
hostPort: 16382,
|
|
84
|
+
});
|
|
85
|
+
await db.waitForLog('Ready to accept connections', 10000);
|
|
86
|
+
const result = await db.exec(['redis-cli', 'PING']);
|
|
87
|
+
expect(result.stdout.trim()).toBe('PONG');
|
|
88
|
+
const stats = await db.stats();
|
|
89
|
+
expect(stats.cpu).toBeDefined();
|
|
90
|
+
await db.destroy();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/network.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network Resource Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Dogfoods the Network resource to verify:
|
|
5
|
+
* - Creation and destruction
|
|
6
|
+
* - Property access (id, name)
|
|
7
|
+
* - Duplicate network handling
|
|
8
|
+
*/
|
|
9
|
+
import { describe, test, expect, afterEach, setDefaultTimeout } from 'bun:test';
|
|
10
|
+
import { Network, destroyAll } from '../index';
|
|
11
|
+
setDefaultTimeout(30_000);
|
|
12
|
+
describe('Network', () => {
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
await destroyAll();
|
|
15
|
+
});
|
|
16
|
+
test('creates a network and returns resource with id and name', async () => {
|
|
17
|
+
const network = await Network('test-network-basic');
|
|
18
|
+
expect(network.id).toBeDefined();
|
|
19
|
+
expect(network.id.length).toBeGreaterThan(0);
|
|
20
|
+
expect(network.name).toBe('test-network-basic');
|
|
21
|
+
await network.destroy();
|
|
22
|
+
});
|
|
23
|
+
test('allows custom driver configuration', async () => {
|
|
24
|
+
const network = await Network('test-network-custom', {
|
|
25
|
+
driver: 'bridge',
|
|
26
|
+
});
|
|
27
|
+
expect(network.name).toBe('test-network-custom');
|
|
28
|
+
expect(network.id).toBeDefined();
|
|
29
|
+
await network.destroy();
|
|
30
|
+
});
|
|
31
|
+
test('handles creating network with same name (replaces existing)', async () => {
|
|
32
|
+
const network1 = await Network('test-network-duplicate');
|
|
33
|
+
const id1 = network1.id;
|
|
34
|
+
const network2 = await Network('test-network-duplicate');
|
|
35
|
+
const id2 = network2.id;
|
|
36
|
+
expect(id2).not.toBe(id1);
|
|
37
|
+
await network2.destroy();
|
|
38
|
+
});
|
|
39
|
+
test('destroy is idempotent', async () => {
|
|
40
|
+
const network = await Network('test-network-idempotent');
|
|
41
|
+
await network.destroy();
|
|
42
|
+
expect(network.destroy()).resolves.toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
});
|
package/dist/api/index.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Re-exports the Promise-based API for cleaner imports.
|
|
5
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';
|
|
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, type ContainerStats, type ExecOptions, type ExecResult, type LogOptions, type LogLine, } from './promise';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/api/index.d.ts.map
CHANGED
|
@@ -1 +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,
|
|
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,EAGlB,KAAK,cAAc,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,OAAO,GACb,MAAM,WAAW,CAAC"}
|
package/dist/api/promise.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* Inspired by Alchemy.run and Pulumi's IAC patterns.
|
|
8
8
|
*/
|
|
9
9
|
import Docker from 'dockerode';
|
|
10
|
+
import { type ContainerStats, type ExecOptions, type ExecResult, type LogOptions, type LogLine } from '../resources';
|
|
11
|
+
export type { ContainerStats, ExecOptions, ExecResult, LogOptions, LogLine, } from '../resources';
|
|
10
12
|
import type { Binding } from '../bindings/types';
|
|
11
13
|
export interface NetworkConfig {
|
|
12
14
|
/** Network driver (default: 'bridge') */
|
|
@@ -49,6 +51,12 @@ export interface ContainerResource {
|
|
|
49
51
|
stop(signal?: string, timeoutMs?: number): Promise<ShutdownMetadata>;
|
|
50
52
|
/** Get container IP in a network */
|
|
51
53
|
getIp(networkName: string): Promise<string>;
|
|
54
|
+
/** Get container resource statistics (CPU, memory, network) */
|
|
55
|
+
stats(): Promise<ContainerStats>;
|
|
56
|
+
/** Execute a command inside the container */
|
|
57
|
+
exec(cmd: string[], options?: Omit<ExecOptions, 'cmd'>): Promise<ExecResult>;
|
|
58
|
+
/** Stream container logs as an async iterable */
|
|
59
|
+
streamLogs(options?: LogOptions): Promise<AsyncIterable<LogLine>>;
|
|
52
60
|
/** Remove this container */
|
|
53
61
|
destroy(): Promise<void>;
|
|
54
62
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"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"}
|
package/dist/api/promise.js
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* Inspired by Alchemy.run and Pulumi's IAC patterns.
|
|
8
8
|
*/
|
|
9
9
|
import { Effect, Scope, Exit } from 'effect';
|
|
10
|
-
import { homedir } from 'os';
|
|
10
|
+
import { homedir, platform } from 'os';
|
|
11
11
|
import { existsSync } from 'fs';
|
|
12
|
+
import { spawn } from 'child_process';
|
|
12
13
|
import Docker from 'dockerode';
|
|
13
14
|
// Import Effect-based resources (internal only)
|
|
14
15
|
import { Network as EffectNetwork, Container as EffectContainer, Image as EffectImage, } from '../resources';
|
|
@@ -44,6 +45,92 @@ function getDocker() {
|
|
|
44
45
|
}
|
|
45
46
|
return dockerClient;
|
|
46
47
|
}
|
|
48
|
+
// ============ Docker Auto-Start (Alchemy.run Style) ============
|
|
49
|
+
const DOCKER_STARTUP_TIMEOUT_MS = 60_000;
|
|
50
|
+
let dockerEnsured = false;
|
|
51
|
+
/**
|
|
52
|
+
* Start Docker Desktop on macOS/Windows if not running.
|
|
53
|
+
*/
|
|
54
|
+
function startDockerDesktop() {
|
|
55
|
+
const os = platform();
|
|
56
|
+
if (os === 'darwin') {
|
|
57
|
+
console.log('[Harpoon] Starting Docker Desktop...');
|
|
58
|
+
const child = spawn('open', ['-a', 'Docker', '--background'], {
|
|
59
|
+
detached: true,
|
|
60
|
+
stdio: 'ignore',
|
|
61
|
+
});
|
|
62
|
+
child.unref();
|
|
63
|
+
}
|
|
64
|
+
else if (os === 'win32') {
|
|
65
|
+
console.log('[Harpoon] Starting Docker Desktop...');
|
|
66
|
+
const child = spawn('cmd', ['/c', 'start', '', 'Docker Desktop'], {
|
|
67
|
+
detached: true,
|
|
68
|
+
stdio: 'ignore',
|
|
69
|
+
});
|
|
70
|
+
child.unref();
|
|
71
|
+
}
|
|
72
|
+
// Linux: Docker daemon managed by systemd, no auto-start
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Wait for Docker daemon to be ready with polling.
|
|
76
|
+
*/
|
|
77
|
+
async function waitForDockerReady(socketPath, timeoutMs) {
|
|
78
|
+
const start = Date.now();
|
|
79
|
+
let lastError = null;
|
|
80
|
+
while (Date.now() - start < timeoutMs) {
|
|
81
|
+
try {
|
|
82
|
+
if (existsSync(socketPath)) {
|
|
83
|
+
const docker = new Docker({ socketPath });
|
|
84
|
+
await docker.ping();
|
|
85
|
+
console.log('[Harpoon] Docker is ready');
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
lastError = e;
|
|
91
|
+
}
|
|
92
|
+
await new Promise(r => setTimeout(r, 500));
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`[Harpoon] Docker not ready after ${timeoutMs}ms: ${lastError?.message}`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Ensure Docker is running, auto-starting if needed.
|
|
98
|
+
* This is the Alchemy.run-style declarative approach.
|
|
99
|
+
*/
|
|
100
|
+
async function ensureDockerRunning() {
|
|
101
|
+
if (dockerEnsured)
|
|
102
|
+
return;
|
|
103
|
+
const socketPath = detectDockerSocket();
|
|
104
|
+
// Check if already running
|
|
105
|
+
if (existsSync(socketPath)) {
|
|
106
|
+
try {
|
|
107
|
+
const docker = new Docker({ socketPath });
|
|
108
|
+
await docker.ping();
|
|
109
|
+
dockerEnsured = true;
|
|
110
|
+
return; // Docker is ready
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Socket exists but daemon not responding, wait for it
|
|
114
|
+
console.log('[Harpoon] Docker socket exists but daemon not responding, waiting...');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Socket doesn't exist, start Docker
|
|
119
|
+
console.log('[Harpoon] Docker not running, attempting auto-start...');
|
|
120
|
+
startDockerDesktop();
|
|
121
|
+
}
|
|
122
|
+
// Wait for Docker to be ready
|
|
123
|
+
await waitForDockerReady(socketPath, DOCKER_STARTUP_TIMEOUT_MS);
|
|
124
|
+
dockerEnsured = true;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get Docker client with auto-start support.
|
|
128
|
+
* Ensures Docker is running before returning client.
|
|
129
|
+
*/
|
|
130
|
+
async function getDockerAsync() {
|
|
131
|
+
await ensureDockerRunning();
|
|
132
|
+
return getDocker();
|
|
133
|
+
}
|
|
47
134
|
/**
|
|
48
135
|
* Set a custom Docker client.
|
|
49
136
|
* Useful for testing or custom configurations.
|
|
@@ -100,7 +187,7 @@ function registerCleanup() {
|
|
|
100
187
|
*/
|
|
101
188
|
export async function Network(name, config = {}) {
|
|
102
189
|
registerCleanup();
|
|
103
|
-
const docker =
|
|
190
|
+
const docker = await getDockerAsync();
|
|
104
191
|
const scope = Effect.runSync(Scope.make());
|
|
105
192
|
const effectConfig = {
|
|
106
193
|
name,
|
|
@@ -139,7 +226,7 @@ export async function Network(name, config = {}) {
|
|
|
139
226
|
*/
|
|
140
227
|
export async function Container(name, config) {
|
|
141
228
|
registerCleanup();
|
|
142
|
-
const docker =
|
|
229
|
+
const docker = await getDockerAsync();
|
|
143
230
|
const scope = Effect.runSync(Scope.make());
|
|
144
231
|
// Convert NetworkResource to { name: string }
|
|
145
232
|
const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
|
|
@@ -169,6 +256,15 @@ export async function Container(name, config) {
|
|
|
169
256
|
async getIp(networkName) {
|
|
170
257
|
return Effect.runPromise(effectResource.getIp(networkName));
|
|
171
258
|
},
|
|
259
|
+
async stats() {
|
|
260
|
+
return Effect.runPromise(effectResource.stats());
|
|
261
|
+
},
|
|
262
|
+
async exec(cmd, options) {
|
|
263
|
+
return Effect.runPromise(effectResource.exec({ cmd, ...options }));
|
|
264
|
+
},
|
|
265
|
+
async streamLogs(options) {
|
|
266
|
+
return Effect.runPromise(effectResource.streamLogs(options));
|
|
267
|
+
},
|
|
172
268
|
async destroy() {
|
|
173
269
|
await cleanup();
|
|
174
270
|
resources.delete(resourceId);
|
|
@@ -199,7 +295,7 @@ export async function Container(name, config) {
|
|
|
199
295
|
*/
|
|
200
296
|
export async function database(name, config) {
|
|
201
297
|
registerCleanup();
|
|
202
|
-
const docker =
|
|
298
|
+
const docker = await getDockerAsync();
|
|
203
299
|
const scope = Effect.runSync(Scope.make());
|
|
204
300
|
// Convert NetworkResource to { name: string }
|
|
205
301
|
const networks = config.networks?.map((n) => 'destroy' in n ? { name: n.name } : n);
|
|
@@ -232,6 +328,15 @@ export async function database(name, config) {
|
|
|
232
328
|
async getIp(networkName) {
|
|
233
329
|
return Effect.runPromise(effectResource.getIp(networkName));
|
|
234
330
|
},
|
|
331
|
+
async stats() {
|
|
332
|
+
return Effect.runPromise(effectResource.stats());
|
|
333
|
+
},
|
|
334
|
+
async exec(cmd, options) {
|
|
335
|
+
return Effect.runPromise(effectResource.exec({ cmd, ...options }));
|
|
336
|
+
},
|
|
337
|
+
async streamLogs(options) {
|
|
338
|
+
return Effect.runPromise(effectResource.streamLogs(options));
|
|
339
|
+
},
|
|
235
340
|
async destroy() {
|
|
236
341
|
await cleanup();
|
|
237
342
|
resources.delete(resourceId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/build-strategies/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gCAAgC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,sDAAsD;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B,6BAA6B;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtD,uCAAuC;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAElC,mDAAmD;IACnD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC,gCAAgC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,oCAAoC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,gEAAgE;IAChE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAElC,2CAA2C;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/build-strategies/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,gCAAgC;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAErB,0CAA0C;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,sDAAsD;IACtD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAE7B,6BAA6B;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAEtD,uCAAuC;IACvC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAElC,mDAAmD;IACnD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC,gCAAgC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,yCAAyC;IACzC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,oCAAoC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAEhC,gEAAgE;IAChE,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAElC,2CAA2C;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAEpC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,uBAAiC;IAC9C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC;IAEnC,YAAY,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAW/D;CACF;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,iCAAiC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,CAChB,KAAK,EAAE,kBAAkB,KACtB,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,6BAA6B;IAC7B,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,CAAC;IAE1D,8BAA8B;IAC9B,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;IAErD,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;CACxC"}
|