diff --git a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts index 64c22ced0528..9105e474d84e 100644 --- a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts @@ -625,6 +625,23 @@ describe('hasSSHProtocol', () => { expect(hasSSHProtocol(url)).toBe(true); }); + it('recognizes implied SSH protocol without a user', () => { + expect(hasSSHProtocol('github.com:facebook/docusaurus.git')).toBe(true); + // An SSH config host alias, as used for several GitHub accounts + expect(hasSSHProtocol('github-work:facebook/docusaurus.git')).toBe(true); + }); + + it('recognizes implied SSH protocol with a path relative to a home directory', () => { + expect(hasSSHProtocol('git@example.com:~git/docusaurus.git')).toBe(true); + expect(hasSSHProtocol('example.com:~/docusaurus.git')).toBe(true); + }); + + it('does not recognize a Windows drive path', () => { + expect(hasSSHProtocol('C:/projects/docusaurus.git')).toBe(false); + expect(hasSSHProtocol('C:\\projects\\docusaurus.git')).toBe(false); + expect(hasSSHProtocol('C:projects/docusaurus.git')).toBe(false); + }); + it('does not recognize HTTPS with credentials', () => { const url = 'https://user:pass@github.com/facebook/docusaurus.git'; expect(hasSSHProtocol(url)).toBe(false); diff --git a/packages/docusaurus-utils/src/urlUtils.ts b/packages/docusaurus-utils/src/urlUtils.ts index 0b661ce3d6cc..35f290130c9a 100644 --- a/packages/docusaurus-utils/src/urlUtils.ts +++ b/packages/docusaurus-utils/src/urlUtils.ts @@ -314,14 +314,24 @@ export function buildHttpsUrl( /** * Whether the current URL is an SSH protocol. In addition to looking for * `ssh:`, it will also allow protocol-less URLs like - * `git@github.com:facebook/docusaurus.git`. + * `git@github.com:facebook/docusaurus.git`, with or without the user part + * (`github.com:facebook/docusaurus.git`, or an SSH config host alias). */ export function hasSSHProtocol(sourceRepoUrl: string): boolean { const url = URL.parse(sourceRepoUrl); - if (url === null) { + // Without "://", git reads `[user@]host:path` as an SCP-style address. When + // the user part is left out, URL.parse() takes the host for a URL scheme + // (`github.com:` is a valid one), so it is checked here as well. + if (url === null || !sourceRepoUrl.includes('://')) { // Recognizes SCP-style addresses, implying SSH // Example: git@github.com:facebook/docusaurus.git - return /^(?:[\w-]+@)?[\w.-]+:[\w./-]+/.test(sourceRepoUrl); + // Example: github.com:~user/docusaurus.git + // A Windows drive path like C:/repo or C:repo is not one: like git on + // Windows, a single-letter host is read as a drive letter. + return ( + /^(?:[\w-]+@)?[\w.-]+:[\w./~-]+/.test(sourceRepoUrl) && + !/^[a-z]:/i.test(sourceRepoUrl) + ); } return url.protocol === 'ssh:'; }