From d459e4327b5d990cae78d7dde3bf82d0ffe6b1de Mon Sep 17 00:00:00 2001 From: shengxinzhe Date: Mon, 16 Mar 2026 15:37:49 +0000 Subject: [PATCH 1/2] fix: add timeout to registry URL validation to prevent CLI hang When --registry-url points to an unreachable host, the CLI hangs indefinitely because fetch() has no timeout or AbortController. Changes: - Add 5s timeout via AbortController to registryValidation() - Use HEAD instead of GET for lightweight validation - Provide clear error message distinguishing timeout from other errors - Add unit tests for registryURLParser and registryValidation Fixes #2027 --- src/utils/generate/registry.ts | 18 ++++++++++-- test/unit/utils/registry.test.ts | 47 ++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 test/unit/utils/registry.test.ts diff --git a/src/utils/generate/registry.ts b/src/utils/generate/registry.ts index 16fdda2e5..be1d1abaf 100644 --- a/src/utils/generate/registry.ts +++ b/src/utils/generate/registry.ts @@ -1,3 +1,5 @@ +const REGISTRY_TIMEOUT_MS = 5000; + export function registryURLParser(input?: string) { if (!input) { return; } const isURL = /^https?:/; @@ -8,12 +10,24 @@ export function registryURLParser(input?: string) { export async function registryValidation(registryUrl?: string, registryAuth?: string, registryToken?: string) { if (!registryUrl) { return; } + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), REGISTRY_TIMEOUT_MS); + try { - const response = await fetch(registryUrl as string); + const response = await fetch(registryUrl as string, { + method: 'HEAD', + signal: controller.signal, + }); if (response.status === 401 && !registryAuth && !registryToken) { throw new Error('You Need to pass either registryAuth in username:password encoded in Base64 or need to pass registryToken'); } - } catch { + } catch (error: unknown) { + if (error instanceof Error && error.name === 'AbortError') { + throw new Error(`Registry URL timed out after ${REGISTRY_TIMEOUT_MS / 1000}s: ${registryUrl}`); + } throw new Error(`Can't fetch registryURL: ${registryUrl}`); + } finally { + clearTimeout(timeoutId); } } diff --git a/test/unit/utils/registry.test.ts b/test/unit/utils/registry.test.ts new file mode 100644 index 000000000..64d69f24b --- /dev/null +++ b/test/unit/utils/registry.test.ts @@ -0,0 +1,47 @@ +import { expect } from 'chai'; +import { registryURLParser, registryValidation } from '../../../src/utils/generate/registry'; + +describe('registryURLParser()', () => { + it('should return undefined for no input', () => { + expect(registryURLParser()).to.be.undefined; + expect(registryURLParser('')).to.be.undefined; + }); + + it('should accept valid http URLs', () => { + expect(registryURLParser('http://registry.example.com')).to.be.undefined; + expect(registryURLParser('https://registry.example.com')).to.be.undefined; + }); + + it('should throw for invalid URLs', () => { + expect(() => registryURLParser('ftp://example.com')).to.throw('Invalid --registry-url flag'); + expect(() => registryURLParser('not-a-url')).to.throw('Invalid --registry-url flag'); + }); +}); + +describe('registryValidation()', () => { + it('should return undefined for no input', async () => { + const result = await registryValidation(); + expect(result).to.be.undefined; + }); + + it('should throw a timeout error for unreachable hosts', async () => { + // 10.255.255.1 is a non-routable IP that will cause a timeout + try { + await registryValidation('http://10.255.255.1'); + expect.fail('Should have thrown'); + } catch (error: unknown) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.match(/timed out|Can't fetch/); + } + }).timeout(10000); + + it('should throw for invalid/unreachable URLs', async () => { + try { + await registryValidation('http://localhost:1'); + expect.fail('Should have thrown'); + } catch (error: unknown) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Can\'t fetch registryURL'); + } + }).timeout(10000); +}); From cf698d7455e45ce4544c28e6d388bfdf0f83d4b4 Mon Sep 17 00:00:00 2001 From: shengxinzhe Date: Mon, 16 Mar 2026 16:03:36 +0000 Subject: [PATCH 2/2] chore: add changeset for registry URL timeout fix Co-Authored-By: Claude Opus 4.6 --- .changeset/fix-registry-url-timeout.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-registry-url-timeout.md diff --git a/.changeset/fix-registry-url-timeout.md b/.changeset/fix-registry-url-timeout.md new file mode 100644 index 000000000..5921fb1d7 --- /dev/null +++ b/.changeset/fix-registry-url-timeout.md @@ -0,0 +1,5 @@ +--- +"@asyncapi/cli": patch +--- + +fix: add timeout to registry URL validation to prevent CLI hang (#2027)