diff --git a/packages/cli/src/commands/extensions/install.test.ts b/packages/cli/src/commands/extensions/install.test.ts index 8b3f8c5807f..4db78548a20 100644 --- a/packages/cli/src/commands/extensions/install.test.ts +++ b/packages/cli/src/commands/extensions/install.test.ts @@ -227,14 +227,16 @@ describe('handleInstall', () => { }); it('throws an error from an unknown source', async () => { - mockInferInstallMetadata.mockRejectedValue( - new Error('Install source not found.'), - ); + const errorMessage = + 'Install source not found: "test://google.com". If you are installing ' + + 'from GitHub, provide the full repository URL ' + + '(e.g. "https://github.com/owner/repo").'; + mockInferInstallMetadata.mockRejectedValue(new Error(errorMessage)); await handleInstall({ source: 'test://google.com', }); - expect(debugErrorSpy).toHaveBeenCalledWith('Install source not found.'); + expect(debugErrorSpy).toHaveBeenCalledWith(errorMessage); expect(processSpy).toHaveBeenCalledWith(1); }); diff --git a/packages/cli/src/config/extension-manager.test.ts b/packages/cli/src/config/extension-manager.test.ts index 33c335c16ba..5547da1f6b3 100644 --- a/packages/cli/src/config/extension-manager.test.ts +++ b/packages/cli/src/config/extension-manager.test.ts @@ -8,7 +8,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { ExtensionManager } from './extension-manager.js'; +import { ExtensionManager, inferInstallMetadata } from './extension-manager.js'; import { createTestMergedSettings, type MergedSettings } from './settings.js'; import { createExtension } from '../test-utils/createExtension.js'; import { EXTENSIONS_DIRECTORY_NAME } from './extensions/variables.js'; @@ -637,4 +637,31 @@ describe('ExtensionManager', () => { ); }); }); + + describe('inferInstallMetadata', () => { + it('throws a helpful error when a local install source is not found', async () => { + const missingSource = path.join(tempHomeDir, 'does-not-exist'); + + await expect(inferInstallMetadata(missingSource)).rejects.toThrow( + /Install source not found/, + ); + }); + + it('suggests the GitHub URL form and an auth hint in the error', async () => { + const missingSource = path.join(tempHomeDir, 'does-not-exist'); + + let thrown: Error | undefined; + try { + await inferInstallMetadata(missingSource); + } catch (e) { + thrown = e as Error; + } + + expect(thrown).toBeDefined(); + expect(thrown!.message).toContain(missingSource); + expect(thrown!.message).toContain('https://github.com/owner/repo'); + expect(thrown!.message.toLowerCase()).toContain('authenticated'); + expect(thrown!.message).toContain('sso://'); + }); + }); }); diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index ded72510fc7..5585bb2d9ac 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -1325,7 +1325,14 @@ export async function inferInstallMetadata( type: 'local', }; } catch { - throw new Error('Install source not found.'); + throw new Error( + `Install source not found: "${source}". ` + + `If you are installing from GitHub, provide the full repository URL ` + + `(e.g. "https://github.com/owner/repo"). ` + + `For private or SSO-protected repositories, make sure you are ` + + `authenticated (e.g. via an SSH key with the appropriate SSO ` + + `authorization, or by using an "sso://" URL).`, + ); } } }