@akash-chowdhury-24/deployhub 2.0.43 → 2.0.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akash-chowdhury-24/deployhub",
3
- "version": "2.0.43",
3
+ "version": "2.0.44",
4
4
  "description": "Zero-configuration deployment and artifact manager",
5
5
  "type": "module",
6
6
  "main": "./src/cli/index.js",
@@ -218,7 +218,24 @@ export function createDockerProvider(config, envName, env = process.env) {
218
218
  await session.exec(ssh, cmds.pull, {
219
219
  timeoutMs: Math.max(session.defaultExecTimeoutMs, 300_000),
220
220
  });
221
- await session.exec(ssh, cmds.run);
221
+ // `docker rm -f` can return before the name is fully released on a
222
+ // contended dockerd (`--rm` + stop). Re-rm immediately before run and
223
+ // retry name conflicts instead of failing a good deploy.
224
+ let lastRunErr;
225
+ for (let attempt = 1; attempt <= 3; attempt++) {
226
+ await session.exec(ssh, cmds.rm);
227
+ try {
228
+ await session.exec(ssh, cmds.run);
229
+ lastRunErr = null;
230
+ break;
231
+ } catch (err) {
232
+ lastRunErr = err;
233
+ const msg = err instanceof Error ? err.message : String(err);
234
+ if (!/already in use|Conflict/i.test(msg) || attempt === 3) throw err;
235
+ await new Promise((r) => setTimeout(r, 1000 * attempt));
236
+ }
237
+ }
238
+ if (lastRunErr) throw lastRunErr;
222
239
  const inspected = await session.execUnchecked(
223
240
  ssh,
224
241
  buildDockerInspectPortsCommand(containerName)
@@ -80,7 +80,8 @@ export function createSshProvider(config, envName, env = process.env) {
80
80
  const { connect, exec, defaultExecTimeoutMs } = session;
81
81
  const startStopTimeoutMs = Math.min(
82
82
  defaultExecTimeoutMs,
83
- Number(env.DEPLOYHUB_SSH_START_TIMEOUT_MS) || 60_000
83
+ Number(env.DEPLOYHUB_SSH_START_TIMEOUT_MS || process.env.DEPLOYHUB_SSH_START_TIMEOUT_MS) ||
84
+ 60_000
84
85
  );
85
86
 
86
87
  /**
@@ -28,8 +28,12 @@ export function createSshExecSession(opts) {
28
28
  const log = opts.log || { info() {}, error() {} };
29
29
 
30
30
  // Defense-in-depth: never let a stuck SSH channel hang CI indefinitely.
31
- // Override with DEPLOYHUB_SSH_EXEC_TIMEOUT_MS (ms).
32
- const defaultExecTimeoutMs = Number(env.DEPLOYHUB_SSH_EXEC_TIMEOUT_MS) || 120_000;
31
+ // Override with DEPLOYHUB_SSH_EXEC_TIMEOUT_MS (ms). Production default is 120s.
32
+ // Fall back to process.env so sparse credential overlays in tests/CI do not
33
+ // drop a process-level timeout override.
34
+ const defaultExecTimeoutMs =
35
+ Number(env.DEPLOYHUB_SSH_EXEC_TIMEOUT_MS || process.env.DEPLOYHUB_SSH_EXEC_TIMEOUT_MS) ||
36
+ 120_000;
33
37
 
34
38
  async function connect() {
35
39
  if (!host || !user) {
@@ -109,15 +109,36 @@ export function createDockerImageDeployContext(config, env = process.env, log) {
109
109
  if (!hasRegistryCredentials()) return;
110
110
  const registry = registryUrl || 'https://index.docker.io/v1/';
111
111
  log.info('Logging in to container registry...');
112
- await execa(
113
- 'docker',
114
- ['login', registry, '-u', registryUser, '--password-stdin'],
115
- {
116
- input: registryToken,
117
- stdio: ['pipe', 'inherit', 'inherit'],
118
- env: getDockerEnv(),
112
+ const attempts = 3;
113
+ const loopback = /^(127\.0\.0\.1|localhost|\[::1\])/i.test(String(registry));
114
+ let lastErr;
115
+ for (let i = 1; i <= attempts; i++) {
116
+ try {
117
+ await execa(
118
+ 'docker',
119
+ ['login', registry, '-u', registryUser, '--password-stdin'],
120
+ {
121
+ input: registryToken,
122
+ stdio: ['pipe', 'pipe', 'pipe'],
123
+ env: getDockerEnv(),
124
+ timeout: 60_000,
125
+ }
126
+ );
127
+ return;
128
+ } catch (err) {
129
+ lastErr = err;
130
+ const execErr = /** @type {{ message?: string, stderr?: string, stdout?: string }} */ (err);
131
+ const combined = `${execErr?.message || ''} ${execErr?.stderr || ''} ${execErr?.stdout || ''}`;
132
+ const transient =
133
+ /timeout|deadline exceeded|connection refused|tls handshake|i\/o timeout|eof|reset by peer|502|503/i.test(
134
+ combined
135
+ );
136
+ if (i === attempts || (!transient && !loopback)) throw err;
137
+ log.warn(`docker login attempt ${i}/${attempts} failed, retrying...`);
138
+ await new Promise((r) => setTimeout(r, 2000 * i));
119
139
  }
120
- );
140
+ }
141
+ throw lastErr;
121
142
  }
122
143
 
123
144
  /**