Skip to content

Commit 5620e5f

Browse files
Merge pull request #203 from contentstack/staging
Add Contentstack Authentication toggle to project creation, Add support for choosing namespace in case of multiple github connections and Update package versions and server logs query
2 parents 2327db4 + dfb510e commit 5620e5f

15 files changed

Lines changed: 1335 additions & 1272 deletions

‎.talismanrc‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,9 @@ fileignoreconfig:
99
- filename: test/unit/commands/rollback.test.ts
1010
checksum: d1f931f2d9a397131409399ad6463653e28b5a2224e870b641d9ba57c4418f18
1111
- filename: package-lock.json
12-
checksum: ffb77989c6fe554cdd79ebe0cbd07c0ce4329dd65b3e9a9089db4abb7b9be2b3
12+
checksum: c94ce0fbbbedcbad4dceef8168971831ed82aad85824db579f2497dcd49aecab
13+
- filename: src/adapters/github.test.ts
14+
checksum: b3d3d3a3c14adde103152d2ac4e1c4b7121a5f7533a87c3cc600f6d25fb59d1b
15+
- filename: src/adapters/file-upload.test.ts
16+
checksum: 962fd8b45578914926d20857ddc3d1bc2fa19e8b192c6dfdc03d214ae1898b59
1317
version: "1.0"

‎package-lock.json‎

Lines changed: 656 additions & 1237 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/cli-launch",
3-
"version": "1.11.1",
3+
"version": "1.11.2",
44
"description": "Launch related operations",
55
"author": "Contentstack CLI",
66
"bin": {
@@ -21,8 +21,8 @@
2121
],
2222
"dependencies": {
2323
"@apollo/client": "^3.14.0",
24-
"@contentstack/cli-command": "^1.8.2",
25-
"@contentstack/cli-utilities": "^1.18.3",
24+
"@contentstack/cli-command": "^1.8.4",
25+
"@contentstack/cli-utilities": "^1.18.5",
2626
"@oclif/core": "^4.2.7",
2727
"@oclif/plugin-help": "^6.2.25",
2828
"@rollup/plugin-commonjs": "^28.0.2",
@@ -31,7 +31,7 @@
3131
"@rollup/plugin-typescript": "^12.1.2",
3232
"@types/express": "^4.17.21",
3333
"@types/express-serve-static-core": "^4.17.34",
34-
"adm-zip": "^0.5.16",
34+
"adm-zip": "^0.5.18",
3535
"chalk": "^4.1.2",
3636
"cross-fetch": "^4.1.0",
3737
"dotenv": "^16.4.7",

‎src/adapters/base-class.test.ts‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,4 +685,71 @@ describe('BaseClass', () => {
685685
expect(exitMock).toHaveBeenCalledWith(1);
686686
});
687687
});
688+
689+
describe('detectFramework', () => {
690+
it('should scope the framework query to the selected GitHub connection namespace', async () => {
691+
const apolloClient = {
692+
query: jest.fn().mockResolvedValueOnce({ data: { framework: { framework: 'GATSBY' } } }),
693+
} as any;
694+
baseClass = new BaseClass({
695+
log: logMock,
696+
exit: exitMock,
697+
apolloClient,
698+
config: {
699+
provider: 'GitHub',
700+
listOfFrameWorks: [],
701+
repository: { fullName: 'test-user/eleventy-sample', defaultBranch: 'main' },
702+
userConnection: { provider: 'GitHub', namespace: 'org-account' },
703+
},
704+
} as any);
705+
(ux.inquire as jest.Mock).mockResolvedValueOnce('GATSBY');
706+
707+
await baseClass.detectFramework();
708+
709+
expect(apolloClient.query).toHaveBeenCalledWith({
710+
query: expect.anything(),
711+
variables: {
712+
query: {
713+
provider: 'GitHub',
714+
repoName: 'test-user/eleventy-sample',
715+
branchName: 'main',
716+
namespace: 'org-account',
717+
},
718+
},
719+
});
720+
});
721+
});
722+
723+
describe('selectBranch', () => {
724+
it('should scope the branches query to the selected GitHub connection namespace', async () => {
725+
const apolloClient = {
726+
query: jest.fn().mockResolvedValueOnce({
727+
data: { branches: { edges: [], pageData: { page: 1 }, pageInfo: { hasNextPage: false } } },
728+
}),
729+
} as any;
730+
baseClass = new BaseClass({
731+
log: logMock,
732+
exit: exitMock,
733+
apolloClient,
734+
config: {
735+
provider: 'GitHub',
736+
flags: {},
737+
repository: { fullName: 'test-user/eleventy-sample' },
738+
userConnection: { provider: 'GitHub', namespace: 'org-account' },
739+
},
740+
} as any);
741+
(ux.inquire as jest.Mock).mockResolvedValueOnce('main');
742+
743+
await baseClass.selectBranch();
744+
745+
expect(apolloClient.query).toHaveBeenCalledWith({
746+
query: expect.anything(),
747+
variables: {
748+
page: 1,
749+
first: 100,
750+
query: { provider: 'GitHub', repoName: 'test-user/eleventy-sample', namespace: 'org-account' },
751+
},
752+
});
753+
});
754+
});
688755
});

‎src/adapters/base-class.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ export default class BaseClass {
160160
*/
161161
async detectFramework(): Promise<void> {
162162
const { fullName, defaultBranch } = this.config.repository || {};
163+
const namespace = this.config.userConnection?.namespace;
163164
const query = this.config.provider === 'FileUpload' ? fileFrameworkQuery : frameworkQuery;
164165
const variables =
165166
this.config.provider === 'FileUpload'
@@ -171,6 +172,7 @@ export default class BaseClass {
171172
provider: this.config.provider,
172173
repoName: fullName,
173174
branchName: defaultBranch,
175+
...(namespace ? { namespace } : {}),
174176
},
175177
};
176178
this.config.framework = (await this.apolloClient
@@ -414,7 +416,7 @@ export default class BaseClass {
414416
if (includes(this.config.supportedAdapters, this.config.provider)) {
415417
const baseUrl = this.config.host.startsWith('http') ? this.config.host : `https://${this.config.host}`;
416418

417-
const gitHubConnectUrl = `${baseUrl.replace('api', 'app').replace('io', 'com')}/#!/launch`;
419+
const gitHubConnectUrl = `${baseUrl.replace('api', 'app').replace('io', 'com')}/#!/launch/settings/connected-accounts`;
418420
this.log(`You can connect your ${this.config.provider} account to the UI using the following URL:`, 'info');
419421
this.log(gitHubConnectUrl, { color: 'green' });
420422
open(gitHubConnectUrl);
@@ -464,12 +466,14 @@ export default class BaseClass {
464466
* @memberof BaseClass
465467
*/
466468
async selectBranch(): Promise<void> {
469+
const namespace = this.config.userConnection?.namespace;
467470
const variables = {
468471
page: 1,
469472
first: 100,
470473
query: {
471474
provider: this.config.provider,
472475
repoName: this.config.repository?.fullName,
476+
...(namespace ? { namespace } : {}),
473477
},
474478
};
475479

‎src/adapters/file-upload.test.ts‎

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,160 @@ describe('FileUpload Adapter', () => {
648648
uploadFileMock.mockRestore();
649649
handleEnvImportFlowMock.mockRestore();
650650
});
651+
652+
it.each([
653+
['a "yes" answer maps to enabled', true, true],
654+
['a "no" answer maps to disabled', false, false],
655+
])(
656+
'should prompt Enable Contentstack Authentication (default enabled) when disable-cs-auth is not provided — %s',
657+
async (_label, answer, expected) => {
658+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
659+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
660+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
661+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');
662+
(cliux.inquire as jest.Mock).mockResolvedValueOnce(answer);
663+
664+
const createSignedUploadUrlMock = jest
665+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
666+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
667+
const archiveMock = jest
668+
.spyOn(FileUpload.prototype as any, 'archive')
669+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
670+
const uploadFileMock = jest
671+
.spyOn(FileUpload.prototype as any, 'uploadFile')
672+
.mockResolvedValue(undefined);
673+
674+
const fileUploadInstance = new FileUpload({
675+
config: {
676+
flags: {
677+
'response-mode': 'buffered',
678+
},
679+
framework: 'GATSBY',
680+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
681+
outputDirectories: { GATSBY: './public' },
682+
},
683+
log: logMock,
684+
exit: exitMock,
685+
} as any);
686+
687+
const handleEnvImportFlowMock = jest
688+
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
689+
.mockResolvedValue(undefined);
690+
691+
await fileUploadInstance.prepareAndUploadNewProjectFile();
692+
693+
expect(cliux.inquire).toHaveBeenCalledWith(
694+
expect.objectContaining({
695+
type: 'confirm',
696+
name: 'contentstackAuth',
697+
default: true,
698+
}),
699+
);
700+
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(expected);
701+
702+
createSignedUploadUrlMock.mockRestore();
703+
archiveMock.mockRestore();
704+
uploadFileMock.mockRestore();
705+
handleEnvImportFlowMock.mockRestore();
706+
},
707+
);
708+
709+
710+
it('should disable Contentstack Authentication without prompt when --disable-cs-auth is passed', async () => {
711+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
712+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
713+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
714+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');
715+
716+
const createSignedUploadUrlMock = jest
717+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
718+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
719+
const archiveMock = jest
720+
.spyOn(FileUpload.prototype as any, 'archive')
721+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
722+
const uploadFileMock = jest
723+
.spyOn(FileUpload.prototype as any, 'uploadFile')
724+
.mockResolvedValue(undefined);
725+
726+
const fileUploadInstance = new FileUpload({
727+
config: {
728+
flags: {
729+
'response-mode': 'buffered',
730+
'disable-cs-auth': true,
731+
},
732+
framework: 'GATSBY',
733+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
734+
outputDirectories: { GATSBY: './public' },
735+
},
736+
log: logMock,
737+
exit: exitMock,
738+
} as any);
739+
740+
const handleEnvImportFlowMock = jest
741+
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
742+
.mockResolvedValue(undefined);
743+
744+
await fileUploadInstance.prepareAndUploadNewProjectFile();
745+
746+
const contentstackAuthCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
747+
(call) => call[0]?.name === 'contentstackAuth',
748+
);
749+
expect(contentstackAuthCalls.length).toBe(0);
750+
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(false);
751+
752+
createSignedUploadUrlMock.mockRestore();
753+
archiveMock.mockRestore();
754+
uploadFileMock.mockRestore();
755+
handleEnvImportFlowMock.mockRestore();
756+
});
757+
758+
it('should enable Contentstack Authentication without prompt when --enable-cs-auth is passed', async () => {
759+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
760+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
761+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
762+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');
763+
764+
const createSignedUploadUrlMock = jest
765+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
766+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
767+
const archiveMock = jest
768+
.spyOn(FileUpload.prototype as any, 'archive')
769+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
770+
const uploadFileMock = jest
771+
.spyOn(FileUpload.prototype as any, 'uploadFile')
772+
.mockResolvedValue(undefined);
773+
774+
const fileUploadInstance = new FileUpload({
775+
config: {
776+
flags: {
777+
'response-mode': 'buffered',
778+
'enable-cs-auth': true,
779+
},
780+
framework: 'GATSBY',
781+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
782+
outputDirectories: { GATSBY: './public' },
783+
},
784+
log: logMock,
785+
exit: exitMock,
786+
} as any);
787+
788+
const handleEnvImportFlowMock = jest
789+
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
790+
.mockResolvedValue(undefined);
791+
792+
await fileUploadInstance.prepareAndUploadNewProjectFile();
793+
794+
const contentstackAuthCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
795+
(call) => call[0]?.name === 'contentstackAuth',
796+
);
797+
expect(contentstackAuthCalls.length).toBe(0);
798+
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(true);
799+
800+
createSignedUploadUrlMock.mockRestore();
801+
archiveMock.mockRestore();
802+
uploadFileMock.mockRestore();
803+
handleEnvImportFlowMock.mockRestore();
804+
});
651805
});
652806
});
653807

‎src/adapters/file-upload.ts‎

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -114,14 +114,15 @@ export default class FileUpload extends BaseClass {
114114
* @memberof FileUpload
115115
*/
116116
async createNewProject(uploadUid: string): Promise<void> {
117-
const {
118-
framework,
119-
projectName,
120-
buildCommand,
121-
outputDirectory,
122-
environmentName,
123-
serverCommand,
124-
isStreamingEnabled
117+
const {
118+
framework,
119+
projectName,
120+
buildCommand,
121+
outputDirectory,
122+
environmentName,
123+
serverCommand,
124+
isStreamingEnabled,
125+
isContentstackAuthenticationEnabled,
125126
} = this.config;
126127
await this.apolloClient
127128
.mutate({
@@ -139,6 +140,7 @@ export default class FileUpload extends BaseClass {
139140
buildCommand: buildCommand === undefined || buildCommand === null ? 'npm run build' : buildCommand,
140141
...(serverCommand && serverCommand.trim() !== '' ? { serverCommand } : {}),
141142
isStreamingEnabled: isStreamingEnabled ?? false,
143+
isContentstackAuthenticationEnabled: isContentstackAuthenticationEnabled ?? true,
142144
},
143145
},
144146
skipGitData: true,
@@ -177,6 +179,8 @@ export default class FileUpload extends BaseClass {
177179
'env-variables': envVariables,
178180
'server-command': serverCommand,
179181
'response-mode': responseMode,
182+
'enable-cs-auth': enableCsAuth,
183+
'disable-cs-auth': disableCsAuth,
180184
alias,
181185
} = this.config.flags;
182186
const { token, apiKey } = configHandler.get(`tokens.${alias}`) ?? {};
@@ -263,6 +267,20 @@ export default class FileUpload extends BaseClass {
263267
} else {
264268
this.config.isStreamingEnabled = responseMode === 'streaming';
265269
}
270+
if (enableCsAuth) {
271+
this.config.isContentstackAuthenticationEnabled = true;
272+
} else if (disableCsAuth) {
273+
this.config.isContentstackAuthenticationEnabled = false;
274+
} else {
275+
this.config.isContentstackAuthenticationEnabled = (await cliux.inquire({
276+
type: 'confirm',
277+
name: 'contentstackAuth',
278+
message:
279+
// eslint-disable-next-line max-len
280+
'Enable Contentstack Authentication? Restricts access to this environment to members of your Contentstack organization.',
281+
default: true,
282+
})) as boolean;
283+
}
266284
this.config.variableType = variableType as unknown as string;
267285
this.config.envVariables = envVariables;
268286
await this.handleEnvImportFlow();

0 commit comments

Comments
 (0)