Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3ea35f5
chore: update version in package.json file
dhruvparekh12 Apr 16, 2025
db9214a
fix: support validating git remote url for SSH format
dhruvparekh12 Apr 21, 2025
132b6d0
Merge pull request #46 from contentstack/CL-1633
dhruvparekh12 Apr 22, 2025
3f72e2c
fix: error handling for creating new GitHub projects
Chhavi-Mandowara Apr 23, 2025
f3a0707
Merge pull request #47 from contentstack/CL-1637
Chhavi-Mandowara Apr 25, 2025
3643777
Merge branch 'main' into resolve-dev-conflicts
Chhavi-Mandowara Apr 28, 2025
90d4322
Merge pull request #49 from contentstack/resolve-dev-conflicts
Chhavi-Mandowara Apr 28, 2025
ab66e71
Merge pull request #48 from contentstack/development
Chhavi-Mandowara Apr 28, 2025
7c3a0c8
fix: allow --type flag to create new project without prompting to sel…
Chhavi-Mandowara Apr 30, 2025
662aa08
Merge pull request #51 from contentstack/CL-1637
Chhavi-Mandowara Apr 30, 2025
4a5c1ba
Merge pull request #52 from contentstack/development
Chhavi-Mandowara May 6, 2025
3c3eca3
chore: update version in package.json file
dhruvparekh12 Apr 16, 2025
025ca09
fix: support validating git remote url for SSH format
dhruvparekh12 Apr 21, 2025
f740881
fix: error handling for creating new GitHub projects
Chhavi-Mandowara Apr 23, 2025
d84466d
fix: allow --type flag to create new project without prompting to sel…
Chhavi-Mandowara Apr 30, 2025
179c85f
Merge branch 'development' of https://github.com/contentstack/launch-…
Chhavi-Mandowara May 6, 2025
f61324c
Merge pull request #53 from contentstack/CL-1637
Chhavi-Mandowara May 6, 2025
e2e4694
Merge pull request #54 from contentstack/development
Chhavi-Mandowara May 6, 2025
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
2 changes: 2 additions & 0 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ fileignoreconfig:
- filename: .github/workflows/secrets-scan.yml
ignore_detectors:
- filecontent
- filename: src/commands/launch/index.test.ts
checksum: 9db6c02ad35a0367343cd753b916dd64db4a9efd24838201d2e1113ed19c9b62
version: "1.0"
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-launch",
"version": "1.9.0",
"version": "1.9.1",
"description": "Launch related operations",
"author": "Contentstack CLI",
"bin": {
Expand Down
1 change: 0 additions & 1 deletion src/adapters/base-class.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import BaseClass from './base-class';
import { cliux as ux, ContentstackClient } from '@contentstack/cli-utilities';
import config from '../config';
import exp from 'constants';

jest.mock('@contentstack/cli-utilities', () => ({
cliux: {
Expand Down
30 changes: 1 addition & 29 deletions src/adapters/base-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ApolloClient } from '@apollo/client/core';
import { writeFileSync, existsSync, readFileSync } from 'fs';
import { cliux as ux, ContentstackClient } from '@contentstack/cli-utilities';

import config from '../config';
import { print, GraphqlApiClient, LogPolling, getOrganizations } from '../util';
import {
branchesQuery,
Expand All @@ -31,7 +30,6 @@ import {
import {
LogFn,
ExitFn,
Providers,
ConfigType,
AdapterConstructorInputs,
EmitMessage,
Expand Down Expand Up @@ -148,31 +146,6 @@ export default class BaseClass {
await this.initApolloClient();
}

/**
* @method selectProjectType - select project type/provider/adapter
*
* @return {*} {Promise<void>}
* @memberof BaseClass
*/
async selectProjectType(): Promise<void> {
const choices = [
...map(config.supportedAdapters, (provider) => ({
value: provider,
name: `Continue with ${provider}`,
})),
{ value: 'FileUpload', name: 'Continue with FileUpload' },
];

const selectedProvider: Providers = await ux.inquire({
choices: choices,
type: 'search-list',
name: 'projectType',
message: 'Choose a project type to proceed',
});

this.config.provider = selectedProvider;
}

/**
* @method detectFramework - detect the project framework
*
Expand Down Expand Up @@ -427,7 +400,6 @@ export default class BaseClass {
* @memberof BaseClass
*/
async connectToAdapterOnUi(emit = true): Promise<void> {
await this.selectProjectType();

if (includes(this.config.supportedAdapters, this.config.provider)) {
const baseUrl = this.config.host.startsWith('http') ? this.config.host : `https://${this.config.host}`;
Expand Down Expand Up @@ -862,4 +834,4 @@ export default class BaseClass {
});
}
}
}
}
289 changes: 289 additions & 0 deletions src/adapters/github.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
import GitHub from './github';
import { getRemoteUrls } from '../util/create-git-meta';
import { repositoriesQuery, userConnectionsQuery } from '../graphql';
import BaseClass from './base-class';
import { existsSync } from 'fs';

jest.mock('../util/create-git-meta');
jest.mock('fs', () => ({
...jest.requireActual('fs'),
existsSync: jest.fn(),
}));

const userConnections = [
{
__typename: 'UserConnection',
userUid: 'testuser1',
provider: 'GitHub',
},
];
const repositories = [
{
__typename: 'GitRepository',
id: '495370701',
url: 'https://github.com/test-user/nextjs-ssr-isr-demo',
name: 'nextjs-ssr-isr-demo',
fullName: 'test-user/nextjs-ssr-isr-demo',
defaultBranch: 'main',
},
{
__typename: 'GitRepository',
id: '555341263',
url: 'https://github.com/test-user/static-site-demo',
name: 'static-site-demo',
fullName: 'test-user/static-site-demo',
defaultBranch: 'main',
},
{
__typename: 'GitRepository',
id: '647250661',
url: 'https://github.com/test-user/eleventy-sample',
name: 'eleventy-sample',
fullName: 'test-user/eleventy-sample',
defaultBranch: 'main',
},
];

describe('GitHub Adapter', () => {
let logMock: jest.Mock;
let exitMock: jest.Mock;

beforeEach(() => {
logMock = jest.fn();
exitMock = jest.fn().mockImplementationOnce(() => {
throw new Error('1');
});
});

afterEach(() => {
jest.resetAllMocks();
});

describe('checkGitHubConnected', () => {
it('should return true if GitHub is connected', async () => {
const userConnectionResponse = { data: { userConnections } };
const apolloClient = {
query: jest.fn().mockResolvedValueOnce(userConnectionResponse),
} as any;
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1', provider: 'GitHub' },
apolloClient: apolloClient,
log: logMock,
} as any);
const connectToAdapterOnUiMock = jest
.spyOn(BaseClass.prototype, 'connectToAdapterOnUi')
.mockResolvedValueOnce(undefined);

await githubAdapterInstance.checkGitHubConnected();

expect(apolloClient.query).toHaveBeenCalledWith({ query: userConnectionsQuery });
expect(logMock).toHaveBeenCalledWith('GitHub connection identified!', 'info');
expect(githubAdapterInstance.config.userConnection).toEqual(userConnections[0]);
expect(connectToAdapterOnUiMock).not.toHaveBeenCalled();
});

it('should log an error and exit if GitHub is not connected', async () => {
const userConnectionResponse = { data: { userConnections: [] } };
const connectToAdapterOnUiMock = jest.spyOn(BaseClass.prototype, 'connectToAdapterOnUi').mockResolvedValueOnce();
const apolloClient = {
query: jest.fn().mockResolvedValueOnce(userConnectionResponse),
} as any;
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1' },
apolloClient: apolloClient,
log: logMock,
} as any);

await githubAdapterInstance.checkGitHubConnected();

expect(apolloClient.query).toHaveBeenCalledWith({ query: userConnectionsQuery });
expect(logMock).toHaveBeenCalledWith('GitHub connection not found!', 'error');
expect(connectToAdapterOnUiMock).toHaveBeenCalled();
expect(githubAdapterInstance.config.userConnection).toEqual(undefined);
});
});

describe('checkGitRemoteAvailableAndValid', () => {
const repositoriesResponse = { data: { repositories } };

it(`should successfully check if the git remote is available and valid
when the github remote URL is HTTPS based`, async () => {
(existsSync as jest.Mock).mockReturnValueOnce(true);
(getRemoteUrls as jest.Mock).mockResolvedValueOnce({
origin: 'https://github.com/test-user/eleventy-sample.git',
});
const apolloClient = {
query: jest.fn().mockResolvedValueOnce(repositoriesResponse),
} as any;
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1' },
apolloClient: apolloClient,
} as any);

const result = await githubAdapterInstance.checkGitRemoteAvailableAndValid();

expect(existsSync).toHaveBeenCalledWith('/home/project1/.git');
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
expect(apolloClient.query).toHaveBeenCalledWith({ query: repositoriesQuery });
expect(githubAdapterInstance.config.repository).toEqual({
__typename: 'GitRepository',
id: '647250661',
url: 'https://github.com/test-user/eleventy-sample',
name: 'eleventy-sample',
fullName: 'test-user/eleventy-sample',
defaultBranch: 'main',
});
expect(result).toBe(true);
});

it(`should successfully check if the git remote is available and valid
when the github remote URL is SSH based`, async () => {
(existsSync as jest.Mock).mockReturnValueOnce(true);
(getRemoteUrls as jest.Mock).mockResolvedValueOnce({
origin: 'git@github.com:test-user/eleventy-sample.git',
});
const apolloClient = {
query: jest.fn().mockResolvedValueOnce(repositoriesResponse),
} as any;
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1' },
apolloClient: apolloClient,
} as any);

const result = await githubAdapterInstance.checkGitRemoteAvailableAndValid();

expect(existsSync).toHaveBeenCalledWith('/home/project1/.git');
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
expect(apolloClient.query).toHaveBeenCalledWith({ query: repositoriesQuery });
expect(githubAdapterInstance.config.repository).toEqual({
__typename: 'GitRepository',
id: '647250661',
url: 'https://github.com/test-user/eleventy-sample',
name: 'eleventy-sample',
fullName: 'test-user/eleventy-sample',
defaultBranch: 'main',
});
expect(result).toBe(true);
});

it('should log an error and exit if git config file does not exists', async () => {
(existsSync as jest.Mock).mockReturnValueOnce(false);
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1' },
log: logMock,
exit: exitMock,
} as any);
let err;

try {
await githubAdapterInstance.checkGitRemoteAvailableAndValid();
} catch (error: any) {
err = error;
}

expect(getRemoteUrls as jest.Mock).not.toHaveBeenCalled();
expect(logMock).toHaveBeenCalledWith('No Git repository configuration found at /home/project1.', 'error');
expect(logMock).toHaveBeenCalledWith(
'Please initialize a Git repository and try again, or use the File Upload option.',
'error',
);
expect(exitMock).toHaveBeenCalledWith(1);
expect(err).toEqual(new Error('1'));
});

it(`should log an error if git repo remote url
is unavailable and exit`, async () => {
(existsSync as jest.Mock).mockReturnValueOnce(true);
(getRemoteUrls as jest.Mock).mockResolvedValueOnce(undefined);
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1' },
log: logMock,
exit: exitMock,
} as any);
let err;

try {
await githubAdapterInstance.checkGitRemoteAvailableAndValid();
} catch (error: any) {
err = error;
}

expect(existsSync).toHaveBeenCalledWith('/home/project1/.git');
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
expect(logMock).toHaveBeenCalledWith(
`No Git remote origin URL found for the repository at /home/project1.
Please add a git remote origin url and try again`,
'error',
);
expect(exitMock).toHaveBeenCalledWith(1);
expect(err).toEqual(new Error('1'));
expect(githubAdapterInstance.config.repository).toBeUndefined();
});

it('should log an error and exit if GitHub app is uninstalled', async () => {
(existsSync as jest.Mock).mockReturnValueOnce(true);
(getRemoteUrls as jest.Mock).mockResolvedValueOnce({
origin: 'https://github.com/test-user/eleventy-sample.git',
});
const apolloClient = {
query: jest.fn().mockRejectedValue(new Error('GitHub app error')),
} as any;
const connectToAdapterOnUiMock = jest.spyOn(BaseClass.prototype, 'connectToAdapterOnUi').mockResolvedValueOnce();
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1' },
apolloClient: apolloClient,
log: logMock,
exit: exitMock,
} as any);
let err;

try {
await githubAdapterInstance.checkGitRemoteAvailableAndValid();
} catch (error: any) {
err = error;
}

expect(existsSync).toHaveBeenCalledWith('/home/project1/.git');
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
expect(apolloClient.query).toHaveBeenCalled();
expect(connectToAdapterOnUiMock).toHaveBeenCalled();
expect(logMock).toHaveBeenCalledWith('GitHub app uninstalled. Please reconnect the app and try again', 'error');
expect(exitMock).toHaveBeenCalledWith(1);
expect(err).toEqual(new Error('1'));
});

it('should log an error and exit if repository is not found in the list of available repositories', async () => {
(existsSync as jest.Mock).mockReturnValueOnce(true);
(getRemoteUrls as jest.Mock).mockResolvedValueOnce({
origin: 'https://github.com/test-user/test-repo-2.git',
});
const apolloClient = {
query: jest.fn().mockResolvedValueOnce(repositoriesResponse),
} as any;
const githubAdapterInstance = new GitHub({
config: { projectBasePath: '/home/project1' },
log: logMock,
exit: exitMock,
apolloClient: apolloClient,
} as any);
let err;

try {
await githubAdapterInstance.checkGitRemoteAvailableAndValid();
} catch (error: any) {
err = error;
}

expect(existsSync).toHaveBeenCalledWith('/home/project1/.git');
expect(getRemoteUrls as jest.Mock).toHaveBeenCalledWith('/home/project1/.git/config');
expect(apolloClient.query).toHaveBeenCalledWith({ query: repositoriesQuery });
expect(logMock).toHaveBeenCalledWith(
'Repository not added to the GitHub app. Please add it to the app’s repository access list and try again.',
'error',
);
expect(exitMock).toHaveBeenCalledWith(1);
expect(err).toEqual(new Error('1'));
expect(githubAdapterInstance.config.repository).toBeUndefined();
});
});
});
Loading