Skip to content

Commit 4f80c81

Browse files
feat: Add Contentstack Authentication toggle to project creation.
Set the isContentstackAuthenticationEnabled environment field when creating a new Launch project, for both the GitHub and FileUpload flows. Contentstack Authentication is enabled by default; users can opt out with the new --disable-cs-auth flag or by answering "no" at the interactive prompt (which defaults to enabled). #claude_code# 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bfb9032 commit 4f80c81

9 files changed

Lines changed: 1025 additions & 1248 deletions

File tree

.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: 654 additions & 1235 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 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": {
@@ -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/file-upload.test.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,161 @@ 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+
'disable-cs-auth': false,
679+
},
680+
framework: 'GATSBY',
681+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
682+
outputDirectories: { GATSBY: './public' },
683+
},
684+
log: logMock,
685+
exit: exitMock,
686+
} as any);
687+
688+
const handleEnvImportFlowMock = jest
689+
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
690+
.mockResolvedValue(undefined);
691+
692+
await fileUploadInstance.prepareAndUploadNewProjectFile();
693+
694+
expect(cliux.inquire).toHaveBeenCalledWith(
695+
expect.objectContaining({
696+
type: 'confirm',
697+
name: 'contentstackAuth',
698+
default: true,
699+
}),
700+
);
701+
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(expected);
702+
703+
createSignedUploadUrlMock.mockRestore();
704+
archiveMock.mockRestore();
705+
uploadFileMock.mockRestore();
706+
handleEnvImportFlowMock.mockRestore();
707+
},
708+
);
709+
710+
711+
it('should disable Contentstack Authentication without prompt when --disable-cs-auth is passed', async () => {
712+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
713+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
714+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
715+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');
716+
717+
const createSignedUploadUrlMock = jest
718+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
719+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
720+
const archiveMock = jest
721+
.spyOn(FileUpload.prototype as any, 'archive')
722+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
723+
const uploadFileMock = jest
724+
.spyOn(FileUpload.prototype as any, 'uploadFile')
725+
.mockResolvedValue(undefined);
726+
727+
const fileUploadInstance = new FileUpload({
728+
config: {
729+
flags: {
730+
'response-mode': 'buffered',
731+
'disable-cs-auth': true,
732+
},
733+
framework: 'GATSBY',
734+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
735+
outputDirectories: { GATSBY: './public' },
736+
},
737+
log: logMock,
738+
exit: exitMock,
739+
} as any);
740+
741+
const handleEnvImportFlowMock = jest
742+
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
743+
.mockResolvedValue(undefined);
744+
745+
await fileUploadInstance.prepareAndUploadNewProjectFile();
746+
747+
const contentstackAuthCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
748+
(call) => call[0]?.name === 'contentstackAuth',
749+
);
750+
expect(contentstackAuthCalls.length).toBe(0);
751+
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(false);
752+
753+
createSignedUploadUrlMock.mockRestore();
754+
archiveMock.mockRestore();
755+
uploadFileMock.mockRestore();
756+
handleEnvImportFlowMock.mockRestore();
757+
});
758+
759+
it('should enable Contentstack Authentication without prompt when --enable-cs-auth is passed', async () => {
760+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
761+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
762+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
763+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');
764+
765+
const createSignedUploadUrlMock = jest
766+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
767+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
768+
const archiveMock = jest
769+
.spyOn(FileUpload.prototype as any, 'archive')
770+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
771+
const uploadFileMock = jest
772+
.spyOn(FileUpload.prototype as any, 'uploadFile')
773+
.mockResolvedValue(undefined);
774+
775+
const fileUploadInstance = new FileUpload({
776+
config: {
777+
flags: {
778+
'response-mode': 'buffered',
779+
'enable-cs-auth': true,
780+
},
781+
framework: 'GATSBY',
782+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
783+
outputDirectories: { GATSBY: './public' },
784+
},
785+
log: logMock,
786+
exit: exitMock,
787+
} as any);
788+
789+
const handleEnvImportFlowMock = jest
790+
.spyOn(fileUploadInstance, 'handleEnvImportFlow' as any)
791+
.mockResolvedValue(undefined);
792+
793+
await fileUploadInstance.prepareAndUploadNewProjectFile();
794+
795+
const contentstackAuthCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
796+
(call) => call[0]?.name === 'contentstackAuth',
797+
);
798+
expect(contentstackAuthCalls.length).toBe(0);
799+
expect(fileUploadInstance.config.isContentstackAuthenticationEnabled).toBe(true);
800+
801+
createSignedUploadUrlMock.mockRestore();
802+
archiveMock.mockRestore();
803+
uploadFileMock.mockRestore();
804+
handleEnvImportFlowMock.mockRestore();
805+
});
651806
});
652807
});
653808

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)