Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
bfb9032
Delete .github/workflows/issues-jira.yml
Aravind-Kumar-cstk Jun 30, 2026
c6ecdc1
feat: Add Contentstack Authentication toggle to project creation.
chhavi-mandowara-cstk Jul 10, 2026
e2ab44b
Merge pull request #195 from contentstack/cl-4504
chhavi-mandowara-cstk Jul 13, 2026
66458c3
feat: add support for choosing namespace in case of multiple github c…
rohan-agrawal-cs Jul 17, 2026
8912d7a
Merge pull request #196 from contentstack/cl-4549
rohan-agrawal-cs Jul 20, 2026
1bdc05e
fix: update GitHub connection URL to direct users to connected accoun…
rohan-agrawal-cs Jul 20, 2026
f8db3ed
Merge pull request #198 from contentstack/cl-4549
rohan-agrawal-cs Jul 20, 2026
0d79389
chore: bump cli-command to 1.8.4 and cli-utilities to 1.18.5
Harshi-Shah-CS Jul 21, 2026
de0bdf5
Merge pull request #200 from contentstack/cl-snyk-test
Harshi-Shah-CS Jul 21, 2026
29d681f
feat: Add Contentstack Authentication toggle to project creation.
chhavi-mandowara-cstk Jul 10, 2026
ff7ecf6
feat: add support for choosing namespace in case of multiple github c…
rohan-agrawal-cs Jul 17, 2026
3c8161e
fix: update GitHub connection URL to direct users to connected accoun…
rohan-agrawal-cs Jul 20, 2026
bc821f9
chore: bump cli-command to 1.8.4 and cli-utilities to 1.18.5
Harshi-Shah-CS Jul 21, 2026
5beb5ff
Merge pull request #204 from contentstack/cl-new-dev
chhavi-mandowara-cstk Jul 23, 2026
09f6b0c
fix: Update get Server logs query to GetServerlessLogsV2 to match the…
AryanBansal-launch Jul 24, 2026
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
6 changes: 5 additions & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,9 @@ fileignoreconfig:
- filename: test/unit/commands/rollback.test.ts
checksum: d1f931f2d9a397131409399ad6463653e28b5a2224e870b641d9ba57c4418f18
- filename: package-lock.json
checksum: ffb77989c6fe554cdd79ebe0cbd07c0ce4329dd65b3e9a9089db4abb7b9be2b3
checksum: c94ce0fbbbedcbad4dceef8168971831ed82aad85824db579f2497dcd49aecab
- filename: src/adapters/github.test.ts
checksum: b3d3d3a3c14adde103152d2ac4e1c4b7121a5f7533a87c3cc600f6d25fb59d1b
- filename: src/adapters/file-upload.test.ts
checksum: 962fd8b45578914926d20857ddc3d1bc2fa19e8b192c6dfdc03d214ae1898b59
version: "1.0"
1,893 changes: 656 additions & 1,237 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-launch",
"version": "1.11.1",
"version": "1.11.2",
"description": "Launch related operations",
"author": "Contentstack CLI",
"bin": {
Expand All @@ -21,8 +21,8 @@
],
"dependencies": {
"@apollo/client": "^3.14.0",
"@contentstack/cli-command": "^1.8.2",
"@contentstack/cli-utilities": "^1.18.3",
"@contentstack/cli-command": "^1.8.4",
"@contentstack/cli-utilities": "^1.18.5",
"@oclif/core": "^4.2.7",
"@oclif/plugin-help": "^6.2.25",
"@rollup/plugin-commonjs": "^28.0.2",
Expand All @@ -31,7 +31,7 @@
"@rollup/plugin-typescript": "^12.1.2",
"@types/express": "^4.17.21",
"@types/express-serve-static-core": "^4.17.34",
"adm-zip": "^0.5.16",
"adm-zip": "^0.5.18",
"chalk": "^4.1.2",
"cross-fetch": "^4.1.0",
"dotenv": "^16.4.7",
Expand Down
67 changes: 67 additions & 0 deletions src/adapters/base-class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,4 +685,71 @@ describe('BaseClass', () => {
expect(exitMock).toHaveBeenCalledWith(1);
});
});

describe('detectFramework', () => {
it('should scope the framework query to the selected GitHub connection namespace', async () => {
const apolloClient = {
query: jest.fn().mockResolvedValueOnce({ data: { framework: { framework: 'GATSBY' } } }),
} as any;
baseClass = new BaseClass({
log: logMock,
exit: exitMock,
apolloClient,
config: {
provider: 'GitHub',
listOfFrameWorks: [],
repository: { fullName: 'test-user/eleventy-sample', defaultBranch: 'main' },
userConnection: { provider: 'GitHub', namespace: 'org-account' },
},
} as any);
(ux.inquire as jest.Mock).mockResolvedValueOnce('GATSBY');

await baseClass.detectFramework();

expect(apolloClient.query).toHaveBeenCalledWith({
query: expect.anything(),
variables: {
query: {
provider: 'GitHub',
repoName: 'test-user/eleventy-sample',
branchName: 'main',
namespace: 'org-account',
},
},
});
});
});

describe('selectBranch', () => {
it('should scope the branches query to the selected GitHub connection namespace', async () => {
const apolloClient = {
query: jest.fn().mockResolvedValueOnce({
data: { branches: { edges: [], pageData: { page: 1 }, pageInfo: { hasNextPage: false } } },
}),
} as any;
baseClass = new BaseClass({
log: logMock,
exit: exitMock,
apolloClient,
config: {
provider: 'GitHub',
flags: {},
repository: { fullName: 'test-user/eleventy-sample' },
userConnection: { provider: 'GitHub', namespace: 'org-account' },
},
} as any);
(ux.inquire as jest.Mock).mockResolvedValueOnce('main');

await baseClass.selectBranch();

expect(apolloClient.query).toHaveBeenCalledWith({
query: expect.anything(),
variables: {
page: 1,
first: 100,
query: { provider: 'GitHub', repoName: 'test-user/eleventy-sample', namespace: 'org-account' },
},
});
});
});
});
6 changes: 5 additions & 1 deletion src/adapters/base-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ export default class BaseClass {
*/
async detectFramework(): Promise<void> {
const { fullName, defaultBranch } = this.config.repository || {};
const namespace = this.config.userConnection?.namespace;
const query = this.config.provider === 'FileUpload' ? fileFrameworkQuery : frameworkQuery;
const variables =
this.config.provider === 'FileUpload'
Expand All @@ -171,6 +172,7 @@ export default class BaseClass {
provider: this.config.provider,
repoName: fullName,
branchName: defaultBranch,
...(namespace ? { namespace } : {}),
},
};
this.config.framework = (await this.apolloClient
Expand Down Expand Up @@ -414,7 +416,7 @@ export default class BaseClass {
if (includes(this.config.supportedAdapters, this.config.provider)) {
const baseUrl = this.config.host.startsWith('http') ? this.config.host : `https://${this.config.host}`;

const gitHubConnectUrl = `${baseUrl.replace('api', 'app').replace('io', 'com')}/#!/launch`;
const gitHubConnectUrl = `${baseUrl.replace('api', 'app').replace('io', 'com')}/#!/launch/settings/connected-accounts`;
this.log(`You can connect your ${this.config.provider} account to the UI using the following URL:`, 'info');
this.log(gitHubConnectUrl, { color: 'green' });
open(gitHubConnectUrl);
Expand Down Expand Up @@ -464,12 +466,14 @@ export default class BaseClass {
* @memberof BaseClass
*/
async selectBranch(): Promise<void> {
const namespace = this.config.userConnection?.namespace;
const variables = {
page: 1,
first: 100,
query: {
provider: this.config.provider,
repoName: this.config.repository?.fullName,
...(namespace ? { namespace } : {}),
},
};

Expand Down
154 changes: 154 additions & 0 deletions src/adapters/file-upload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,160 @@ describe('FileUpload Adapter', () => {
uploadFileMock.mockRestore();
handleEnvImportFlowMock.mockRestore();
});

it.each([
['a "yes" answer maps to enabled', true, true],
['a "no" answer maps to disabled', false, false],
])(
'should prompt Enable Contentstack Authentication (default enabled) when disable-cs-auth is not provided — %s',
async (_label, answer, expected) => {
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');
(cliux.inquire as jest.Mock).mockResolvedValueOnce(answer);

const createSignedUploadUrlMock = jest
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
const archiveMock = jest
.spyOn(FileUpload.prototype as any, 'archive')
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
const uploadFileMock = jest
.spyOn(FileUpload.prototype as any, 'uploadFile')
.mockResolvedValue(undefined);

const fileUploadInstance = new FileUpload({
config: {
flags: {
'response-mode': 'buffered',
},
framework: 'GATSBY',
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
outputDirectories: { GATSBY: './public' },
},
log: logMock,
exit: exitMock,
} as any);

const handleEnvImportFlowMock = jest
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
.mockResolvedValue(undefined);

await fileUploadInstance.prepareAndUploadNewProjectFile();

expect(cliux.inquire).toHaveBeenCalledWith(
expect.objectContaining({
type: 'confirm',
name: 'contentstackAuth',
default: true,
}),
);
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(expected);

createSignedUploadUrlMock.mockRestore();
archiveMock.mockRestore();
uploadFileMock.mockRestore();
handleEnvImportFlowMock.mockRestore();
},
);


it('should disable Contentstack Authentication without prompt when --disable-cs-auth is passed', async () => {
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');

const createSignedUploadUrlMock = jest
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
const archiveMock = jest
.spyOn(FileUpload.prototype as any, 'archive')
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
const uploadFileMock = jest
.spyOn(FileUpload.prototype as any, 'uploadFile')
.mockResolvedValue(undefined);

const fileUploadInstance = new FileUpload({
config: {
flags: {
'response-mode': 'buffered',
'disable-cs-auth': true,
},
framework: 'GATSBY',
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
outputDirectories: { GATSBY: './public' },
},
log: logMock,
exit: exitMock,
} as any);

const handleEnvImportFlowMock = jest
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
.mockResolvedValue(undefined);

await fileUploadInstance.prepareAndUploadNewProjectFile();

const contentstackAuthCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
(call) => call[0]?.name === 'contentstackAuth',
);
expect(contentstackAuthCalls.length).toBe(0);
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(false);

createSignedUploadUrlMock.mockRestore();
archiveMock.mockRestore();
uploadFileMock.mockRestore();
handleEnvImportFlowMock.mockRestore();
});

it('should enable Contentstack Authentication without prompt when --enable-cs-auth is passed', async () => {
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');

const createSignedUploadUrlMock = jest
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
const archiveMock = jest
.spyOn(FileUpload.prototype as any, 'archive')
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
const uploadFileMock = jest
.spyOn(FileUpload.prototype as any, 'uploadFile')
.mockResolvedValue(undefined);

const fileUploadInstance = new FileUpload({
config: {
flags: {
'response-mode': 'buffered',
'enable-cs-auth': true,
},
framework: 'GATSBY',
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
outputDirectories: { GATSBY: './public' },
},
log: logMock,
exit: exitMock,
} as any);

const handleEnvImportFlowMock = jest
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
.mockResolvedValue(undefined);

await fileUploadInstance.prepareAndUploadNewProjectFile();

const contentstackAuthCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
(call) => call[0]?.name === 'contentstackAuth',
);
expect(contentstackAuthCalls.length).toBe(0);
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(true);

createSignedUploadUrlMock.mockRestore();
archiveMock.mockRestore();
uploadFileMock.mockRestore();
handleEnvImportFlowMock.mockRestore();
});
});
});

34 changes: 26 additions & 8 deletions src/adapters/file-upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,14 +114,15 @@ export default class FileUpload extends BaseClass {
* @memberof FileUpload
*/
async createNewProject(uploadUid: string): Promise<void> {
const {
framework,
projectName,
buildCommand,
outputDirectory,
environmentName,
serverCommand,
isStreamingEnabled
const {
framework,
projectName,
buildCommand,
outputDirectory,
environmentName,
serverCommand,
isStreamingEnabled,
isContentstackAuthenticationEnabled,
} = this.config;
await this.apolloClient
.mutate({
Expand All @@ -139,6 +140,7 @@ export default class FileUpload extends BaseClass {
buildCommand: buildCommand === undefined || buildCommand === null ? 'npm run build' : buildCommand,
...(serverCommand && serverCommand.trim() !== '' ? { serverCommand } : {}),
isStreamingEnabled: isStreamingEnabled ?? false,
isContentstackAuthenticationEnabled: isContentstackAuthenticationEnabled ?? true,
},
},
skipGitData: true,
Expand Down Expand Up @@ -177,6 +179,8 @@ export default class FileUpload extends BaseClass {
'env-variables': envVariables,
'server-command': serverCommand,
'response-mode': responseMode,
'enable-cs-auth': enableCsAuth,
'disable-cs-auth': disableCsAuth,
alias,
} = this.config.flags;
const { token, apiKey } = configHandler.get(`tokens.${alias}`) ?? {};
Expand Down Expand Up @@ -263,6 +267,20 @@ export default class FileUpload extends BaseClass {
} else {
this.config.isStreamingEnabled = responseMode === 'streaming';
}
if (enableCsAuth) {
this.config.isContentstackAuthenticationEnabled = true;
} else if (disableCsAuth) {
this.config.isContentstackAuthenticationEnabled = false;
} else {
this.config.isContentstackAuthenticationEnabled = (await cliux.inquire({
type: 'confirm',
name: 'contentstackAuth',
message:
// eslint-disable-next-line max-len
'Enable Contentstack Authentication? Restricts access to this environment to members of your Contentstack organization.',
default: true,
})) as boolean;
}
this.config.variableType = variableType as unknown as string;
this.config.envVariables = envVariables;
await this.handleEnvImportFlow();
Expand Down
Loading
Loading