Skip to content

Commit 025ca09

Browse files
fix: support validating git remote url for SSH format
Co-authored-by: Chhavi-Mandowara <chhavi.mandowara@contentstack.com>
1 parent 3c3eca3 commit 025ca09

File tree

2 files changed

+189
-3
lines changed

2 files changed

+189
-3
lines changed

src/adapters/github.test.ts

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import GitHub from './github';
2+
import { getRemoteUrls } from '../util/create-git-meta';
3+
import { repositoriesQuery } from '../graphql';
4+
import BaseClass from './base-class';
5+
6+
jest.mock('../util/create-git-meta');
7+
8+
const repositories = [
9+
{
10+
__typename: 'GitRepository',
11+
id: '495370701',
12+
url: 'https://github.com/test-user/nextjs-ssr-isr-demo',
13+
name: 'nextjs-ssr-isr-demo',
14+
fullName: 'test-user/nextjs-ssr-isr-demo',
15+
defaultBranch: 'main',
16+
},
17+
{
18+
__typename: 'GitRepository',
19+
id: '555341263',
20+
url: 'https://github.com/test-user/static-site-demo',
21+
name: 'static-site-demo',
22+
fullName: 'test-user/static-site-demo',
23+
defaultBranch: 'main',
24+
},
25+
{
26+
__typename: 'GitRepository',
27+
id: '647250661',
28+
url: 'https://github.com/test-user/eleventy-sample',
29+
name: 'eleventy-sample',
30+
fullName: 'test-user/eleventy-sample',
31+
defaultBranch: 'main',
32+
},
33+
];
34+
35+
describe('GitHub Adapter', () => {
36+
describe('checkGitRemoteAvailableAndValid', () => {
37+
const repositoriesResponse = { data: { repositories } };
38+
let logMock: jest.Mock;
39+
let exitMock: jest.Mock;
40+
41+
beforeEach(() => {
42+
logMock = jest.fn();
43+
exitMock = jest.fn().mockImplementationOnce(() => {
44+
throw new Error('1');
45+
});
46+
});
47+
48+
afterEach(() => {
49+
jest.resetAllMocks();
50+
});
51+
52+
it(`should successfully check if the git remote is available and valid
53+
when the github remote URL is HTTPS based`, async () => {
54+
(getRemoteUrls as jest.Mock).mockResolvedValueOnce({
55+
origin: 'https://github.com/test-user/eleventy-sample.git',
56+
});
57+
const apolloClient = {
58+
query: jest.fn().mockResolvedValueOnce(repositoriesResponse),
59+
} as any;
60+
const githubAdapterInstance = new GitHub({
61+
config: { projectBasePath: '/home/project1' },
62+
apolloClient: apolloClient,
63+
} as any);
64+
65+
const result = await githubAdapterInstance.checkGitRemoteAvailableAndValid();
66+
67+
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
68+
expect(apolloClient.query).toHaveBeenCalledWith({ query: repositoriesQuery });
69+
expect(githubAdapterInstance.config.repository).toEqual({
70+
__typename: 'GitRepository',
71+
id: '647250661',
72+
url: 'https://github.com/test-user/eleventy-sample',
73+
name: 'eleventy-sample',
74+
fullName: 'test-user/eleventy-sample',
75+
defaultBranch: 'main',
76+
});
77+
expect(result).toBe(true);
78+
});
79+
80+
it(`should successfully check if the git remote is available and valid
81+
when the github remote URL is SSH based`, async () => {
82+
(getRemoteUrls as jest.Mock).mockResolvedValueOnce({
83+
origin: 'git@github.com:test-user/eleventy-sample.git',
84+
});
85+
const apolloClient = {
86+
query: jest.fn().mockResolvedValueOnce(repositoriesResponse),
87+
} as any;
88+
const githubAdapterInstance = new GitHub({
89+
config: { projectBasePath: '/home/project1' },
90+
apolloClient: apolloClient,
91+
} as any);
92+
93+
const result = await githubAdapterInstance.checkGitRemoteAvailableAndValid();
94+
95+
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
96+
expect(apolloClient.query).toHaveBeenCalledWith({ query: repositoriesQuery });
97+
expect(githubAdapterInstance.config.repository).toEqual({
98+
__typename: 'GitRepository',
99+
id: '647250661',
100+
url: 'https://github.com/test-user/eleventy-sample',
101+
name: 'eleventy-sample',
102+
fullName: 'test-user/eleventy-sample',
103+
defaultBranch: 'main',
104+
});
105+
expect(result).toBe(true);
106+
});
107+
108+
it(`should log an error and proceed to connection via UI
109+
if git repo remote url is unavailable and exit`, async () => {
110+
(getRemoteUrls as jest.Mock).mockResolvedValueOnce(undefined);
111+
const connectToAdapterOnUiMock
112+
= jest.spyOn(BaseClass.prototype, 'connectToAdapterOnUi').mockResolvedValueOnce(undefined);
113+
const githubAdapterInstance = new GitHub({
114+
config: { projectBasePath: '/home/project1' },
115+
log: logMock,
116+
exit: exitMock
117+
} as any);
118+
let err;
119+
120+
try {
121+
await githubAdapterInstance.checkGitRemoteAvailableAndValid();
122+
} catch (error: any) {
123+
err = error;
124+
}
125+
126+
127+
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
128+
expect(logMock).toHaveBeenCalledWith('GitHub project not identified!', 'error');
129+
expect(connectToAdapterOnUiMock).toHaveBeenCalled();
130+
expect(exitMock).toHaveBeenCalledWith(1);
131+
expect(err).toEqual(new Error('1'));
132+
expect(githubAdapterInstance.config.repository).toBeUndefined();
133+
});
134+
135+
it('should log an error and exit if repository is not found in the list of available repositories', async () => {
136+
(getRemoteUrls as jest.Mock).mockResolvedValueOnce({
137+
origin: 'https://github.com/test-user/test-repo-2.git',
138+
});
139+
const apolloClient = {
140+
query: jest.fn().mockResolvedValueOnce(repositoriesResponse),
141+
} as any;
142+
const githubAdapterInstance = new GitHub({
143+
config: { projectBasePath: '/home/project1' },
144+
log: logMock,
145+
exit: exitMock,
146+
apolloClient: apolloClient,
147+
} as any);
148+
let err;
149+
150+
try {
151+
await githubAdapterInstance.checkGitRemoteAvailableAndValid();
152+
} catch (error: any) {
153+
err = error;
154+
}
155+
156+
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
157+
expect(apolloClient.query).toHaveBeenCalledWith({ query: repositoriesQuery });
158+
expect(logMock).toHaveBeenCalledWith('Repository not found in the list!', 'error');
159+
expect(exitMock).toHaveBeenCalledWith(1);
160+
expect(err).toEqual(new Error('1'));
161+
expect(githubAdapterInstance.config.repository).toBeUndefined();
162+
});
163+
});
164+
});

src/adapters/github.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import omit from 'lodash/omit';
44
import find from 'lodash/find';
55
import split from 'lodash/split';
66
import { exec } from 'child_process';
7-
import replace from 'lodash/replace';
87
import includes from 'lodash/includes';
98
import { configHandler, cliux as ux } from '@contentstack/cli-utilities';
109

@@ -267,27 +266,49 @@ export default class GitHub extends BaseClass {
267266
return this.config.userConnection;
268267
}
269268

269+
private extractRepoFullNameFromGithubRemoteURL(url: string) {
270+
let match;
271+
272+
// HTTPS format: https://github.com/owner/repo.git
273+
match = url.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)(\.git)?$/);
274+
if (match) {
275+
return `${match[1]}/${match[2].replace(/\.git$/, '')}`;
276+
}
277+
278+
// SSH format: git@github.com:owner/repo.git
279+
match = url.match(/^git@github\.com:([^/]+)\/([^/]+)(\.git)?$/);
280+
if (match) {
281+
return `${match[1]}/${match[2].replace(/\.git$/, '')}`;
282+
}
283+
}
284+
285+
270286
/**
271287
* @method checkGitRemoteAvailableAndValid - GitHub repository verification
272288
*
273289
* @return {*} {(Promise<boolean | void>)}
274290
* @memberof GitHub
275291
*/
276292
async checkGitRemoteAvailableAndValid(): Promise<boolean | void> {
277-
const localRemoteUrl = (await getRemoteUrls(resolve(this.config.projectBasePath, '.git/config')))?.origin || '';
293+
const gitConfigFilePath = resolve(this.config.projectBasePath, '.git/config');
294+
const remoteUrls = await getRemoteUrls(gitConfigFilePath);
295+
const localRemoteUrl = remoteUrls?.origin || '';
278296

279297
if (!localRemoteUrl) {
280298
this.log('GitHub project not identified!', 'error');
281299
await this.connectToAdapterOnUi();
300+
this.exit(1);
282301
}
283302

284303
const repositories = await this.apolloClient
285304
.query({ query: repositoriesQuery })
286305
.then(({ data: { repositories } }) => repositories)
287306
.catch((error) => this.log(error, 'error'));
288307

308+
const repoFullName = this.extractRepoFullNameFromGithubRemoteURL(localRemoteUrl);
309+
289310
this.config.repository = find(repositories, {
290-
url: replace(localRemoteUrl, '.git', ''),
311+
fullName: repoFullName,
291312
});
292313

293314
if (!this.config.repository) {
@@ -306,6 +327,7 @@ export default class GitHub extends BaseClass {
306327
*/
307328
async checkUserGitHubAccess(): Promise<boolean> {
308329
return new Promise<boolean>((resolve, reject) => {
330+
// eslint-disable-next-line @typescript-eslint/no-this-alias
309331
const self = this;
310332
const defaultBranch = this.config.repository?.defaultBranch;
311333
if (!defaultBranch) return reject('Branch not found');

0 commit comments

Comments
 (0)