From 17c01f921357ac0494ff62ea051c0f7f9ca8d852 Mon Sep 17 00:00:00 2001 From: Tim Benniks Date: Thu, 3 Jul 2025 09:13:20 +0200 Subject: [PATCH 1/2] chore: Enhance bootstrap process with new app types and dev server options --- .talismanrc | 8 +- .../messages/index.json | 42 ++-- .../src/bootstrap/index.ts | 54 ++++- .../src/bootstrap/interactive.ts | 10 + .../src/bootstrap/utils.ts | 197 ++++++++---------- .../src/commands/cm/bootstrap.ts | 23 +- packages/contentstack-bootstrap/src/config.ts | 69 ++++-- .../test/bootstrap-error-handling.test.js | 33 +++ .../test/bootstrap-integration.test.js | 43 ++++ .../test/bootstrap.test.js | 72 +++++++ .../test/interactive-dev-server.test.js | 108 ++++++++++ 11 files changed, 508 insertions(+), 151 deletions(-) create mode 100644 packages/contentstack-bootstrap/test/bootstrap-error-handling.test.js create mode 100644 packages/contentstack-bootstrap/test/bootstrap-integration.test.js create mode 100644 packages/contentstack-bootstrap/test/interactive-dev-server.test.js diff --git a/.talismanrc b/.talismanrc index c494d64384..0a3e10a37a 100644 --- a/.talismanrc +++ b/.talismanrc @@ -111,9 +111,9 @@ fileignoreconfig: - filename: .husky/pre-commit checksum: 7a12030ddfea18d6f85edc25f1721fb2009df00fdd42bab66b05de25ab3e32b2 - filename: packages/contentstack-bootstrap/src/bootstrap/utils.ts - checksum: 0b0af505ce1a74eb8df519f106291e319eb3ea74003ca63e03527f59a8821d39 + checksum: 2845a5fc50cbe46591868c64d2105f9befbddfb27a547a8a68ada93071845df4 - filename: packages/contentstack-bootstrap/test/bootstrap.test.js - checksum: 5f0355a5048183d61b605cbc160e6727a9de32832d9159e903fee49f9ab751d5 + checksum: c19fb9af02f858f92f66408d54343fcb84140b1050c492570f53fc9406f416f4 - filename: packages/contentstack/package.json checksum: 9b0fdd100effcdbb5ee3809f7f102bfd11c88dd76e49db5103434f3aa29473dd - filename: pnpm-lock.yaml @@ -122,10 +122,6 @@ fileignoreconfig: checksum: bd99d269c0b6694577f4751fa96b3d85856e41bbef624b4ec1196630d6c1d168 - filename: packages/contentstack-migrate-rte/test/commands/json-migration.test.js checksum: 1f5ee5b39119667bd4830f9dbbbf757fb922f4ec3b7f6fad06bbfbf214fe7f73 - - filename: packages/contentstack-bootstrap/src/bootstrap/utils.ts - checksum: 0b0af505ce1a74eb8df519f106291e319eb3ea74003ca63e03527f59a8821d39 - - filename: packages/contentstack-bootstrap/test/bootstrap.test.js - checksum: 5f0355a5048183d61b605cbc160e6727a9de32832d9159e903fee49f9ab751d5 - filename: package-lock.json checksum: 290d420891b9612fd6b4f134cb849770704f10b58d83f7ad05a4f88204818778 - filename: packages/contentstack-utilities/test/unit/logger.test.ts diff --git a/packages/contentstack-bootstrap/messages/index.json b/packages/contentstack-bootstrap/messages/index.json index 27a07684f3..3a56ee4301 100644 --- a/packages/contentstack-bootstrap/messages/index.json +++ b/packages/contentstack-bootstrap/messages/index.json @@ -1,18 +1,26 @@ { - "CLI_BOOTSTRAP_INVALID_APP_NAME": "Invalid app name received, use cm:bootstrap see the list of apps supported", - "CLI_BOOTSTRAP_LOGIN_FAILED": "You need to login, first. See: auth:login --help", - "CLI_BOOTSTRAP_GITHUB_ACCESS_NOT_FOUND": "No Github access token found", - "CLI_BOOTSTRAP_START_CLONE_APP": "Cloning the selected app", - "CLI_BOOTSTRAP_REPO_NOT_FOUND": "Unable to find a repo for \"%s\"", - "CLI_BOOTSTRAP_NO_API_KEY_FOUND": "No API key generated for the stack", - "CLI_BOOTSTRAP_STACK_CREATION_FAILED": "Unable to create stack for content \"%s\"", - "CLI_BOOTSTRAP_APP_SELECTION_ENQUIRY": "Select an App", - "CLI_BOOTSTRAP_APP_COPY_SOURCE_CODE_DESTINATION_TYPE_ENQUIRY": "Choose the location where you want to copy the source code", - "CLI_BOOTSTRAP_APP_COPY_SOURCE_CODE_DESTINATION_ENQUIRY": "Enter destination path", - "CLI_BOOTSTRAP_NO_ACCESS_TOKEN_CREATED": "Note: Access token not created already, check out this link https://github.com/settings/tokens \n Provide github access token", - "CLI_BOOTSTRAP_TYPE_OF_APP_ENQUIRY": "Choose the type of app you want to clone", - "CLI_BOOTSTRAP_APP_FAILED_TO_CREATE_TOKEN_FOR_ENV": "Failed to create delivery token for env \"%s\"", - "CLI_BOOTSTRAP_APP_FAILED_TO_CREATE_ENV_FILE_FOR_ENV": "Failed to setup env file for \"%s\"", - "CLI_BOOTSTRAP_APP_ENV_NOT_FOUND_FOR_THE_STACK": "No environments found for the stack", - "CLI_BOOTSTRAP_SUCCESS": "Project setup is successful!" -} \ No newline at end of file + "CLI_BOOTSTRAP_INVALID_APP_NAME": "Invalid app name received, use cm:bootstrap see the list of apps supported", + "CLI_BOOTSTRAP_LOGIN_FAILED": "You need to login, first. See: auth:login --help", + "CLI_BOOTSTRAP_GITHUB_ACCESS_NOT_FOUND": "No Github access token found", + "CLI_BOOTSTRAP_START_CLONE_APP": "Cloning the selected app", + "CLI_BOOTSTRAP_REPO_NOT_FOUND": "Unable to find a repo for \"%s\"", + "CLI_BOOTSTRAP_NO_API_KEY_FOUND": "No API key generated for the stack", + "CLI_BOOTSTRAP_STACK_CREATION_FAILED": "Unable to create stack for content \"%s\"", + "CLI_BOOTSTRAP_APP_SELECTION_ENQUIRY": "Select an App", + "CLI_BOOTSTRAP_APP_COPY_SOURCE_CODE_DESTINATION_TYPE_ENQUIRY": "Choose the location where you want to copy the source code", + "CLI_BOOTSTRAP_APP_COPY_SOURCE_CODE_DESTINATION_ENQUIRY": "Enter destination path", + "CLI_BOOTSTRAP_NO_ACCESS_TOKEN_CREATED": "Note: Access token not created already, check out this link https://github.com/settings/tokens \n Provide github access token", + "CLI_BOOTSTRAP_TYPE_OF_APP_ENQUIRY": "Choose the type of app you want to clone", + "CLI_BOOTSTRAP_APP_FAILED_TO_CREATE_TOKEN_FOR_ENV": "Failed to create delivery token for env \"%s\"", + "CLI_BOOTSTRAP_APP_FAILED_TO_CREATE_ENV_FILE_FOR_ENV": "Failed to setup env file for \"%s\"", + "CLI_BOOTSTRAP_APP_ENV_NOT_FOUND_FOR_THE_STACK": "No environments found for the stack", + "CLI_BOOTSTRAP_SUCCESS": "Project setup is successful!", + "CLI_BOOTSTRAP_SUCCESS_LIVE_PREVIEW_NOTE": "Note: Before running the app, please turn on Live Preview in the CMS: Stack Settings > Live Preview > Enable Live Preview", + "CLI_BOOTSTRAP_INSTALLING_DEPENDENCIES": "Installing local dependencies with NPM", + "CLI_BOOTSTRAP_DEPENDENCIES_INSTALLED": "NPM dependencies installed successfully!", + "CLI_BOOTSTRAP_DEPENDENCIES_INSTALL_FAILED": "Failed to install NPM dependencies.", + "CLI_BOOTSTRAP_RUN_DEV_SERVER_ENQUIRY": "Do you want to install dependencies and run the app locally (npm install && npm run dev)?", + "CLI_BOOTSTRAP_STARTING_DEV_SERVER": "Starting development server...", + "CLI_BOOTSTRAP_DEV_SERVER_STARTED": "Development server started successfully! Check your terminal for more details.", + "CLI_BOOTSTRAP_DEV_SERVER_FAILED": "Failed to start development server. You can start it manually with 'npm run dev' in the project directory." +} diff --git a/packages/contentstack-bootstrap/src/bootstrap/index.ts b/packages/contentstack-bootstrap/src/bootstrap/index.ts index 43de31b33e..dca6a72201 100644 --- a/packages/contentstack-bootstrap/src/bootstrap/index.ts +++ b/packages/contentstack-bootstrap/src/bootstrap/index.ts @@ -1,4 +1,5 @@ import * as path from 'path'; +import { execSync, spawn } from 'child_process'; import { cliux, sanitizePath } from '@contentstack/cli-utilities'; import { default as ContentStackSeed } from '@contentstack/cli-cm-seed/lib/commands/cm/stacks/seed'; @@ -19,6 +20,7 @@ export interface BootstrapOptions { accessToken?: string; appType: string; livePreviewEnabled?: boolean; + runDevServer?: boolean; master_locale: any; } @@ -113,13 +115,14 @@ export default class Bootstrap { this.options.livePreviewEnabled as boolean, this.options.seedParams.managementToken as string, ); + } else { throw new Error(messageHandler.parse('CLI_BOOTSTRAP_NO_API_KEY_FOUND')); } if (this.options.livePreviewEnabled) { cliux.print( - 'Note: Before running the app, please configure a preview token, preview host, and app host in the environment file', + messageHandler.parse('CLI_BOOTSTRAP_SUCCESS_LIVE_PREVIEW_NOTE'), { color: 'yellow', }, @@ -127,8 +130,55 @@ export default class Bootstrap { } cliux.print(messageHandler.parse('CLI_BOOTSTRAP_SUCCESS')); + + // Install dependencies and start development server if requested (after all other operations) + if (this.options.runDevServer) { + // Install project dependencies + cliux.loader(messageHandler.parse('CLI_BOOTSTRAP_INSTALLING_DEPENDENCIES')); + try { + execSync('npm install', { + cwd: this.cloneDirectory, + stdio: 'inherit' + }); + cliux.loader(); + cliux.print(messageHandler.parse('CLI_BOOTSTRAP_DEPENDENCIES_INSTALLED')); + + // Start development server + cliux.print(messageHandler.parse('CLI_BOOTSTRAP_STARTING_DEV_SERVER')); + cliux.print(messageHandler.parse('CLI_BOOTSTRAP_DEV_SERVER_STARTED')); + cliux.print('You can now access your application. Check the output above for the local URL.'); + + // Run npm run dev using spawn for long-running process + const devProcess = spawn('npm', ['run', 'dev'], { + cwd: this.cloneDirectory, + stdio: 'inherit', + shell: true + }); + + devProcess.on('error', (error) => { + cliux.print(messageHandler.parse('CLI_BOOTSTRAP_DEV_SERVER_FAILED'), { + color: 'yellow', + }); + console.error('Failed to start dev server:', error); + }); + + // Handle process exit + devProcess.on('exit', (code) => { + if (code !== 0 && code !== null) { + cliux.print(messageHandler.parse('CLI_BOOTSTRAP_DEV_SERVER_FAILED'), { + color: 'yellow', + }); + } + }); + } catch (installError: any) { + cliux.loader(); + cliux.print(messageHandler.parse('CLI_BOOTSTRAP_DEPENDENCIES_INSTALL_FAILED'), { + color: 'yellow', + }); + } + } } catch (error) { cliux.error(messageHandler.parse('CLI_BOOTSTRAP_STACK_CREATION_FAILED', this.appConfig.stack)); } } -} \ No newline at end of file +} diff --git a/packages/contentstack-bootstrap/src/bootstrap/interactive.ts b/packages/contentstack-bootstrap/src/bootstrap/interactive.ts index af02558219..2db5d0e558 100644 --- a/packages/contentstack-bootstrap/src/bootstrap/interactive.ts +++ b/packages/contentstack-bootstrap/src/bootstrap/interactive.ts @@ -99,6 +99,16 @@ export async function inquireLivePreviewSupport() { return livePreviewEnabled; } +export async function inquireRunDevServer() { + const { runDevServer } = await inquirer.prompt({ + type: 'confirm', + name: 'runDevServer', + message: messageHandler.parse('CLI_BOOTSTRAP_RUN_DEV_SERVER_ENQUIRY'), + default: true, + }); + return runDevServer; +} + export async function continueBootstrapCommand() { const { shouldContinue } = await inquirer.prompt({ type: 'list', diff --git a/packages/contentstack-bootstrap/src/bootstrap/utils.ts b/packages/contentstack-bootstrap/src/bootstrap/utils.ts index fad0c86421..10cbd0cc09 100644 --- a/packages/contentstack-bootstrap/src/bootstrap/utils.ts +++ b/packages/contentstack-bootstrap/src/bootstrap/utils.ts @@ -5,7 +5,7 @@ import { continueBootstrapCommand } from '../bootstrap/interactive'; import { AppConfig } from '../config'; import messageHandler from '../messages'; -interface EnviornmentVariables { +interface EnvironmentVariables { api_key: string; deliveryToken: string; environment: string; @@ -101,9 +101,9 @@ export const setupEnvironments = async ( try { const tokenResult = !managementToken ? await managementAPIClient - .stack({ api_key }) - .deliveryToken() - .create(body, livePreviewEnabled ? { create_with_preview_token: true } : {}) + .stack({ api_key }) + .deliveryToken() + .create(body, livePreviewEnabled ? { create_with_preview_token: true } : {}) : {}; if (livePreviewEnabled && !tokenResult.preview_token && !managementToken) { cliux.print( @@ -117,7 +117,7 @@ export const setupEnvironments = async ( } } if (tokenResult.token) { - const environmentVariables: EnviornmentVariables = { + const environmentVariables: EnvironmentVariables = { api_key, deliveryToken: tokenResult.token ?? '', environment: environment.name, @@ -135,7 +135,6 @@ export const setupEnvironments = async ( cliux.print(messageHandler.parse('CLI_BOOTSTRAP_APP_FAILED_TO_CREATE_TOKEN_FOR_ENV', environment.name)); } } catch (error) { - console.log('error', error); cliux.print(messageHandler.parse('CLI_BOOTSTRAP_APP_FAILED_TO_CREATE_ENV_FILE_FOR_ENV', environment.name)); } } else { @@ -169,7 +168,7 @@ const writeEnvFile = (content: string, fileName: string) => { const envFileHandler = async ( appConfigKey: string, - environmentVariables: EnviornmentVariables, + environmentVariables: EnvironmentVariables, clonedDirectory: string, region: any, livePreviewEnabled: boolean, @@ -193,115 +192,113 @@ const envFileHandler = async ( } const production = environmentVariables.environment === 'production' ? true : false; switch (appConfigKey) { + case 'kickstart-next': + case 'kickstart-next-ssr': + case 'kickstart-next-ssg': + case 'kickstart-next-middleware': + case 'kickstart-next-graphql': + fileName = `.env`; + filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); + content = `NEXT_PUBLIC_CONTENTSTACK_API_KEY=${environmentVariables.api_key + }\nNEXT_PUBLIC_CONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\nNEXT_PUBLIC_CONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || '' + }\nNEXT_PUBLIC_CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment + }\nNEXT_PUBLIC_CONTENTSTACK_REGION=${region.name + }\nNEXT_PUBLIC_CONTENTSTACK_PREVIEW=${livePreviewEnabled ? 'true' : 'false'}`; + result = await writeEnvFile(content, filePath); + break; + + case 'kickstart-nuxt': + case 'kickstart-nuxt-ssr': + fileName = `.env`; + filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); + content = `NUXT_CONTENTSTACK_API_KEY=${environmentVariables.api_key + }\nNUXT_CONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\nNUXT_CONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || '' + }\nNUXT_CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment + }\nNUXT_CONTENTSTACK_REGION=${region.name + }\nNUXT_CONTENTSTACK_PREVIEW=${livePreviewEnabled ? 'true' : 'false'}`; + result = await writeEnvFile(content, filePath); + break; + case 'reactjs': case 'reactjs-starter': fileName = `.env.${environmentVariables.environment}.local`; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); - content = `REACT_APP_CONTENTSTACK_API_KEY=${ - environmentVariables.api_key - }\nREACT_APP_CONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken}${ - livePreviewEnabled - ? `\nREACT_APP_CONTENTSTACK_PREVIEW_TOKEN=${ - environmentVariables.preview_token || `''` - }\nREACT_APP_CONTENTSTACK_PREVIEW_HOST=${previewHost}\nREACT_APP_CONTENTSTACK_APP_HOST=${appHost}\n` + content = `REACT_APP_CONTENTSTACK_API_KEY=${environmentVariables.api_key + }\nREACT_APP_CONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken}${livePreviewEnabled + ? `\nREACT_APP_CONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''` + }\nREACT_APP_CONTENTSTACK_PREVIEW_HOST=${previewHost}\nREACT_APP_CONTENTSTACK_APP_HOST=${appHost}\n` : '\n' - }\nREACT_APP_CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}\n${ - customHost ? '\nREACT_APP_CONTENTSTACK_API_HOST=' + customHost : '' - }${ - !isUSRegion && !customHost ? '\nREACT_APP_CONTENTSTACK_REGION=' + region.name : '' - }\nSKIP_PREFLIGHT_CHECK=true\nREACT_APP_CONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}`; + }\nREACT_APP_CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}\n${customHost ? '\nREACT_APP_CONTENTSTACK_API_HOST=' + customHost : '' + }${!isUSRegion && !customHost ? '\nREACT_APP_CONTENTSTACK_REGION=' + region.name : '' + }\nSKIP_PREFLIGHT_CHECK=true\nREACT_APP_CONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}`; result = await writeEnvFile(content, filePath); break; case 'nextjs': case 'nextjs-starter': fileName = `.env.${environmentVariables.environment}.local`; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); - content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${ - environmentVariables.deliveryToken - }\n${ - livePreviewEnabled - ? `\nCONTENTSTACK_PREVIEW_TOKEN=${ - environmentVariables.preview_token || `''` - }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\nCONTENTSTACK_APP_HOST=${appHost}\n` + content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\n${livePreviewEnabled + ? `\nCONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''` + }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\nCONTENTSTACK_APP_HOST=${appHost}\n` : '\n' - }CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}\nCONTENTSTACK_API_HOST=${ - customHost ? customHost : managementAPIHost - }${ - !isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' - }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\nCONTENTSTACK_LIVE_EDIT_TAGS=false`; + }CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}\nCONTENTSTACK_API_HOST=${customHost ? customHost : managementAPIHost + }${!isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' + }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\nCONTENTSTACK_LIVE_EDIT_TAGS=false`; result = await writeEnvFile(content, filePath); break; case 'compass-app': fileName = '.env'; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); - content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${ - environmentVariables.deliveryToken - }\nCONTENTSTACK_BRANCH=main${ - livePreviewEnabled - ? `\nCONTENTSTACK_PREVIEW_TOKEN=${ - environmentVariables.preview_token || `''` - }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\n` + content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\nCONTENTSTACK_BRANCH=main${livePreviewEnabled + ? `\nCONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''` + }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\n` : '\n' - }CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}${ - !isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' - }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\nCONTENTSTACK_LIVE_EDIT_TAGS=false\nCONTENTSTACK_API_HOST=${ - customHost ? customHost : managementAPIHost - }${ - !isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' - }\nCONTENTSTACK_APP_HOST=${appHost}\nCONTENTSTACK_MANAGEMENT_TOKEN=${ - managementTokenResult.uid - }\nCONTENTSTACK_HOST=${cdnHost}`; + }CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}${!isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' + }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\nCONTENTSTACK_LIVE_EDIT_TAGS=false\nCONTENTSTACK_API_HOST=${customHost ? customHost : managementAPIHost + }${!isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' + }\nCONTENTSTACK_APP_HOST=${appHost}\nCONTENTSTACK_MANAGEMENT_TOKEN=${managementTokenResult.uid + }\nCONTENTSTACK_HOST=${cdnHost}`; result = await writeEnvFile(content, filePath); break; case 'gatsby': case 'gatsby-starter': fileName = `.env.${environmentVariables.environment}`; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); - content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${ - environmentVariables.deliveryToken - }\n${ - livePreviewEnabled - ? `\nCONTENTSTACK_PREVIEW_TOKEN=${ - environmentVariables.preview_token || `''` - }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\nCONTENTSTACK_APP_HOST=${appHost}\n` + content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\n${livePreviewEnabled + ? `\nCONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''` + }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\nCONTENTSTACK_APP_HOST=${appHost}\n` : '\n' - }\nCONTENTSTACK_ENVIRONMENT=${ - environmentVariables.environment - }\nCONTENTSTACK_API_HOST=${managementAPIHost}\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}`; + }\nCONTENTSTACK_ENVIRONMENT=${environmentVariables.environment + }\nCONTENTSTACK_API_HOST=${managementAPIHost}\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}`; result = await writeEnvFile(content, filePath); break; case 'angular': - content = `export const environment = { \n\tproduction:${ - environmentVariables.environment === 'production' ? true : false - }, \n\tconfig : { \n\t\tapi_key: '${environmentVariables.api_key}', \n\t\tdelivery_token: '${ - environmentVariables.deliveryToken - }',\n${ - livePreviewEnabled - ? `\n\tpreivew_token:'${ - environmentVariables.preview_token || `''` - }'\n\tpreview_host:'${previewHost}'\n\tapp_host:'${appHost}'\n` + content = `export const environment = { \n\tproduction:${environmentVariables.environment === 'production' ? true : false + }, \n\tconfig : { \n\t\tapi_key: '${environmentVariables.api_key}', \n\t\tdelivery_token: '${environmentVariables.deliveryToken + }',\n${livePreviewEnabled + ? `\n\tpreivew_token:'${environmentVariables.preview_token || `''` + }'\n\tpreview_host:'${previewHost}'\n\tapp_host:'${appHost}'\n` : '\n' - },\n\t\tenvironment: '${environmentVariables.environment}'${ - !isUSRegion && !customHost ? `,\n\t\tregion: '${region.name}'` : '' - } \n\t } \n };`; + },\n\t\tenvironment: '${environmentVariables.environment}'${!isUSRegion && !customHost ? `,\n\t\tregion: '${region.name}'` : '' + } \n\t } \n };`; fileName = `.env${environmentVariables.environment === 'production' ? '.prod' : ''}`; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), 'src', 'environments', sanitizePath(fileName))); result = await writeEnvFile(content, filePath); break; case 'angular-starter': - content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${ - environmentVariables.deliveryToken - }\n${ - livePreviewEnabled - ? `\nCONTENTSTACK_PREVIEW_TOKEN=${ - environmentVariables.preview_token || `''` - }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\nCONTENTSTACK_APP_HOST=${appHost}\n` + content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\n${livePreviewEnabled + ? `\nCONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''` + }\nCONTENTSTACK_PREVIEW_HOST=${previewHost}\nCONTENTSTACK_APP_HOST=${appHost}\n` : '\n' - }CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}\nCONTENTSTACK_API_HOST=${ - customHost ? customHost : managementAPIHost - }${ - !isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' - }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\nCONTENTSTACK_LIVE_EDIT_TAGS=false`; + }CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}\nCONTENTSTACK_API_HOST=${customHost ? customHost : managementAPIHost + }${!isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' + }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\nCONTENTSTACK_LIVE_EDIT_TAGS=false`; fileName = `.env${environmentVariables.environment === 'production' ? '.prod' : ''}`; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); result = await writeEnvFile(content, filePath); @@ -313,37 +310,27 @@ const envFileHandler = async ( fileName = production ? '.env.production' : '.env'; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); // Note: Stencil app needs all the env variables, even if they are not having values otherwise the rollup does not work properly and throws process in undefined error. - content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${ - environmentVariables.deliveryToken - }\n${ - livePreviewEnabled - ? `\nCONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''`}\nCONTENTSTACK_PREVIEW_HOST=${ - customHost ?? previewHost - }\nCONTENTSTACK_APP_HOST=${appHost}` + content = `CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nCONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\n${livePreviewEnabled + ? `\nCONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''`}\nCONTENTSTACK_PREVIEW_HOST=${customHost ?? previewHost + }\nCONTENTSTACK_APP_HOST=${appHost}` : '\n' - }\nCONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}${ - !isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' - }\nCONTENTSTACK_API_HOST=${ - customHost ? customHost : managementAPIHost - }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\n\nCONTENTSTACK_LIVE_EDIT_TAGS=false`; + }\nCONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}${!isUSRegion && !customHost ? '\nCONTENTSTACK_REGION=' + region.name : '' + }\nCONTENTSTACK_API_HOST=${customHost ? customHost : managementAPIHost + }\nCONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}\n\nCONTENTSTACK_LIVE_EDIT_TAGS=false`; result = await writeEnvFile(content, filePath); break; case 'vue-starter': fileName = '.env'; filePath = pathValidator(path.join(sanitizePath(clonedDirectory), sanitizePath(fileName))); - content = `VUE_APP_CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nVUE_APP_CONTENTSTACK_DELIVERY_TOKEN=${ - environmentVariables.deliveryToken - }\n${ - livePreviewEnabled - ? `\nVUE_APP_CONTENTSTACK_PREVIEW_TOKEN=${ - environmentVariables.preview_token || `''` - }\nVUE_APP_CONTENTSTACK_PREVIEW_HOST=${previewHost}\nVUE_APP_CONTENTSTACK_APP_HOST=${appHost}\n` + content = `VUE_APP_CONTENTSTACK_API_KEY=${environmentVariables.api_key}\nVUE_APP_CONTENTSTACK_DELIVERY_TOKEN=${environmentVariables.deliveryToken + }\n${livePreviewEnabled + ? `\nVUE_APP_CONTENTSTACK_PREVIEW_TOKEN=${environmentVariables.preview_token || `''` + }\nVUE_APP_CONTENTSTACK_PREVIEW_HOST=${previewHost}\nVUE_APP_CONTENTSTACK_APP_HOST=${appHost}\n` : '\n' - }\nVUE_APP_CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}${ - customHost ? '\nVUE_APP_CONTENTSTACK_API_HOST=' + customHost : '' - }${ - !isUSRegion && !customHost ? '\nVUE_APP_CONTENTSTACK_REGION=' + region.name : '' - }\nVUE_APP_CONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}`; + }\nVUE_APP_CONTENTSTACK_ENVIRONMENT=${environmentVariables.environment}${customHost ? '\nVUE_APP_CONTENTSTACK_API_HOST=' + customHost : '' + }${!isUSRegion && !customHost ? '\nVUE_APP_CONTENTSTACK_REGION=' + region.name : '' + }\nVUE_APP_CONTENTSTACK_LIVE_PREVIEW=${livePreviewEnabled}`; result = await writeEnvFile(content, filePath); break; default: diff --git a/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts b/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts index 483fd2032c..2a353d1d5d 100644 --- a/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts +++ b/packages/contentstack-bootstrap/src/commands/cm/bootstrap.ts @@ -6,6 +6,7 @@ import { inquireApp, inquireAppType, inquireLivePreviewSupport, + inquireRunDevServer, } from '../../bootstrap/interactive'; import { printFlagDeprecation, @@ -27,15 +28,16 @@ export default class BootstrapCommand extends Command { static examples = [ '$ csdx cm:bootstrap', '$ csdx cm:bootstrap --project-dir ', - '$ csdx cm:bootstrap --app-name "reactjs-starter" --project-dir ', - '$ csdx cm:bootstrap --app-name "reactjs-starter" --project-dir --stack-api-key "stack-api-key"', - '$ csdx cm:bootstrap --app-name "reactjs-starter" --project-dir --org "your-org-uid" --stack-name "stack-name"', + '$ csdx cm:bootstrap --app-name "kickstart-next" --project-dir ', + '$ csdx cm:bootstrap --app-name "kickstart-next" --project-dir --stack-api-key "stack-api-key"', + '$ csdx cm:bootstrap --app-name "kickstart-next" --project-dir --org "your-org-uid" --stack-name "stack-name"', + '$ csdx cm:bootstrap --app-name "kickstart-next" --project-dir --run-dev-server', ]; static flags: FlagInput = { 'app-name': flags.string({ description: - 'App name, reactjs-starter, nextjs-starter, gatsby-starter, angular-starter, nuxt-starter, vue-starter, stencil-starter', + 'App name, kickstart-next, kickstart-next-ssr, kickstart-next-ssg, kickstart-next-graphql, kickstart-next-middleware, kickstart-nuxt, kickstart-nuxt-ssr', multiple: false, required: false, }), @@ -76,11 +78,16 @@ export default class BootstrapCommand extends Command { char: 'y', required: false, }), + 'run-dev-server': flags.boolean({ + description: 'Automatically start the development server after setup', + required: false, + default: false, + }), // To be deprecated appName: flags.string({ char: 'a', - description: 'App name, reactjs-starter, nextjs-starter, gatsby-starter, angular-starter, nuxt-starter', + description: 'App name, kickstart-next, kickstart-next-ssr, kickstart-next-ssg, kickstart-next-graphql, kickstart-next-middleware, kickstart-nuxt, kickstart-nuxt-ssr', multiple: false, required: false, hidden: true, @@ -153,7 +160,6 @@ export default class BootstrapCommand extends Command { const yes = bootstrapCommandFlags.yes as string; const appConfig: AppConfig = getAppLevelConfigByName(selectedAppName || selectedApp.configKey); - const master_locale = appConfig.master_locale || DEFAULT_MASTER_LOCALE; let cloneDirectory = @@ -165,6 +171,8 @@ export default class BootstrapCommand extends Command { cloneDirectory = resolve(cloneDirectory); const livePreviewEnabled = bootstrapCommandFlags.yes ? true : await inquireLivePreviewSupport(); + const runDevServer = bootstrapCommandFlags['run-dev-server'] || + (bootstrapCommandFlags.yes ? false : await inquireRunDevServer()); const seedParams: SeedParams = {}; const stackAPIKey = bootstrapCommandFlags['stack-api-key']; @@ -190,6 +198,7 @@ export default class BootstrapCommand extends Command { region: this.region, appType, livePreviewEnabled, + runDevServer, master_locale, }; const bootstrap = new Bootstrap(options); @@ -198,4 +207,4 @@ export default class BootstrapCommand extends Command { this.error(error, { exit: 1, suggestions: error.suggestions }); } } -} \ No newline at end of file +} diff --git a/packages/contentstack-bootstrap/src/config.ts b/packages/contentstack-bootstrap/src/config.ts index 9275cfd4d8..c745b07b29 100644 --- a/packages/contentstack-bootstrap/src/config.ts +++ b/packages/contentstack-bootstrap/src/config.ts @@ -16,23 +16,64 @@ export interface AppConfig { const config: Configuration = { sampleApps: [ - { displayName: 'React JS', configKey: 'reactjs' }, - { displayName: 'Next JS', configKey: 'nextjs' }, - { displayName: 'Gatsby', configKey: 'gatsby' }, - { displayName: 'Angular', configKey: 'angular' }, + { displayName: 'React JS (Deprecated)', configKey: 'reactjs' }, + { displayName: 'Next JS (Deprecated)', configKey: 'nextjs' }, + { displayName: 'Gatsby (Deprecated)', configKey: 'gatsby' }, + { displayName: 'Angular (Deprecated)', configKey: 'angular' }, ], starterApps: [ - { displayName: 'React JS', configKey: 'reactjs-starter' }, - { displayName: 'Next JS', configKey: 'nextjs-starter' }, - { displayName: 'Gatsby', configKey: 'gatsby-starter' }, - { displayName: 'Angular', configKey: 'angular-starter' }, - { displayName: 'Nuxt JS (To be Deprecated)', configKey: 'nuxt-starter' }, - { displayName: 'Vue JS', configKey: 'vue-starter' }, - { displayName: 'Stencil', configKey: 'stencil-starter' }, - { displayName: 'Nuxt3', configKey: 'nuxt3-starter' }, - { displayName: 'Compass App', configKey: 'compass-app' } + { displayName: 'Compass App', configKey: 'compass-app' }, + { displayName: 'Kickstart Next.js', configKey: 'kickstart-next' }, + { displayName: 'Kickstart Next.js SSR', configKey: 'kickstart-next-ssr' }, + { displayName: 'Kickstart Next.js SSG', configKey: 'kickstart-next-ssg' }, + { displayName: 'Kickstart Next.js GraphQL', configKey: 'kickstart-next-graphql' }, + { displayName: 'Kickstart Next.js Middleware', configKey: 'kickstart-next-middleware' }, + { displayName: 'Kickstart NuxtJS', configKey: 'kickstart-next-nuxt' }, + { displayName: 'Kickstart NuxtJS SSR', configKey: 'kickstart-next-nuxt-ssr' }, + + { displayName: 'React JS (Deprecated)', configKey: 'reactjs-starter' }, + { displayName: 'Next JS (Deprecated)', configKey: 'nextjs-starter' }, + { displayName: 'Gatsby (Deprecated)', configKey: 'gatsby-starter' }, + { displayName: 'Angular (Deprecated)', configKey: 'angular-starter' }, + { displayName: 'Nuxt JS (Deprecated)', configKey: 'nuxt-starter' }, + { displayName: 'Vue JS (Deprecated)', configKey: 'vue-starter' }, + { displayName: 'Stencil (Deprecated)', configKey: 'stencil-starter' }, + { displayName: 'Nuxt3 (Deprecated)', configKey: 'nuxt3-starter' }, ], appLevelConfig: { + 'kickstart-next': { + source: 'contentstack/kickstart-next', + stack: 'contentstack/kickstart-stack-seed', + }, + + 'kickstart-next-ssr': { + source: 'contentstack/kickstart-next-ssr', + stack: 'contentstack/kickstart-stack-seed', + }, + + 'kickstart-next-ssg': { + source: 'contentstack/kickstart-next-ssg', + stack: 'contentstack/kickstart-stack-seed', + }, + + 'kickstart-next-graphql': { + source: 'contentstack/kickstart-next-graphql', + stack: 'contentstack/kickstart-stack-seed', + }, + + 'kickstart-next-middleware': { + source: 'contentstack/kickstart-next-middleware', + stack: 'contentstack/kickstart-stack-seed', + }, + + 'kickstart-next-nuxt': { + source: 'contentstack/kickstart-next-nuxt', + stack: 'contentstack/kickstart-stack-seed', + }, + 'kickstart-next-nuxt-ssr': { + source: 'contentstack/kickstart-next-nuxt-ssr', + stack: 'contentstack/kickstart-stack-seed', + }, nextjs: { source: 'contentstack/contentstack-nextjs-react-universal-demo', stack: 'contentstack/stack-contentstack-nextjs-react-universal-demo', @@ -100,4 +141,4 @@ export function getAppLevelConfigByName(appConfigKey: string): any { } config.appLevelConfig[appConfigKey].appConfigKey = appConfigKey; return config.appLevelConfig[appConfigKey]; -} \ No newline at end of file +} diff --git a/packages/contentstack-bootstrap/test/bootstrap-error-handling.test.js b/packages/contentstack-bootstrap/test/bootstrap-error-handling.test.js new file mode 100644 index 0000000000..85ec0e6076 --- /dev/null +++ b/packages/contentstack-bootstrap/test/bootstrap-error-handling.test.js @@ -0,0 +1,33 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); + +describe('Bootstrap Error Handling Tests', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should handle npm install failures gracefully', () => { + const messages = require('../messages/index.json'); + expect(messages.CLI_BOOTSTRAP_DEPENDENCIES_INSTALL_FAILED).to.exist; + expect(messages.CLI_BOOTSTRAP_DEV_SERVER_FAILED).to.exist; + expect(messages.CLI_BOOTSTRAP_NO_API_KEY_FOUND).to.exist; + }); + + it('should have proper error messages defined', () => { + const messages = require('../messages/index.json'); + expect(messages.CLI_BOOTSTRAP_REPO_NOT_FOUND).to.include('%s'); + expect(messages.CLI_BOOTSTRAP_STACK_CREATION_FAILED).to.include('%s'); + }); + + it('should export proper interfaces and constants', () => { + const Bootstrap = require('../lib/bootstrap/index'); + expect(Bootstrap.ENGLISH_LOCALE).to.equal('en-us'); + expect(Bootstrap.default).to.be.a('function'); + }); +}); diff --git a/packages/contentstack-bootstrap/test/bootstrap-integration.test.js b/packages/contentstack-bootstrap/test/bootstrap-integration.test.js new file mode 100644 index 0000000000..902a010573 --- /dev/null +++ b/packages/contentstack-bootstrap/test/bootstrap-integration.test.js @@ -0,0 +1,43 @@ +const { expect } = require('chai'); + +describe('Bootstrap Integration Tests', () => { + it('should have proper module structure', () => { + const Bootstrap = require('../lib/bootstrap/index'); + const interactive = require('../lib/bootstrap/interactive'); + const utils = require('../lib/bootstrap/utils'); + + expect(Bootstrap.default).to.be.a('function'); + expect(interactive.inquireApp).to.be.a('function'); + expect(interactive.inquireRunDevServer).to.be.a('function'); + expect(utils.setupEnvironments).to.be.a('function'); + }); + + it('should validate command flag structure', () => { + const BootstrapCommand = require('../lib/commands/cm/bootstrap').default; + + expect(BootstrapCommand.flags).to.have.property('run-dev-server'); + expect(BootstrapCommand.flags).to.have.property('app-name'); + expect(BootstrapCommand.flags).to.have.property('project-dir'); + expect(BootstrapCommand.flags).to.have.property('stack-api-key'); + expect(BootstrapCommand.flags).to.have.property('org'); + expect(BootstrapCommand.flags).to.have.property('stack-name'); + expect(BootstrapCommand.flags).to.have.property('yes'); + }); + + it('should validate GitHub client exports', () => { + const GitHubClient = require('../lib/bootstrap/github/client').default; + const GithubError = require('../lib/bootstrap/github/github-error').default; + + expect(GitHubClient).to.be.a('function'); + expect(GitHubClient.parsePath).to.be.a('function'); + expect(GithubError).to.be.a('function'); + }); + + it('should validate configuration structure', () => { + const config = require('../lib/config'); + + expect(config.getAppLevelConfigByName).to.be.a('function'); + expect(config.default).to.have.property('starterApps'); + expect(config.default).to.have.property('sampleApps'); + }); +}); diff --git a/packages/contentstack-bootstrap/test/bootstrap.test.js b/packages/contentstack-bootstrap/test/bootstrap.test.js index 6231a1befc..fa7b66010c 100644 --- a/packages/contentstack-bootstrap/test/bootstrap.test.js +++ b/packages/contentstack-bootstrap/test/bootstrap.test.js @@ -249,4 +249,76 @@ describe('Bootstrapping an app', () => { throw err; } }); + + it('should handle --run-dev-server flag correctly', async () => { + // Mock execSync and spawn for npm commands + const childProcess = require('child_process'); + const execSyncStub = sandbox.stub(childProcess, 'execSync').returns(); + const spawnStub = sandbox.stub(childProcess, 'spawn').returns({ + on: sandbox.stub().callsArg(1), + }); + + try { + const MockBootstrapCommand = class extends Command { + async run() { + cliux.loader('Cloning the selected app'); + await githubClientStub.getLatest(process.cwd()); + cliux.loader(); + + const result = await ContentStackSeed.run(['--repo', mock.appConfig.stack]); + + if (result?.api_key) { + await utils.setupEnvironments( + { + stack: () => ({ + environment: () => ({ + query: () => ({ + find: () => Promise.resolve(mock.environments), + }), + }), + deliveryToken: () => ({ + create: () => Promise.resolve(mock.deliveryToken), + }), + }), + }, + result.api_key, + mock.appConfig, + process.cwd(), + mock.region, + true, + mock.managementToken.token, + ); + } + + cliux.print(messages.CLI_BOOTSTRAP_SUCCESS); + + // Simulate runDevServer = true + const runDevServer = true; + if (runDevServer) { + cliux.loader(messages.CLI_BOOTSTRAP_INSTALLING_DEPENDENCIES); + execSyncStub('npm install', { cwd: process.cwd(), stdio: 'inherit' }); + cliux.loader(); + cliux.print(messages.CLI_BOOTSTRAP_DEPENDENCIES_INSTALLED); + cliux.print(messages.CLI_BOOTSTRAP_STARTING_DEV_SERVER); + cliux.print(messages.CLI_BOOTSTRAP_DEV_SERVER_STARTED); + spawnStub('npm', ['run', 'dev'], { cwd: process.cwd(), stdio: 'inherit', shell: true }); + } + } + }; + + const command = new MockBootstrapCommand(); + await command.run(); + + // Verify that npm install was called + expect(execSyncStub.calledWith('npm install')).to.be.true; + // Verify that npm run dev was called + expect(spawnStub.calledWith('npm', ['run', 'dev'])).to.be.true; + // Verify messages were printed + expect(stdout).to.include(messages.CLI_BOOTSTRAP_DEPENDENCIES_INSTALLED); + expect(stdout).to.include(messages.CLI_BOOTSTRAP_DEV_SERVER_STARTED); + } catch (err) { + console.error('Error during dev server test:', err); + throw err; + } + }); }); diff --git a/packages/contentstack-bootstrap/test/interactive-dev-server.test.js b/packages/contentstack-bootstrap/test/interactive-dev-server.test.js new file mode 100644 index 0000000000..7725fda04e --- /dev/null +++ b/packages/contentstack-bootstrap/test/interactive-dev-server.test.js @@ -0,0 +1,108 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); +const inquirer = require('inquirer'); +const { inquireRunDevServer } = require('../lib/bootstrap/interactive'); +const messages = require('../messages/index.json'); + +describe('Interactive Dev Server Tests', () => { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('#inquireRunDevServer', () => { + it('should return true when user selects yes', async () => { + sandbox.stub(inquirer, 'prompt').resolves({ runDevServer: true }); + + const result = await inquireRunDevServer(); + + expect(result).to.be.true; + expect(inquirer.prompt.calledOnce).to.be.true; + + const promptArgs = inquirer.prompt.getCall(0).args[0]; + expect(promptArgs.type).to.equal('confirm'); + expect(promptArgs.name).to.equal('runDevServer'); + expect(promptArgs.message).to.equal(messages.CLI_BOOTSTRAP_RUN_DEV_SERVER_ENQUIRY); + expect(promptArgs.default).to.be.true; + }); + + it('should return false when user selects no', async () => { + sandbox.stub(inquirer, 'prompt').resolves({ runDevServer: false }); + + const result = await inquireRunDevServer(); + + expect(result).to.be.false; + expect(inquirer.prompt.calledOnce).to.be.true; + }); + + it('should have correct message asking about install and run', () => { + expect(messages.CLI_BOOTSTRAP_RUN_DEV_SERVER_ENQUIRY).to.include('install dependencies and run the app locally'); + expect(messages.CLI_BOOTSTRAP_RUN_DEV_SERVER_ENQUIRY).to.include('npm install && npm run dev'); + }); + + it('should respect --run-dev-server flag in command parsing', async () => { + // This test would require more complex mocking of the command parsing + // For now, we can verify the flag is properly defined + const BootstrapCommand = require('../lib/commands/cm/bootstrap').default; + + expect(BootstrapCommand.flags).to.have.property('run-dev-server'); + expect(BootstrapCommand.flags['run-dev-server'].description).to.include('development server after setup'); + }); + }); + + describe('Edge Cases and Interactive Functions', () => { + it('should handle all interactive functions', async () => { + const { + inquireApp, + inquireCloneDirectory, + inquireAppType, + inquireLivePreviewSupport, + inquireGithubAccessToken, + } = require('../lib/bootstrap/interactive'); + + // Test inquireApp with exit selection + sandbox + .stub(inquirer, 'prompt') + .onFirstCall() + .resolves({ app: 'Exit' }) + .onSecondCall() + .resolves({ path: 'Other' }) + .onThirdCall() + .resolves({ path: '/custom/path' }) + .onCall(3) + .resolves({ type: 'sampleapp' }) + .onCall(4) + .resolves({ livePreviewEnabled: false }) + .onCall(5) + .resolves({ token: 'test-token' }); + + try { + await inquireApp([{ displayName: 'Test App', value: 'test' }]); + expect.fail('Should have thrown an error for Exit'); + } catch (error) { + expect(error.message).to.equal('Exit'); + } + + // Test inquireCloneDirectory with "Other" option + const customPath = await inquireCloneDirectory(); + expect(customPath).to.equal('/custom/path'); + + // Test inquireAppType + const appType = await inquireAppType(); + expect(appType).to.equal('sampleapp'); + + // Test inquireLivePreviewSupport + const livePreview = await inquireLivePreviewSupport(); + expect(livePreview).to.be.false; + + // Test inquireGithubAccessToken + const token = await inquireGithubAccessToken(); + expect(token).to.equal('test-token'); + }); + }); +}); From 30ce5bf62b7f95e17b55f2fd9a5527c30c366ec9 Mon Sep 17 00:00:00 2001 From: Tim Benniks Date: Thu, 3 Jul 2025 09:14:04 +0200 Subject: [PATCH 2/2] chrore: added files in .talismanrc for false positives --- .talismanrc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.talismanrc b/.talismanrc index 0a3e10a37a..ca5edae336 100644 --- a/.talismanrc +++ b/.talismanrc @@ -132,4 +132,8 @@ fileignoreconfig: checksum: 09f3b73dd995bafc253265c676f06308513e6b1842d9bc01d39e6b6945a54c7d - filename: packages/contentstack-export/src/export/modules/assets.ts checksum: c7f19e6c4a212329d981cebce9a9a8393923dd7c85feb762ddcdca678f7a9349 -version: "1.0" \ No newline at end of file + - filename: packages/contentstack-bootstrap/messages/index.json + checksum: c435ceaa709a7504da303a6ea674e07a89030d8ad4152e7917cd17e7f3e58052 + - filename: packages/contentstack-bootstrap/src/config.ts + checksum: 65d300dc729fb84f5446c0b14921555db01fe5c90be3d297e3d0418a37b3696a +version: '1.0'