diff --git a/src/adapters/file-upload.test.ts b/src/adapters/file-upload.test.ts index f974074..3b35d02 100644 --- a/src/adapters/file-upload.test.ts +++ b/src/adapters/file-upload.test.ts @@ -304,6 +304,245 @@ describe('FileUpload Adapter', () => { ); expect(serverCommandCalls.length).toBe(0); }); + + it('should prompt Enable Streaming Response when flag is not provided and framework supports server commands', + 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('./dist'); + (cliux.inquire as jest.Mock).mockResolvedValueOnce('npm start'); + (cliux.inquire as jest.Mock).mockResolvedValueOnce(true); + + 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: { + 'server-command': undefined, + 'response-mode': undefined, + }, + framework: 'OTHER', + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { OTHER: './dist' }, + }, + log: logMock, + exit: exitMock, + } as any); + + await fileUploadInstance.prepareAndUploadNewProjectFile(); + + expect(cliux.inquire).toHaveBeenCalledWith({ + type: 'confirm', + name: 'enableStreamingResponse', + message: 'Enable Streaming Response', + default: false, + }); + expect(fileUploadInstance.config.isStreamingEnabled).toBe(true); + + createSignedUploadUrlMock.mockRestore(); + archiveMock.mockRestore(); + uploadFileMock.mockRestore(); + }); + + it('should not prompt for response mode when flag is streaming', 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('./dist'); + + 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: { + 'server-command': 'npm start', + 'response-mode': 'streaming', + }, + framework: 'OTHER', + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { OTHER: './dist' }, + }, + log: logMock, + exit: exitMock, + } as any); + + await fileUploadInstance.prepareAndUploadNewProjectFile(); + + const enableStreamingCalls = (cliux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'enableStreamingResponse', + ); + expect(enableStreamingCalls.length).toBe(0); + expect(fileUploadInstance.config.isStreamingEnabled).toBe(true); + + createSignedUploadUrlMock.mockRestore(); + archiveMock.mockRestore(); + uploadFileMock.mockRestore(); + }); + + it('should not prompt for response mode when flag is buffered', 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('./dist'); + + 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: { + 'server-command': 'npm start', + 'response-mode': 'buffered', + }, + framework: 'OTHER', + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { OTHER: './dist' }, + }, + log: logMock, + exit: exitMock, + } as any); + + await fileUploadInstance.prepareAndUploadNewProjectFile(); + + const enableStreamingCalls = (cliux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'enableStreamingResponse', + ); + expect(enableStreamingCalls.length).toBe(0); + expect(fileUploadInstance.config.isStreamingEnabled).toBe(false); + + createSignedUploadUrlMock.mockRestore(); + archiveMock.mockRestore(); + uploadFileMock.mockRestore(); + }); + + it('should prompt Enable Streaming Response for Gatsby when flag is not provided', 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'); + (cliux.inquire as jest.Mock).mockResolvedValueOnce(true); + + 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': undefined, + }, + 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 serverCommandCalls = (cliux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'serverCommand', + ); + expect(serverCommandCalls.length).toBe(0); + expect(cliux.inquire).toHaveBeenCalledWith({ + type: 'confirm', + name: 'enableStreamingResponse', + message: 'Enable Streaming Response', + default: false, + }); + expect(fileUploadInstance.config.isStreamingEnabled).toBe(true); + + createSignedUploadUrlMock.mockRestore(); + archiveMock.mockRestore(); + uploadFileMock.mockRestore(); + handleEnvImportFlowMock.mockRestore(); + }); + + it('should apply response-mode flag for Gatsby without prompt', 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', + }, + 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 enableStreamingCalls = (cliux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'enableStreamingResponse', + ); + expect(enableStreamingCalls.length).toBe(0); + expect(fileUploadInstance.config.isStreamingEnabled).toBe(false); + expect(logMock).not.toHaveBeenCalledWith( + 'The --response-mode option was ignored for this framework preset.', + 'warn', + ); + + createSignedUploadUrlMock.mockRestore(); + archiveMock.mockRestore(); + uploadFileMock.mockRestore(); + handleEnvImportFlowMock.mockRestore(); + }); }); }); diff --git a/src/adapters/file-upload.ts b/src/adapters/file-upload.ts index e571736..c02bf41 100755 --- a/src/adapters/file-upload.ts +++ b/src/adapters/file-upload.ts @@ -114,7 +114,15 @@ export default class FileUpload extends BaseClass { * @memberof FileUpload */ async createNewProject(uploadUid: string): Promise { - const { framework, projectName, buildCommand, outputDirectory, environmentName, serverCommand } = this.config; + const { + framework, + projectName, + buildCommand, + outputDirectory, + environmentName, + serverCommand, + isStreamingEnabled + } = this.config; await this.apolloClient .mutate({ mutation: importProjectMutation, @@ -130,6 +138,7 @@ export default class FileUpload extends BaseClass { environmentVariables: map(this.envVariables, ({ key, value }) => ({ key, value })), buildCommand: buildCommand === undefined || buildCommand === null ? 'npm run build' : buildCommand, ...(serverCommand && serverCommand.trim() !== '' ? { serverCommand } : {}), + isStreamingEnabled: isStreamingEnabled ?? false, }, }, skipGitData: true, @@ -169,6 +178,7 @@ export default class FileUpload extends BaseClass { 'server-command': serverCommand, alias, } = this.config.flags; + const responseMode = this.config.flags['response-mode'] as unknown as string | undefined; const { token, apiKey } = configHandler.get(`tokens.${alias}`) ?? {}; this.config.selectedStack = apiKey; this.config.deliveryToken = token; @@ -239,6 +249,16 @@ export default class FileUpload extends BaseClass { this.config.serverCommand = serverCommand; } } + if (!responseMode) { + this.config.isStreamingEnabled = (await cliux.inquire({ + type: 'confirm', + name: 'enableStreamingResponse', + message: 'Enable Streaming Response', + default: false, + })) as boolean; + } else { + this.config.isStreamingEnabled = responseMode === 'streaming'; + } this.config.variableType = variableType as unknown as string; this.config.envVariables = envVariables; await this.handleEnvImportFlow(); diff --git a/src/adapters/github.test.ts b/src/adapters/github.test.ts index 1b24329..600c425 100644 --- a/src/adapters/github.test.ts +++ b/src/adapters/github.test.ts @@ -564,5 +564,199 @@ describe('GitHub Adapter', () => { ); expect(serverCommandCalls.length).toBe(0); }); + + it('should prompt Enable Streaming Response when flag is not provided and framework supports server commands', + async () => { + (ux.inquire as jest.Mock).mockResolvedValueOnce('test-project'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('Default'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('./dist'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('npm start'); + (ux.inquire as jest.Mock).mockResolvedValueOnce(true); + + const githubInstance = new GitHub({ + config: { + flags: { + 'server-command': undefined, + 'response-mode': undefined, + }, + framework: 'OTHER', + repository: { fullName: 'test-user/repo', name: 'repo' }, + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { OTHER: './dist' }, + }, + log: logMock, + exit: exitMock, + } as any); + + const handleEnvImportFlowMock = jest + .spyOn(githubInstance, 'handleEnvImportFlow' as any) + .mockResolvedValue(undefined); + + await githubInstance.prepareForNewProjectCreation(); + + expect(ux.inquire).toHaveBeenCalledWith({ + type: 'confirm', + name: 'enableStreamingResponse', + message: 'Enable Streaming Response', + default: false, + }); + expect(githubInstance.config.isStreamingEnabled).toBe(true); + + handleEnvImportFlowMock.mockRestore(); + }); + + it('should not prompt for response mode when flag is streaming', async () => { + (ux.inquire as jest.Mock).mockResolvedValueOnce('test-project'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('Default'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('./dist'); + + const githubInstance = new GitHub({ + config: { + flags: { + 'server-command': 'npm start', + 'response-mode': 'streaming', + }, + framework: 'OTHER', + repository: { fullName: 'test-user/repo', name: 'repo' }, + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { OTHER: './dist' }, + }, + log: logMock, + exit: exitMock, + } as any); + + const handleEnvImportFlowMock = jest + .spyOn(githubInstance, 'handleEnvImportFlow' as any) + .mockResolvedValue(undefined); + + await githubInstance.prepareForNewProjectCreation(); + + const enableStreamingCalls = (ux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'enableStreamingResponse', + ); + expect(enableStreamingCalls.length).toBe(0); + expect(githubInstance.config.isStreamingEnabled).toBe(true); + + handleEnvImportFlowMock.mockRestore(); + }); + + it('should not prompt for response mode when flag is buffered', async () => { + (ux.inquire as jest.Mock).mockResolvedValueOnce('test-project'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('Default'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('./dist'); + + const githubInstance = new GitHub({ + config: { + flags: { + 'server-command': 'npm start', + 'response-mode': 'buffered', + }, + framework: 'OTHER', + repository: { fullName: 'test-user/repo', name: 'repo' }, + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { OTHER: './dist' }, + }, + log: logMock, + exit: exitMock, + } as any); + + const handleEnvImportFlowMock = jest + .spyOn(githubInstance, 'handleEnvImportFlow' as any) + .mockResolvedValue(undefined); + + await githubInstance.prepareForNewProjectCreation(); + + const enableStreamingCalls = (ux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'enableStreamingResponse', + ); + expect(enableStreamingCalls.length).toBe(0); + expect(githubInstance.config.isStreamingEnabled).toBe(false); + + handleEnvImportFlowMock.mockRestore(); + }); + + it('should prompt Enable Streaming Response for Gatsby when flag is not provided', async () => { + (ux.inquire as jest.Mock).mockResolvedValueOnce('test-project'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('Default'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('./public'); + (ux.inquire as jest.Mock).mockResolvedValueOnce(false); + + const githubInstance = new GitHub({ + config: { + flags: { + 'response-mode': undefined, + }, + framework: 'GATSBY', + repository: { fullName: 'test-user/repo', name: 'repo' }, + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { GATSBY: './public' }, + }, + log: logMock, + exit: exitMock, + } as any); + + const handleEnvImportFlowMock = jest + .spyOn(githubInstance, 'handleEnvImportFlow' as any) + .mockResolvedValue(undefined); + + await githubInstance.prepareForNewProjectCreation(); + + const serverCommandCalls = (ux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'serverCommand', + ); + expect(serverCommandCalls.length).toBe(0); + expect(ux.inquire).toHaveBeenCalledWith({ + type: 'confirm', + name: 'enableStreamingResponse', + message: 'Enable Streaming Response', + default: false, + }); + expect(githubInstance.config.isStreamingEnabled).toBe(false); + + handleEnvImportFlowMock.mockRestore(); + }); + + it('should apply response-mode flag for Gatsby without prompt', async () => { + (ux.inquire as jest.Mock).mockResolvedValueOnce('test-project'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('Default'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('npm run build'); + (ux.inquire as jest.Mock).mockResolvedValueOnce('./public'); + + const githubInstance = new GitHub({ + config: { + flags: { + 'response-mode': 'streaming', + }, + framework: 'GATSBY', + repository: { fullName: 'test-user/repo', name: 'repo' }, + supportedFrameworksForServerCommands: ['ANGULAR', 'OTHER', 'REMIX', 'NUXT'], + outputDirectories: { GATSBY: './public' }, + }, + log: logMock, + exit: exitMock, + } as any); + + const handleEnvImportFlowMock = jest + .spyOn(githubInstance, 'handleEnvImportFlow' as any) + .mockResolvedValue(undefined); + + await githubInstance.prepareForNewProjectCreation(); + + const enableStreamingCalls = (ux.inquire as jest.Mock).mock.calls.filter( + (call) => call[0]?.name === 'enableStreamingResponse', + ); + expect(enableStreamingCalls.length).toBe(0); + expect(githubInstance.config.isStreamingEnabled).toBe(true); + expect(logMock).not.toHaveBeenCalledWith( + 'The --response-mode option was ignored for this framework preset.', + 'warn', + ); + + handleEnvImportFlowMock.mockRestore(); + }); }); }); diff --git a/src/adapters/github.ts b/src/adapters/github.ts index a1032dc..8f7db35 100755 --- a/src/adapters/github.ts +++ b/src/adapters/github.ts @@ -100,6 +100,7 @@ export default class GitHub extends BaseClass { environmentName, provider: gitProvider, serverCommand, + isStreamingEnabled, } = this.config; const username = split(repository?.fullName, '/')[0]; @@ -124,6 +125,7 @@ export default class GitHub extends BaseClass { environmentVariables: map(this.envVariables, ({ key, value }) => ({ key, value })), buildCommand: buildCommand === undefined || buildCommand === null ? 'npm run build' : buildCommand, ...(serverCommand && serverCommand.trim() !== '' ? { serverCommand } : {}), + isStreamingEnabled: isStreamingEnabled ?? false, }, }, }, @@ -162,6 +164,7 @@ export default class GitHub extends BaseClass { 'server-command': serverCommand, alias, } = this.config.flags; + const responseMode = this.config.flags['response-mode'] as unknown as string | undefined; const { token, apiKey } = configHandler.get(`tokens.${alias}`) ?? {}; this.config.selectedStack = apiKey; this.config.deliveryToken = token; @@ -234,6 +237,16 @@ export default class GitHub extends BaseClass { this.config.serverCommand = serverCommand; } } + if (!responseMode) { + this.config.isStreamingEnabled = (await ux.inquire({ + type: 'confirm', + name: 'enableStreamingResponse', + message: 'Enable Streaming Response', + default: false, + })) as boolean; + } else { + this.config.isStreamingEnabled = responseMode === 'streaming'; + } this.config.variableType = variableType as unknown as string; this.config.envVariables = envVariables; await this.handleEnvImportFlow(); diff --git a/src/commands/launch/index.ts b/src/commands/launch/index.ts index 4ffc745..f60143c 100755 --- a/src/commands/launch/index.ts +++ b/src/commands/launch/index.ts @@ -26,6 +26,8 @@ export default class Launch extends BaseCommand { // eslint-disable-next-line max-len '<%= config.bin %> <%= command.id %> --config --type --name= --environment= --branch= --build-command= --framework=