@docker-harpoon/core 0.2.0 → 0.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"promise.d.ts","sourceRoot":"","sources":["../../src/api/promise.ts"],"names":[],"mappings":"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"}
@@ -7,44 +7,15 @@
7
7
  * Inspired by Harpoon and Pulumi's IAC patterns.
8
8
  */
9
9
  import { Effect, Scope, Exit } from 'effect';
10
- import { homedir, platform } from 'os';
11
- import { existsSync } from 'fs';
12
- import { spawn } from 'child_process';
13
- import Docker from 'dockerode';
14
10
  // Import Effect-based resources (internal only)
15
11
  import { Network as EffectNetwork, Container as EffectContainer, Image as EffectImage, } from '../resources';
16
12
  import { database as effectDatabase, } from '../helpers';
13
+ import { createDockerClient, DockerResolutionError, formatDockerConnectionFailure, resolveDockerConnection, } from '../docker-resolver';
17
14
  // Binding type is used as-is from internal types
18
15
  // Pre-built bindings like PrismaBinding handle Effect internally
19
16
  // Users creating simple bindings can use createEnvBinding()
20
17
  // ============ Docker Client Management ============
21
18
  let dockerClient = null;
22
- /**
23
- * Detect Docker socket path for the current platform.
24
- */
25
- function detectDockerSocket() {
26
- const candidates = ['/var/run/docker.sock', `${homedir()}/.docker/run/docker.sock`];
27
- for (const path of candidates) {
28
- if (existsSync(path)) {
29
- return path;
30
- }
31
- }
32
- // Windows uses named pipes, let dockerode handle it
33
- return '/var/run/docker.sock';
34
- }
35
- /**
36
- * Get the Docker client, auto-detecting the socket if needed.
37
- */
38
- function getDocker() {
39
- if (!dockerClient) {
40
- const socketPath = detectDockerSocket();
41
- dockerClient = new Docker({ socketPath });
42
- }
43
- return dockerClient;
44
- }
45
- // ============ Docker Auto-Start (Harpoon Style) ============
46
- const DOCKER_STARTUP_TIMEOUT_MS = 60_000;
47
- let dockerEnsured = false;
48
19
  /**
49
20
  * Simple logging helper for promise-based API.
50
21
  */
@@ -59,87 +30,38 @@ const harpoonLog = {
59
30
  error: (message, err) => console.error(`[Harpoon] ${message}`, err ?? ''),
60
31
  };
61
32
  /**
62
- * Start Docker Desktop on macOS/Windows if not running.
33
+ * Get Docker client with shared endpoint discovery.
63
34
  */
64
- function startDockerDesktop() {
65
- const os = platform();
66
- if (os === 'darwin') {
67
- harpoonLog.info('Starting Docker Desktop...');
68
- const child = spawn('open', ['-a', 'Docker', '--background'], {
69
- detached: true,
70
- stdio: 'ignore',
71
- });
72
- child.unref();
35
+ async function getDockerAsync() {
36
+ if (dockerClient) {
37
+ return dockerClient;
73
38
  }
74
- else if (os === 'win32') {
75
- harpoonLog.info('Starting Docker Desktop...');
76
- const child = spawn('cmd', ['/c', 'start', '', 'Docker Desktop'], {
77
- detached: true,
78
- stdio: 'ignore',
79
- });
80
- child.unref();
39
+ let resolution;
40
+ try {
41
+ resolution = resolveDockerConnection();
81
42
  }
82
- // Linux: Docker daemon managed by systemd, no auto-start
83
- }
84
- /**
85
- * Wait for Docker daemon to be ready with polling.
86
- */
87
- async function waitForDockerReady(socketPath, timeoutMs) {
88
- const start = Date.now();
89
- let lastError = null;
90
- while (Date.now() - start < timeoutMs) {
91
- try {
92
- if (existsSync(socketPath)) {
93
- const docker = new Docker({ socketPath });
94
- await docker.ping();
95
- harpoonLog.info('Docker is ready');
96
- return;
97
- }
43
+ catch (error) {
44
+ if (error instanceof DockerResolutionError) {
45
+ const lines = [
46
+ 'Failed to resolve Docker endpoint.',
47
+ error.message,
48
+ 'Harpoon will not launch Docker (desktop, engine, or other runtimes) automatically. Start your configured Docker runtime, or set DOCKER_SOCKET, DOCKER_HOST, or DOCKER_CONTEXT to a local Docker endpoint.',
49
+ 'You can also inject a Docker client with setDocker().',
50
+ ];
51
+ throw new Error(lines.join('\n'));
98
52
  }
99
- catch (e) {
100
- lastError = e;
101
- }
102
- await new Promise((r) => setTimeout(r, 500));
53
+ throw error;
103
54
  }
104
- throw new Error(`Docker not ready after ${timeoutMs}ms: ${lastError?.message}`);
105
- }
106
- /**
107
- * Ensure Docker is running, auto-starting if needed.
108
- * This is the Harpoon-style declarative approach.
109
- */
110
- async function ensureDockerRunning() {
111
- if (dockerEnsured)
112
- return;
113
- const socketPath = detectDockerSocket();
114
- // Check if already running
115
- if (existsSync(socketPath)) {
116
- try {
117
- const docker = new Docker({ socketPath });
118
- await docker.ping();
119
- dockerEnsured = true;
120
- return; // Docker is ready
121
- }
122
- catch {
123
- // Socket exists but daemon not responding, wait for it
124
- harpoonLog.debug('Docker socket exists but daemon not responding, waiting...');
125
- }
55
+ const docker = createDockerClient(resolution);
56
+ try {
57
+ await docker.ping();
126
58
  }
127
- else {
128
- // Socket doesn't exist, start Docker
129
- harpoonLog.info('Docker not running, attempting auto-start...');
130
- startDockerDesktop();
59
+ catch (error) {
60
+ throw new Error(formatDockerConnectionFailure(resolution, error));
131
61
  }
132
- // Wait for Docker to be ready
133
- await waitForDockerReady(socketPath, DOCKER_STARTUP_TIMEOUT_MS);
134
- dockerEnsured = true;
135
- }
136
- /**
137
- * Get Docker client with auto-start support.
138
- * Ensures Docker is running before returning client.
139
- */
140
- async function getDockerAsync() {
141
- await ensureDockerRunning();
142
- return getDocker();
62
+ harpoonLog.debug(`Connected to Docker daemon via ${resolution.sourceDescription}`);
63
+ dockerClient = docker;
64
+ return dockerClient;
143
65
  }
144
66
  /**
145
67
  * Set a custom Docker client.
@@ -0,0 +1,36 @@
1
+ import Docker from 'dockerode';
2
+ interface DockerResolutionFileSystem {
3
+ existsSync: (filePath: string) => boolean;
4
+ readFileSync: (filePath: string) => string;
5
+ readdirSync: (directoryPath: string) => string[];
6
+ }
7
+ export interface ResolveDockerConnectionOptions {
8
+ readonly explicitSocketPath?: string;
9
+ readonly env?: NodeJS.ProcessEnv;
10
+ readonly homeDir?: string;
11
+ readonly platform?: NodeJS.Platform;
12
+ readonly fs?: DockerResolutionFileSystem;
13
+ }
14
+ export interface DockerConnectionResolution {
15
+ readonly dockerOptions: Docker.DockerOptions;
16
+ readonly endpoint: string;
17
+ readonly socketPath: string;
18
+ readonly source: string;
19
+ readonly sourceDescription: string;
20
+ readonly triedEndpoints: readonly string[];
21
+ }
22
+ export declare class DockerResolutionError extends Error {
23
+ readonly source: string;
24
+ readonly triedEndpoints: readonly string[];
25
+ readonly cause: Error | undefined;
26
+ constructor(message: string, options: {
27
+ source: string;
28
+ triedEndpoints?: readonly string[];
29
+ cause?: unknown;
30
+ });
31
+ }
32
+ export declare function resolveDockerConnection(options?: ResolveDockerConnectionOptions): DockerConnectionResolution;
33
+ export declare function createDockerClient(options?: ResolveDockerConnectionOptions | DockerConnectionResolution): Docker;
34
+ export declare function formatDockerConnectionFailure(resolution: DockerConnectionResolution, cause: unknown, injectedClientHint?: string): string;
35
+ export {};
36
+ //# sourceMappingURL=docker-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docker-resolver.d.ts","sourceRoot":"","sources":["../src/docker-resolver.ts"],"names":[],"mappings":"AAGA,OAAO,MAAM,MAAM,WAAW,CAAC;AAS/B,UAAU,0BAA0B;IAClC,UAAU,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3C,WAAW,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,MAAM,EAAE,CAAC;CAClD;AAQD,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,EAAE,0BAA0B,CAAC;CAC1C;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,aAAa,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5C;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAE3C,SAAkB,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IAE3C,YACE,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACnC,KAAK,CAAC,EAAE,OAAO,CAAC;KACjB,EAWF;CACF;AAyND,wBAAgB,uBAAuB,CACrC,OAAO,GAAE,8BAAmC,GAC3C,0BAA0B,CAqE5B;AAED,wBAAgB,kBAAkB,CAChC,OAAO,GAAE,8BAA8B,GAAG,0BAA+B,GACxE,MAAM,CAIR;AAED,wBAAgB,6BAA6B,CAC3C,UAAU,EAAE,0BAA0B,EACtC,KAAK,EAAE,OAAO,EACd,kBAAkB,SAAgB,GACjC,MAAM,CAiBR"}
@@ -0,0 +1,214 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'fs';
2
+ import { homedir, platform as osPlatform } from 'os';
3
+ import path from 'path';
4
+ import Docker from 'dockerode';
5
+ const WINDOWS_DOCKER_PIPE = '//./pipe/docker_engine';
6
+ // Docker's built-in "default" context has no on-disk metadata in
7
+ // ~/.docker/contexts/meta/ and must be treated the same as having no context
8
+ // configured, falling through to the local socket candidate search.
9
+ const DOCKER_DEFAULT_CONTEXT = 'default';
10
+ const defaultFileSystem = {
11
+ existsSync,
12
+ readFileSync: (filePath) => readFileSync(filePath, 'utf-8'),
13
+ readdirSync: (directoryPath) => readdirSync(directoryPath),
14
+ };
15
+ export class DockerResolutionError extends Error {
16
+ source;
17
+ triedEndpoints;
18
+ cause;
19
+ constructor(message, options) {
20
+ super(message);
21
+ this.name = 'DockerResolutionError';
22
+ this.source = options.source;
23
+ this.triedEndpoints = options.triedEndpoints ?? [];
24
+ this.cause = asError(options.cause);
25
+ if (Error.captureStackTrace) {
26
+ Error.captureStackTrace(this, DockerResolutionError);
27
+ }
28
+ }
29
+ }
30
+ const asError = (value) => {
31
+ if (value === undefined) {
32
+ return undefined;
33
+ }
34
+ return value instanceof Error ? value : new Error(String(value));
35
+ };
36
+ const buildResolutionError = (message, source, triedEndpoints = [], cause) => new DockerResolutionError(message, {
37
+ source,
38
+ triedEndpoints,
39
+ cause,
40
+ });
41
+ const getLocalDockerSocketCandidates = (homeDirectory, platform) => {
42
+ if (platform === 'win32') {
43
+ return [WINDOWS_DOCKER_PIPE];
44
+ }
45
+ const candidates = ['/var/run/docker.sock'];
46
+ if (homeDirectory) {
47
+ candidates.push(`${homeDirectory}/.docker/run/docker.sock`, `${homeDirectory}/.docker/desktop/docker.sock`, `${homeDirectory}/.rd/docker.sock`, `${homeDirectory}/.colima/default/docker.sock`, `${homeDirectory}/.orbstack/run/docker.sock`);
48
+ }
49
+ return candidates;
50
+ };
51
+ const normalizePipePath = (value) => {
52
+ const withForwardSlashes = value.replace(/\\/g, '/');
53
+ if (withForwardSlashes.startsWith('//./pipe/')) {
54
+ return withForwardSlashes;
55
+ }
56
+ const trimmed = withForwardSlashes.replace(/^\/+/, '');
57
+ if (trimmed.startsWith('./pipe/')) {
58
+ return `//${trimmed}`;
59
+ }
60
+ if (trimmed.startsWith('pipe/')) {
61
+ return `//./${trimmed}`;
62
+ }
63
+ return withForwardSlashes;
64
+ };
65
+ const normalizeDockerHost = (value, sourceDescription) => {
66
+ if (value.startsWith('unix://')) {
67
+ const encoded = value.slice('unix://'.length);
68
+ try {
69
+ return decodeURIComponent(encoded);
70
+ }
71
+ catch (cause) {
72
+ throw buildResolutionError(`Malformed percent-encoding in ${sourceDescription} value "${value}". The path portion "${encoded}" is not valid URI-encoded text.`, sourceDescription, [value], cause);
73
+ }
74
+ }
75
+ if (value.startsWith('npipe://')) {
76
+ return normalizePipePath(value.slice('npipe://'.length));
77
+ }
78
+ const hasUriScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
79
+ if (hasUriScheme) {
80
+ throw buildResolutionError(`Unsupported ${sourceDescription} value "${value}". Harpoon currently supports only local Docker endpoints via raw socket paths, unix://, or npipe://.`, sourceDescription, [value]);
81
+ }
82
+ if (value.startsWith('\\\\.\\pipe\\') || value.startsWith('//./pipe/')) {
83
+ return normalizePipePath(value);
84
+ }
85
+ return value;
86
+ };
87
+ const makeResolution = (socketPath, endpoint, source, sourceDescription, triedEndpoints) => ({
88
+ dockerOptions: { socketPath },
89
+ endpoint,
90
+ socketPath,
91
+ source,
92
+ sourceDescription,
93
+ triedEndpoints,
94
+ });
95
+ const resolveDirectSocket = (value, source, sourceDescription) => {
96
+ const socketPath = normalizeDockerHost(value, sourceDescription);
97
+ return makeResolution(socketPath, value, source, sourceDescription, [socketPath]);
98
+ };
99
+ const readJson = (filePath, fileSystem) => {
100
+ if (!fileSystem.existsSync(filePath)) {
101
+ return undefined;
102
+ }
103
+ try {
104
+ return JSON.parse(fileSystem.readFileSync(filePath));
105
+ }
106
+ catch {
107
+ return undefined;
108
+ }
109
+ };
110
+ const readCurrentDockerContext = (homeDirectory, fileSystem) => {
111
+ if (!homeDirectory) {
112
+ return undefined;
113
+ }
114
+ const configPath = path.join(homeDirectory, '.docker', 'config.json');
115
+ const config = readJson(configPath, fileSystem);
116
+ if (!config) {
117
+ return undefined;
118
+ }
119
+ const currentContext = config['currentContext'];
120
+ return typeof currentContext === 'string' &&
121
+ currentContext.length > 0 &&
122
+ currentContext !== DOCKER_DEFAULT_CONTEXT
123
+ ? currentContext
124
+ : undefined;
125
+ };
126
+ const resolveDockerContext = (contextName, source, sourceDescription, homeDirectory, fileSystem) => {
127
+ if (!homeDirectory) {
128
+ throw buildResolutionError(`Unable to resolve ${sourceDescription}. No home directory is available for Docker context lookup.`, source);
129
+ }
130
+ const contextsDirectory = path.join(homeDirectory, '.docker', 'contexts', 'meta');
131
+ let entries;
132
+ try {
133
+ entries = fileSystem.readdirSync(contextsDirectory);
134
+ }
135
+ catch (error) {
136
+ throw buildResolutionError(`Unable to resolve ${sourceDescription}. Docker context metadata was not found at ${contextsDirectory}.`, source, [contextsDirectory], error);
137
+ }
138
+ for (const entry of entries) {
139
+ const metaPath = path.join(contextsDirectory, entry, 'meta.json');
140
+ const meta = readJson(metaPath, fileSystem);
141
+ if (!meta || meta['Name'] !== contextName) {
142
+ continue;
143
+ }
144
+ const endpoints = meta['Endpoints'];
145
+ const dockerEndpoint = endpoints && typeof endpoints === 'object'
146
+ ? endpoints['docker']
147
+ : undefined;
148
+ const host = dockerEndpoint && typeof dockerEndpoint === 'object'
149
+ ? dockerEndpoint['Host']
150
+ : undefined;
151
+ if (typeof host !== 'string' || host.length === 0) {
152
+ throw buildResolutionError(`Docker context "${contextName}" does not define a Docker host endpoint.`, source, [metaPath]);
153
+ }
154
+ const socketPath = normalizeDockerHost(host, sourceDescription);
155
+ return makeResolution(socketPath, host, source, sourceDescription, [socketPath]);
156
+ }
157
+ throw buildResolutionError(`Docker context "${contextName}" was not found in ${contextsDirectory}.`, source, [contextsDirectory]);
158
+ };
159
+ export function resolveDockerConnection(options = {}) {
160
+ const fileSystem = options.fs ?? defaultFileSystem;
161
+ const env = options.env ?? process.env;
162
+ const homeDirectory = options.homeDir ?? homedir();
163
+ const platform = options.platform ?? osPlatform();
164
+ if (options.explicitSocketPath) {
165
+ return resolveDirectSocket(options.explicitSocketPath, 'explicitSocketPath', 'explicit socket override');
166
+ }
167
+ const dockerSocket = env.DOCKER_SOCKET;
168
+ if (dockerSocket) {
169
+ return resolveDirectSocket(dockerSocket, 'DOCKER_SOCKET', 'DOCKER_SOCKET');
170
+ }
171
+ const dockerHost = env.DOCKER_HOST;
172
+ if (dockerHost) {
173
+ return resolveDirectSocket(dockerHost, 'DOCKER_HOST', 'DOCKER_HOST');
174
+ }
175
+ const dockerContext = env.DOCKER_CONTEXT;
176
+ if (dockerContext && dockerContext !== DOCKER_DEFAULT_CONTEXT) {
177
+ return resolveDockerContext(dockerContext, 'DOCKER_CONTEXT', `DOCKER_CONTEXT=${dockerContext}`, homeDirectory, fileSystem);
178
+ }
179
+ const currentContext = readCurrentDockerContext(homeDirectory, fileSystem);
180
+ if (currentContext) {
181
+ return resolveDockerContext(currentContext, 'currentContext', `Docker currentContext=${currentContext}`, homeDirectory, fileSystem);
182
+ }
183
+ const candidates = getLocalDockerSocketCandidates(homeDirectory, platform);
184
+ let resolvedSocketPath;
185
+ const triedEndpoints = [];
186
+ for (const candidatePath of candidates) {
187
+ triedEndpoints.push(candidatePath);
188
+ if (fileSystem.existsSync(candidatePath)) {
189
+ resolvedSocketPath = candidatePath;
190
+ break;
191
+ }
192
+ }
193
+ if (resolvedSocketPath === undefined) {
194
+ resolvedSocketPath = candidates[0];
195
+ }
196
+ return makeResolution(resolvedSocketPath, resolvedSocketPath, 'fallback', 'fallback socket search', triedEndpoints);
197
+ }
198
+ export function createDockerClient(options = {}) {
199
+ const resolution = 'dockerOptions' in options ? options : resolveDockerConnection(options);
200
+ return new Docker(resolution.dockerOptions);
201
+ }
202
+ export function formatDockerConnectionFailure(resolution, cause, injectedClientHint = 'setDocker()') {
203
+ const lines = ['Failed to connect to Docker daemon.'];
204
+ lines.push(`Source: ${resolution.sourceDescription}`);
205
+ lines.push(`Endpoint: ${resolution.endpoint}`);
206
+ lines.push(`Tried: ${resolution.triedEndpoints.join(', ')}`);
207
+ const error = asError(cause);
208
+ if (error?.message) {
209
+ lines.push(`Cause: ${error.message}`);
210
+ }
211
+ lines.push('Harpoon will not launch Docker (desktop, engine, or other runtimes) automatically. Start your configured Docker runtime, or set DOCKER_SOCKET, DOCKER_HOST, or DOCKER_CONTEXT to a local Docker endpoint.');
212
+ lines.push(`You can also inject a Docker client with ${injectedClientHint}.`);
213
+ return lines.join('\n');
214
+ }
package/dist/index.d.ts CHANGED
@@ -28,6 +28,7 @@
28
28
  * - BuildBinding interface for Dockerfile transformations
29
29
  */
30
30
  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, type VolumeMapping, } from './api';
31
+ export { createDockerClient, resolveDockerConnection, formatDockerConnectionFailure, DockerResolutionError, type ResolveDockerConnectionOptions, type DockerConnectionResolution, } from './docker-resolver';
31
32
  export type { Binding, BuildBinding, BindingsEnv, FlatBindingsEnv } from './bindings';
32
33
  export { isBuildBinding, mergeBindingsEnv, createEnvBinding } from './bindings';
33
34
  export { HarpoonError, NetworkError, ImageError, ContainerError, ScopeError, BindingError, toHarpoonError, asCause, isHarpoonError, isNetworkError, isImageError, isContainerError, isScopeError, isBindingError, } from './errors';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,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,EACZ,KAAK,aAAa,GACnB,MAAM,OAAO,CAAC;AAIf,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEtF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIhF,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,UAAU,EACV,YAAY,EACZ,cAAc,EACd,OAAO,EACP,cAAc,EACd,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,8BAA8B,EAC9B,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,MAAM,EACN,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAChB,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,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,EACZ,KAAK,aAAa,GACnB,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,6BAA6B,EAC7B,qBAAqB,EACrB,KAAK,8BAA8B,EACnC,KAAK,0BAA0B,GAChC,MAAM,mBAAmB,CAAC;AAI3B,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEtF,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAIhF,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,cAAc,EACd,UAAU,EACV,YAAY,EACZ,cAAc,EACd,OAAO,EACP,cAAc,EACd,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,cAAc,GACf,MAAM,UAAU,CAAC;AAElB,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAIhD,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,YAAY,EACZ,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,aAAa,EACb,YAAY,EACZ,WAAW,EACX,qBAAqB,EACrB,YAAY,GACb,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,8BAA8B,EAC9B,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,cAAc,EACd,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,WAAW,EACX,MAAM,EACN,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,mBAAmB,EACnB,iBAAiB,EACjB,eAAe,GAChB,MAAM,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ export {
33
33
  Network, Container, database, Image,
34
34
  // Docker client management
35
35
  setDocker, resetDocker, destroyAll, } from './api';
36
+ export { createDockerClient, resolveDockerConnection, formatDockerConnectionFailure, DockerResolutionError, } from './docker-resolver';
36
37
  export { isBuildBinding, mergeBindingsEnv, createEnvBinding } from './bindings';
37
38
  // ============ Errors ============
38
39
  export { HarpoonError, NetworkError, ImageError, ContainerError, ScopeError, BindingError, toHarpoonError, asCause, isHarpoonError, isNetworkError, isImageError, isContainerError, isScopeError, isBindingError, } from './errors';
@@ -1 +1 @@
1
- {"version":3,"file":"DockerClient.d.ts","sourceRoot":"","sources":["../../src/services/DockerClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DockerClient.d.ts","sourceRoot":"","sources":["../../src/services/DockerClient.ts"],"names":[],"mappings":"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"}
@@ -6,8 +6,7 @@
6
6
  */
7
7
  import { Context, Effect, Layer } from 'effect';
8
8
  import Docker from 'dockerode';
9
- import { existsSync } from 'fs';
10
- import { homedir, platform } from 'os';
9
+ import { formatDockerConnectionFailure, resolveDockerConnection } from '../docker-resolver';
11
10
  import { HarpoonConfig } from './HarpoonConfig';
12
11
  import { DockerConnectionError, wrapAsDockerError, isTransientError, } from './DockerErrors';
13
12
  import { DockerRateLimiter, DockerRateLimiterLive, dockerRetrySchedule } from './DockerRateLimiter';
@@ -16,28 +15,6 @@ import { DockerRateLimiter, DockerRateLimiterLive, dockerRetrySchedule } from '.
16
15
  */
17
16
  export class DockerClient extends Context.Tag('@harpoon/DockerClient')() {
18
17
  }
19
- /**
20
- * Detect Docker socket path for the current platform.
21
- */
22
- function detectDockerSocket() {
23
- const candidates = [
24
- '/var/run/docker.sock',
25
- `${homedir()}/.docker/run/docker.sock`,
26
- // Colima on macOS
27
- `${homedir()}/.colima/default/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
- if (platform() === 'win32') {
36
- return '//./pipe/docker_engine';
37
- }
38
- // Default fallback
39
- return '/var/run/docker.sock';
40
- }
41
18
  /**
42
19
  * Helper to add retry logic for transient errors.
43
20
  */
@@ -58,14 +35,23 @@ const withRetryOnTransient = (effect) => effect.pipe(Effect.retry({
58
35
  export const DockerClientLive = Layer.scoped(DockerClient, Effect.gen(function* () {
59
36
  const config = yield* HarpoonConfig;
60
37
  const rateLimiter = yield* DockerRateLimiter;
61
- // Determine socket path
62
- const socketPath = config.dockerSocket ?? detectDockerSocket();
38
+ const resolution = yield* Effect.try({
39
+ try: () => resolveDockerConnection({
40
+ ...(config.dockerSocket !== undefined && {
41
+ explicitSocketPath: config.dockerSocket,
42
+ }),
43
+ }),
44
+ catch: (e) => new DockerConnectionError(e instanceof Error ? e.message : String(e), {
45
+ cause: e,
46
+ }),
47
+ });
48
+ const socketPath = resolution.socketPath;
63
49
  // Create Docker client
64
- const docker = new Docker({ socketPath });
50
+ const docker = new Docker(resolution.dockerOptions);
65
51
  // Verify connection on startup (with retry for transient errors)
66
52
  yield* Effect.tryPromise({
67
53
  try: () => docker.ping(),
68
- catch: (e) => new DockerConnectionError(`Failed to connect to Docker daemon`, {
54
+ catch: (e) => new DockerConnectionError(formatDockerConnectionFailure(resolution, e, 'a custom DockerClient layer'), {
69
55
  socketPath,
70
56
  cause: e,
71
57
  }),
@@ -79,7 +65,7 @@ export const DockerClientLive = Layer.scoped(DockerClient, Effect.gen(function*
79
65
  client: docker,
80
66
  ping: () => rateLimiter.withPermit(Effect.tryPromise({
81
67
  try: () => docker.ping(),
82
- catch: (e) => new DockerConnectionError(`Docker ping failed`, {
68
+ catch: (e) => new DockerConnectionError(formatDockerConnectionFailure(resolution, e, 'a custom DockerClient layer'), {
83
69
  socketPath,
84
70
  cause: e,
85
71
  }),
@@ -24,7 +24,7 @@ export interface HarpoonConfigService {
24
24
  readonly retryAttempts: number;
25
25
  /** Delay between retry attempts */
26
26
  readonly retryDelay: Duration.Duration;
27
- /** Docker startup timeout (for auto-start on macOS/Windows) */
27
+ /** Legacy Docker startup timeout setting. Currently unused. */
28
28
  readonly dockerStartupTimeout: Duration.Duration;
29
29
  }
30
30
  declare const HarpoonConfig_base: Context.TagClass<HarpoonConfig, "@harpoon/HarpoonConfig", HarpoonConfigService>;
@@ -44,7 +44,7 @@ export declare class HarpoonConfig extends HarpoonConfig_base {
44
44
  * - HARPOON_PARALLEL_LIMIT: Max parallel operations (default: 4)
45
45
  * - HARPOON_RETRY_ATTEMPTS: Retry attempts (default: 3)
46
46
  * - HARPOON_RETRY_DELAY: Retry delay in milliseconds (default: 1000)
47
- * - HARPOON_DOCKER_STARTUP_TIMEOUT: Docker startup timeout in milliseconds (default: 60000)
47
+ * - HARPOON_DOCKER_STARTUP_TIMEOUT: Legacy Docker startup timeout in milliseconds (default: 60000)
48
48
  */
49
49
  export declare const HarpoonConfigLive: Layer.Layer<HarpoonConfig, import("effect/ConfigError").ConfigError, never>;
50
50
  /**
@@ -21,7 +21,7 @@ export class HarpoonConfig extends Context.Tag('@harpoon/HarpoonConfig')() {
21
21
  * - HARPOON_PARALLEL_LIMIT: Max parallel operations (default: 4)
22
22
  * - HARPOON_RETRY_ATTEMPTS: Retry attempts (default: 3)
23
23
  * - HARPOON_RETRY_DELAY: Retry delay in milliseconds (default: 1000)
24
- * - HARPOON_DOCKER_STARTUP_TIMEOUT: Docker startup timeout in milliseconds (default: 60000)
24
+ * - HARPOON_DOCKER_STARTUP_TIMEOUT: Legacy Docker startup timeout in milliseconds (default: 60000)
25
25
  */
26
26
  export const HarpoonConfigLive = Layer.effect(HarpoonConfig, Effect.gen(function* () {
27
27
  // Load all configuration with defaults
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docker-harpoon/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Core Docker resource primitives and binding interface",
5
5
  "license": "MIT",
6
6
  "files": [