Skip to content

Commit 4bb7a6a

Browse files
feat: add streaming option during project creation
1 parent b7f6b0b commit 4bb7a6a

6 files changed

Lines changed: 287 additions & 1 deletion

File tree

src/adapters/file-upload.test.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,138 @@ describe('FileUpload Adapter', () => {
304304
);
305305
expect(serverCommandCalls.length).toBe(0);
306306
});
307+
308+
it('should prompt for streaming response when framework supports server commands and flag is not provided',
309+
async () => {
310+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
311+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
312+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
313+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./dist');
314+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm start'); // server command prompt
315+
(cliux.inquire as jest.Mock).mockResolvedValueOnce(true);
316+
317+
const createSignedUploadUrlMock = jest
318+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
319+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
320+
const archiveMock = jest
321+
.spyOn(FileUpload.prototype as any, 'archive')
322+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
323+
const uploadFileMock = jest
324+
.spyOn(FileUpload.prototype as any, 'uploadFile')
325+
.mockResolvedValue(undefined);
326+
327+
const fileUploadInstance = new FileUpload({
328+
config: {
329+
flags: {
330+
'server-command': undefined, // Make it undefined so it prompts for server command
331+
'enable-streaming-response': undefined,
332+
},
333+
framework: 'OTHER',
334+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
335+
outputDirectories: { OTHER: './dist' },
336+
},
337+
log: logMock,
338+
exit: exitMock,
339+
} as any);
340+
341+
await fileUploadInstance.prepareAndUploadNewProjectFile();
342+
343+
expect(cliux.inquire).toHaveBeenCalledWith({
344+
type: 'confirm',
345+
name: 'isStreamingEnabled',
346+
message: 'Enable Streaming Responses',
347+
default: false,
348+
});
349+
expect(fileUploadInstance.config.isStreamingEnabled).toBe(true);
350+
351+
createSignedUploadUrlMock.mockRestore();
352+
archiveMock.mockRestore();
353+
uploadFileMock.mockRestore();
354+
});
355+
356+
it('should not prompt for streaming response when flag is provided', async () => {
357+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
358+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
359+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
360+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./dist');
361+
362+
const createSignedUploadUrlMock = jest
363+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
364+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
365+
const archiveMock = jest
366+
.spyOn(FileUpload.prototype as any, 'archive')
367+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
368+
const uploadFileMock = jest
369+
.spyOn(FileUpload.prototype as any, 'uploadFile')
370+
.mockResolvedValue(undefined);
371+
372+
const fileUploadInstance = new FileUpload({
373+
config: {
374+
flags: {
375+
'server-command': 'npm start',
376+
'enable-streaming-response': true,
377+
},
378+
framework: 'OTHER',
379+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
380+
outputDirectories: { OTHER: './dist' },
381+
},
382+
log: logMock,
383+
exit: exitMock,
384+
} as any);
385+
386+
await fileUploadInstance.prepareAndUploadNewProjectFile();
387+
388+
const streamingResponseCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
389+
(call) => call[0]?.name === 'isStreamingEnabled',
390+
);
391+
expect(streamingResponseCalls.length).toBe(0);
392+
expect(fileUploadInstance.config.isStreamingEnabled).toBe(true);
393+
394+
createSignedUploadUrlMock.mockRestore();
395+
archiveMock.mockRestore();
396+
uploadFileMock.mockRestore();
397+
});
398+
399+
it('should not prompt for streaming response when framework does not support server commands', async () => {
400+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
401+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('Default');
402+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
403+
(cliux.inquire as jest.Mock).mockResolvedValueOnce('./public');
404+
405+
const createSignedUploadUrlMock = jest
406+
.spyOn(FileUpload.prototype as any, 'createSignedUploadUrl')
407+
.mockResolvedValue({ uploadUid: 'test-upload-uid' });
408+
const archiveMock = jest
409+
.spyOn(FileUpload.prototype as any, 'archive')
410+
.mockResolvedValue({ zipName: 'test.zip', zipPath: '/path/to/test.zip', projectName: 'test-project' });
411+
const uploadFileMock = jest
412+
.spyOn(FileUpload.prototype as any, 'uploadFile')
413+
.mockResolvedValue(undefined);
414+
415+
const fileUploadInstance = new FileUpload({
416+
config: {
417+
flags: {
418+
'enable-streaming-response': undefined,
419+
},
420+
framework: 'GATSBY',
421+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
422+
outputDirectories: { GATSBY: './public' },
423+
},
424+
log: logMock,
425+
exit: exitMock,
426+
} as any);
427+
428+
await fileUploadInstance.prepareAndUploadNewProjectFile();
429+
430+
const streamingResponseCalls = (cliux.inquire as jest.Mock).mock.calls.filter(
431+
(call) => call[0]?.name === 'isStreamingEnabled',
432+
);
433+
expect(streamingResponseCalls.length).toBe(0);
434+
435+
createSignedUploadUrlMock.mockRestore();
436+
archiveMock.mockRestore();
437+
uploadFileMock.mockRestore();
438+
});
307439
});
308440
});
309441

src/adapters/file-upload.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,15 @@ export default class FileUpload extends BaseClass {
114114
* @memberof FileUpload
115115
*/
116116
async createNewProject(uploadUid: string): Promise<void> {
117-
const { framework, projectName, buildCommand, outputDirectory, environmentName, serverCommand } = this.config;
117+
const {
118+
framework,
119+
projectName,
120+
buildCommand,
121+
outputDirectory,
122+
environmentName,
123+
serverCommand,
124+
isStreamingEnabled
125+
} = this.config;
118126
await this.apolloClient
119127
.mutate({
120128
mutation: importProjectMutation,
@@ -130,6 +138,7 @@ export default class FileUpload extends BaseClass {
130138
environmentVariables: map(this.envVariables, ({ key, value }) => ({ key, value })),
131139
buildCommand: buildCommand === undefined || buildCommand === null ? 'npm run build' : buildCommand,
132140
...(serverCommand && serverCommand.trim() !== '' ? { serverCommand } : {}),
141+
...(isStreamingEnabled !== undefined ? { isStreamingEnabled } : {}),
133142
},
134143
},
135144
skipGitData: true,
@@ -167,6 +176,7 @@ export default class FileUpload extends BaseClass {
167176
'variable-type': variableType,
168177
'env-variables': envVariables,
169178
'server-command': serverCommand,
179+
'enable-streaming-response': isStreamingEnabled,
170180
alias,
171181
} = this.config.flags;
172182
const { token, apiKey } = configHandler.get(`tokens.${alias}`) ?? {};
@@ -238,6 +248,18 @@ export default class FileUpload extends BaseClass {
238248
} else {
239249
this.config.serverCommand = serverCommand;
240250
}
251+
252+
if (isStreamingEnabled === undefined) {
253+
const isStreamingEnabledInput = await cliux.inquire({
254+
type: 'confirm',
255+
name: 'isStreamingEnabled',
256+
message: 'Enable Streaming Responses',
257+
default: false,
258+
});
259+
this.config.isStreamingEnabled = isStreamingEnabledInput as boolean;
260+
} else {
261+
this.config.isStreamingEnabled = isStreamingEnabled as unknown as boolean;
262+
}
241263
}
242264
this.config.variableType = variableType as unknown as string;
243265
this.config.envVariables = envVariables;

src/adapters/github.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,5 +564,116 @@ describe('GitHub Adapter', () => {
564564
);
565565
expect(serverCommandCalls.length).toBe(0);
566566
});
567+
568+
it('should prompt for streaming response when framework supports server commands and flag is not provided',
569+
async () => {
570+
(ux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
571+
(ux.inquire as jest.Mock).mockResolvedValueOnce('Default');
572+
(ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
573+
(ux.inquire as jest.Mock).mockResolvedValueOnce('./dist');
574+
(ux.inquire as jest.Mock).mockResolvedValueOnce('npm start');
575+
(ux.inquire as jest.Mock).mockResolvedValueOnce(true);
576+
577+
const githubInstance = new GitHub({
578+
config: {
579+
flags: {
580+
'server-command': undefined, // Make it undefined so it prompts for server command
581+
'enable-streaming-response': undefined,
582+
},
583+
framework: 'OTHER',
584+
repository: { fullName: 'test-user/repo', name: 'repo' },
585+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
586+
outputDirectories: { OTHER: './dist' },
587+
},
588+
log: logMock,
589+
exit: exitMock,
590+
} as any);
591+
592+
const handleEnvImportFlowMock = jest
593+
.spyOn(githubInstance, 'handleEnvImportFlow' as any)
594+
.mockResolvedValue(undefined);
595+
596+
await githubInstance.prepareForNewProjectCreation();
597+
598+
expect(ux.inquire).toHaveBeenCalledWith({
599+
type: 'confirm',
600+
name: 'isStreamingEnabled',
601+
message: 'Enable Streaming Responses',
602+
default: false,
603+
});
604+
expect(githubInstance.config.isStreamingEnabled).toBe(true);
605+
606+
handleEnvImportFlowMock.mockRestore();
607+
});
608+
609+
it('should not prompt for streaming response when flag is provided', async () => {
610+
(ux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
611+
(ux.inquire as jest.Mock).mockResolvedValueOnce('Default');
612+
(ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
613+
(ux.inquire as jest.Mock).mockResolvedValueOnce('./dist');
614+
615+
const githubInstance = new GitHub({
616+
config: {
617+
flags: {
618+
'server-command': 'npm start',
619+
'enable-streaming-response': true,
620+
},
621+
framework: 'OTHER',
622+
repository: { fullName: 'test-user/repo', name: 'repo' },
623+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
624+
outputDirectories: { OTHER: './dist' },
625+
},
626+
log: logMock,
627+
exit: exitMock,
628+
} as any);
629+
630+
const handleEnvImportFlowMock = jest
631+
.spyOn(githubInstance, 'handleEnvImportFlow' as any)
632+
.mockResolvedValue(undefined);
633+
634+
await githubInstance.prepareForNewProjectCreation();
635+
636+
const streamingResponseCalls = (ux.inquire as jest.Mock).mock.calls.filter(
637+
(call) => call[0]?.name === 'isStreamingEnabled',
638+
);
639+
expect(streamingResponseCalls.length).toBe(0);
640+
expect(githubInstance.config.isStreamingEnabled).toBe(true);
641+
642+
handleEnvImportFlowMock.mockRestore();
643+
});
644+
645+
it('should not prompt for streaming response when framework does not support server commands', async () => {
646+
(ux.inquire as jest.Mock).mockResolvedValueOnce('test-project');
647+
(ux.inquire as jest.Mock).mockResolvedValueOnce('Default');
648+
(ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build');
649+
(ux.inquire as jest.Mock).mockResolvedValueOnce('./public');
650+
651+
const githubInstance = new GitHub({
652+
config: {
653+
flags: {
654+
'enable-streaming-response': undefined,
655+
},
656+
framework: 'GATSBY',
657+
repository: { fullName: 'test-user/repo', name: 'repo' },
658+
supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'],
659+
outputDirectories: { GATSBY: './public' },
660+
},
661+
log: logMock,
662+
exit: exitMock,
663+
} as any);
664+
665+
const handleEnvImportFlowMock = jest
666+
.spyOn(githubInstance, 'handleEnvImportFlow' as any)
667+
.mockResolvedValue(undefined);
668+
669+
await githubInstance.prepareForNewProjectCreation();
670+
671+
const streamingResponseCalls = (ux.inquire as jest.Mock).mock.calls.filter(
672+
(call) => call[0]?.name === 'isStreamingEnabled',
673+
);
674+
expect(streamingResponseCalls.length).toBe(0);
675+
676+
handleEnvImportFlowMock.mockRestore();
677+
});
567678
});
568679
});

src/adapters/github.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export default class GitHub extends BaseClass {
100100
environmentName,
101101
provider: gitProvider,
102102
serverCommand,
103+
isStreamingEnabled,
103104
} = this.config;
104105
const username = split(repository?.fullName, '/')[0];
105106

@@ -124,6 +125,7 @@ export default class GitHub extends BaseClass {
124125
environmentVariables: map(this.envVariables, ({ key, value }) => ({ key, value })),
125126
buildCommand: buildCommand === undefined || buildCommand === null ? 'npm run build' : buildCommand,
126127
...(serverCommand && serverCommand.trim() !== '' ? { serverCommand } : {}),
128+
...(isStreamingEnabled !== undefined ? { isStreamingEnabled } : {}),
127129
},
128130
},
129131
},
@@ -160,6 +162,7 @@ export default class GitHub extends BaseClass {
160162
'variable-type': variableType,
161163
'env-variables': envVariables,
162164
'server-command': serverCommand,
165+
'enable-streaming-response': isStreamingEnabled,
163166
alias,
164167
} = this.config.flags;
165168
const { token, apiKey } = configHandler.get(`tokens.${alias}`) ?? {};
@@ -233,6 +236,18 @@ export default class GitHub extends BaseClass {
233236
} else {
234237
this.config.serverCommand = serverCommand;
235238
}
239+
240+
if (isStreamingEnabled === undefined) {
241+
const isStreamingEnabledInput = await ux.inquire({
242+
type: 'confirm',
243+
name: 'isStreamingEnabled',
244+
message: 'Enable Streaming Responses',
245+
default: false,
246+
});
247+
this.config.isStreamingEnabled = isStreamingEnabledInput as boolean;
248+
} else {
249+
this.config.isStreamingEnabled = isStreamingEnabled as unknown as boolean;
250+
}
236251
}
237252
this.config.variableType = variableType as unknown as string;
238253
this.config.envVariables = envVariables;

src/commands/launch/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ export default class Launch extends BaseCommand<typeof Launch> {
2626
// eslint-disable-next-line max-len
2727
'<%= config.bin %> <%= command.id %> --config <path/to/launch/config/file> --type <options: GitHub|FileUpload> --name=<value> --environment=<value> --branch=<value> --build-command=<value> --framework=<option> --org=<value> --out-dir=<value> --server-command=<value>',
2828
// eslint-disable-next-line max-len
29+
'<%= config.bin %> <%= command.id %> --config <path/to/launch/config/file> --type <options: GitHub|FileUpload> --name=<value> --environment=<value> --branch=<value> --build-command=<value> --framework=<option> --org=<value> --out-dir=<value> --server-command=<value> --enable-streaming-response',
30+
// eslint-disable-next-line max-len
2931
'<%= config.bin %> <%= command.id %> --config <path/to/launch/config/file> --type <options: GitHub|FileUpload> --name=<value> --environment=<value> --branch=<value> --build-command=<value> --framework=<option> --org=<value> --out-dir=<value> --variable-type="Import variables from a stack" --alias=<value>',
3032
// eslint-disable-next-line max-len
3133
'<%= config.bin %> <%= command.id %> --config <path/to/launch/config/file> --type <options: GitHub|FileUpload> --name=<value> --environment=<value> --branch=<value> --build-command=<value> --framework=<option> --org=<value> --out-dir=<value> --variable-type="Manually add custom variables to the list" --env-variables="APP_ENV:prod, TEST_ENV:testVal"',
@@ -98,6 +100,9 @@ export default class Launch extends BaseCommand<typeof Launch> {
98100
description: '[optional] Redeploy with last file upload',
99101
default: false,
100102
}),
103+
'enable-streaming-response': Flags.boolean({
104+
description: '[optional] Enable streaming response for the server.',
105+
}),
101106
};
102107

103108
async run(): Promise<void> {

src/types/launch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ type ConfigType = {
5353
repository?: Record<string, any>;
5454
currentConfig: Record<string, any>;
5555
deliveryToken?: Record<string, any>;
56+
isStreamingEnabled?: boolean;
5657
} & typeof config &
5758
Record<string, any>;
5859

0 commit comments

Comments
 (0)