diff --git a/src/core/plugin.ts b/src/core/plugin.ts index e6e0c80..3ca6eb8 100644 --- a/src/core/plugin.ts +++ b/src/core/plugin.ts @@ -157,7 +157,8 @@ export async function fetchPlugin( } const { owner, repo } = parsed; - const cachePath = getPluginCachePath(owner, repo, branch); + const effectiveBranch = branch ?? parsed.branch; + const cachePath = getPluginCachePath(owner, repo, effectiveBranch); // Return cached result if this repo was already fetched this session const cached = fetchCache.get(cachePath); @@ -170,7 +171,7 @@ export async function fetchPlugin( owner, repo, offline, - branch, + effectiveBranch, deps, ); fetchCache.set(cachePath, promise); diff --git a/tests/unit/core/plugin.test.ts b/tests/unit/core/plugin.test.ts index 3e16c3a..44ef287 100644 --- a/tests/unit/core/plugin.test.ts +++ b/tests/unit/core/plugin.test.ts @@ -56,6 +56,42 @@ describe('fetchPlugin', () => { expect(result.cachePath).toContain('owner-repo'); }); + it('uses the branch encoded in a deep GitHub URL', async () => { + existsSyncMock.mockReturnValueOnce(false); + + const result = await fetchPlugin( + 'https://github.com/owner/repo/blob/main/skills/example', + {}, + deps, + ); + + expect(result.success).toBe(true); + expect(result.cachePath).toContain('owner-repo@main'); + expect(cloneToMock).toHaveBeenCalledWith( + 'https://github.com/owner/repo.git', + expect.stringContaining('owner-repo@main'), + 'main', + ); + }); + + it('prefers an explicit branch override to the URL branch', async () => { + existsSyncMock.mockReturnValueOnce(false); + + const result = await fetchPlugin( + 'https://github.com/owner/repo/blob/main/skills/example', + { branch: 'release' }, + deps, + ); + + expect(result.success).toBe(true); + expect(result.cachePath).toContain('owner-repo@release'); + expect(cloneToMock).toHaveBeenCalledWith( + 'https://github.com/owner/repo.git', + expect.stringContaining('owner-repo@release'), + 'release', + ); + }); + it('should handle authentication errors', async () => { existsSyncMock.mockReturnValueOnce(false); cloneToMock.mockRejectedValueOnce(