Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions packages/cli/src/commands/extensions/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand Down
29 changes: 28 additions & 1 deletion packages/cli/src/config/extension-manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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://');
});
});
});
9 changes: 8 additions & 1 deletion packages/cli/src/config/extension-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).`,
);
}
}
}
Expand Down
Loading