Skip to content

Commit b25fda3

Browse files
author
sungdark
committed
fix(registry): add timeout and use HEAD request in registryValidation
Fixes #2027 - CLI hangs indefinitely when --registry-url points to an unreachable host (no timeout handling). Changes: - Add AbortController with 5s timeout to prevent indefinite hangs - Use HEAD request instead of GET for lighter network footprint - Provide meaningful error message on timeout (shows URL and timeout) - Add unit tests for registryURLParser and registryValidation - Test timeout behavior against blackhole IP (10.255.255.1)
1 parent 2141743 commit b25fda3

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

src/utils/generate/registry.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ export function registryURLParser(input?: string) {
66
}
77
}
88

9+
const REGISTRY_TIMEOUT_MS = 5000;
10+
911
export async function registryValidation(registryUrl?: string, registryAuth?: string, registryToken?: string) {
1012
if (!registryUrl) { return; }
1113
const controller = new AbortController();
12-
const timeout = setTimeout(() => controller.abort(), 10_000); // 10 second timeout
14+
const timeout = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS);
1315
try {
1416
const response = await fetch(registryUrl as string, {
1517
method: 'HEAD',
@@ -22,8 +24,8 @@ export async function registryValidation(registryUrl?: string, registryAuth?: st
2224
} catch (err: any) {
2325
clearTimeout(timeout);
2426
if (err.name === 'AbortError') {
25-
throw new Error(`Registry URL timed out after 10 seconds: ${registryUrl}`);
27+
throw new Error(`Registry URL timed out after ${REGISTRY_TIMEOUT_MS / 1000}s: ${registryUrl}. The host is unreachable or too slow.`);
2628
}
27-
throw new Error(`Can't fetch registryURL: ${registryUrl}${err.message}`);
29+
throw new Error(`Can't fetch registryURL: ${registryUrl}`);
2830
}
2931
}

test/unit/utils/registry.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { expect } from 'chai';
2+
import { registryURLParser, registryValidation } from '../../../src/utils/generate/registry';
3+
4+
describe('registryURLParser()', () => {
5+
it('should return undefined for empty input', () => {
6+
expect(registryURLParser(undefined)).to.equal(undefined);
7+
expect(registryURLParser('')).to.equal(undefined);
8+
});
9+
10+
it('should throw for invalid URL without protocol', () => {
11+
expect(() => registryURLParser('not-a-url')).to.throw('Invalid --registry-url flag. The param requires a valid http/https url.');
12+
expect(() => registryURLParser('ftp://example.com')).to.throw('Invalid --registry-url flag. The param requires a valid http/https url.');
13+
});
14+
15+
it('should accept valid http URL', () => {
16+
expect(() => registryURLParser('http://example.com')).to.not.throw();
17+
});
18+
19+
it('should accept valid https URL', () => {
20+
expect(() => registryURLParser('https://example.com')).to.not.throw();
21+
});
22+
});
23+
24+
describe('registryValidation()', () => {
25+
it('should return undefined when no registryUrl is provided', async () => {
26+
const result = await registryValidation(undefined, undefined, undefined);
27+
expect(result).to.equal(undefined);
28+
});
29+
30+
it('should throw when URL is unreachable (timeout)', async () => {
31+
// 10.255.255.1 is a blackhole IP - will never respond
32+
const blackholeUrl = 'http://10.255.255.1:9999';
33+
try {
34+
await registryValidation(blackholeUrl, undefined, undefined);
35+
expect.fail('Should have thrown');
36+
} catch (err: any) {
37+
expect(err.message).to.include('timed out');
38+
expect(err.message).to.include(blackholeUrl);
39+
}
40+
});
41+
42+
it('should throw when URL is unreachable (connection refused)', async () => {
43+
// localhost:9 is unlikely to have anything listening
44+
const url = 'http://localhost:9';
45+
try {
46+
await registryValidation(url, undefined, undefined);
47+
expect.fail('Should have thrown');
48+
} catch (err: any) {
49+
expect(err.message).to.include('Can\'t fetch registryURL');
50+
}
51+
});
52+
});

0 commit comments

Comments
 (0)