From 1b8077564e4317ff21b506e91ea0953bdb72d569 Mon Sep 17 00:00:00 2001 From: afmireski Date: Sat, 22 Feb 2025 16:06:21 -0300 Subject: [PATCH 01/10] feat: Implemented wrapper functions for questions --- .mise.toml | 2 ++ .vscode/settings.json | 5 +++++ bin/util/question/index.js | 11 +++++++++++ bin/util/question/inquirer.js | 15 +++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 .mise.toml create mode 100644 .vscode/settings.json create mode 100644 bin/util/question/index.js create mode 100644 bin/util/question/inquirer.js diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 0000000..6a068cd --- /dev/null +++ b/.mise.toml @@ -0,0 +1,2 @@ +[tools] +node = "22.13" diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fa74c7a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "jest.runMode": { + "type": "on-demand" + } +} diff --git a/bin/util/question/index.js b/bin/util/question/index.js new file mode 100644 index 0000000..38affd1 --- /dev/null +++ b/bin/util/question/index.js @@ -0,0 +1,11 @@ +export function confirmationQuestion(questionCallback) { + return async function (message, defaultValue = true) { + return await questionCallback(message, defaultValue); + }; +} + +export function selectionQuestion(questionCallback) { + return async function (message, choices) { + return await questionCallback(message, choices); + }; +} diff --git a/bin/util/question/inquirer.js b/bin/util/question/inquirer.js new file mode 100644 index 0000000..e0f4c51 --- /dev/null +++ b/bin/util/question/inquirer.js @@ -0,0 +1,15 @@ +import { confirm, select } from "@inquirer/prompts"; + +export async function askConfirmation(message, defaultValue = true) { + return confirm({ + message, + default: defaultValue, + }); +} + +export async function askSelection(message, choices) { + return select({ + message: message, + choices: choices.map((choice) => ({ name: choice, value: choice })), + }); +} From d8fc0a415f46d8adb1a2ca18af0346f4b2445045 Mon Sep 17 00:00:00 2001 From: afmireski Date: Sat, 22 Feb 2025 16:06:46 -0300 Subject: [PATCH 02/10] refactor: Changed inquirer functions by generic functions --- bin/util/docker.js | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/bin/util/docker.js b/bin/util/docker.js index 83e22a3..f9e6f05 100644 --- a/bin/util/docker.js +++ b/bin/util/docker.js @@ -1,21 +1,25 @@ -import { confirm, select } from "@inquirer/prompts"; import { templates } from "../configs.js"; import chalk from "chalk"; +import { confirmationQuestion, selectionQuestion } from "./question/index.js"; +import { askConfirmation, askSelection } from "./question/inquirer.js"; + +const confirmQuestion = confirmationQuestion(askConfirmation); +const selectQuestion = selectionQuestion(askSelection); export async function userPrompts(needDB) { let runtimeNeedDB = false; if (needDB) { - runtimeNeedDB = await confirm({ - message: "Do you wish to containerize DB service? (Default: Yes)", - default: true, - }); + runtimeNeedDB = await confirmQuestion( + "Do you wish to containerize DB service? (Default: Yes)", + true, + ); } - const addCacheService = await confirm({ - message: "Do you want to add a cache service? (Default: No)", - default: false, - }); + const addCacheService = await confirmQuestion( + "Do you want to add a cache service? (Default: No)", + false, + ); return { runtimeNeedDB, addCacheService }; } @@ -30,10 +34,10 @@ async function promptCacheService(packageName) { "amazon/aws-elasticache:redis", ]; - const image = await select({ - message: "Select the Docker image for the cache service:", - choices: cacheImages.map((img) => ({ name: img, value: img })), - }); + const image = await selectQuestion( + "Select the Docker image for the cache service:", + cacheImages, + ); let ports; switch (image) { From 27fcc5cf5efd582274c12e3d10ea83a485602c37 Mon Sep 17 00:00:00 2001 From: afmireski Date: Sun, 23 Feb 2025 19:35:59 -0300 Subject: [PATCH 03/10] refactor: Removed index.js on util/question - That approach don't work well with mock attempts --- bin/util/docker.js | 12 ++++++------ bin/util/question/index.js | 11 ----------- 2 files changed, 6 insertions(+), 17 deletions(-) delete mode 100644 bin/util/question/index.js diff --git a/bin/util/docker.js b/bin/util/docker.js index f9e6f05..9ae747c 100644 --- a/bin/util/docker.js +++ b/bin/util/docker.js @@ -3,20 +3,20 @@ import chalk from "chalk"; import { confirmationQuestion, selectionQuestion } from "./question/index.js"; import { askConfirmation, askSelection } from "./question/inquirer.js"; -const confirmQuestion = confirmationQuestion(askConfirmation); -const selectQuestion = selectionQuestion(askSelection); +// const confirmQuestion = (confirmationQuestion(askConfirmation)); +// const selectQuestion = selectionQuestion(askSelection); export async function userPrompts(needDB) { - let runtimeNeedDB = false; + let runtimeNeedDB = false; if (needDB) { - runtimeNeedDB = await confirmQuestion( + runtimeNeedDB = await askConfirmation( "Do you wish to containerize DB service? (Default: Yes)", true, ); } - const addCacheService = await confirmQuestion( + const addCacheService = await askConfirmation( "Do you want to add a cache service? (Default: No)", false, ); @@ -34,7 +34,7 @@ async function promptCacheService(packageName) { "amazon/aws-elasticache:redis", ]; - const image = await selectQuestion( + const image = await askSelection( "Select the Docker image for the cache service:", cacheImages, ); diff --git a/bin/util/question/index.js b/bin/util/question/index.js deleted file mode 100644 index 38affd1..0000000 --- a/bin/util/question/index.js +++ /dev/null @@ -1,11 +0,0 @@ -export function confirmationQuestion(questionCallback) { - return async function (message, defaultValue = true) { - return await questionCallback(message, defaultValue); - }; -} - -export function selectionQuestion(questionCallback) { - return async function (message, choices) { - return await questionCallback(message, choices); - }; -} From 8a4f4688bf1cd8c866790750116b466e1fa752d3 Mon Sep 17 00:00:00 2001 From: afmireski Date: Sun, 23 Feb 2025 19:37:23 -0300 Subject: [PATCH 04/10] refactor: Moved initCommand to init.js - Moved was needed because if initCommand was called during tests, index.js was loaded, breaking the execution --- bin/index.js | 506 +++++++++++++++++++++++++-------------------------- bin/init.js | 262 ++++++++++++++++++++++++++ 2 files changed, 512 insertions(+), 256 deletions(-) create mode 100644 bin/init.js diff --git a/bin/index.js b/bin/index.js index 3460b1b..a253244 100755 --- a/bin/index.js +++ b/bin/index.js @@ -1,26 +1,11 @@ #!/usr/bin/env node import { program } from "commander"; -import fs from "fs-extra"; -import path from "path"; -import { fileURLToPath } from "url"; -import { execSync } from "child_process"; import figlet from "figlet"; import chalk from "chalk"; -import { createSpinner } from "nanospinner"; import { metadata, commands, templates } from "./configs.js"; -import validate from "validate-npm-package-name"; -import { - getServicesData, - generateDockerComposeFile, - userPrompts, -} from "./util/docker.js"; -import { initMenu } from "./util/menu.js"; import { clearCWD } from "./util/clear.js"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const parentDir = path.dirname(__dirname); +import { initCommand } from "./init.js"; program .name(metadata.command) @@ -96,246 +81,255 @@ program clearCWD(); }); -async function initCommand(options) { - const selectedTemplate = options.template || "basic"; // Default to 'basic' if no template is specified - const packageName = options.name || "qse-server"; // Default to 'qse-server' if no name is specified - const removeNodemon = options.removeNodemon; - const removeDependencies = options.removeDeps; - const dockerCompose = options.dockerCompose; - - if (!options.template) { - initMenu(initCommand); - return; - } - - if (packageName) { - const validateResult = validate(packageName); - if (validateResult.validForNewPackages === false) { - const errors = validateResult.errors || validateResult.warnings; - console.error( - chalk.red.bold( - `Invalid package name: ${errors.join( - ", ", - )}. Please provide a valid package name.`, - ), - ); - return; - } - } - - if (!templates[selectedTemplate]) { - console.error( - chalk.red( - `Template ${chalk.bgRed.bold( - selectedTemplate, - )} does not exist. To see available templates use ${chalk.yellow( - '"qse list"', - )}.`, - ), - ); - return; - } - - const targetDir = process.cwd(); - const templatePath = path.join( - parentDir, - "templates", - templates[selectedTemplate].name, - ); - - const isUrl = templates[selectedTemplate].isUrl; - const needDB = templates[selectedTemplate].needDB; - let runtimeNeedDB = false; - - let dockerTemplate = - selectedTemplate.split("_")[0] === "express" || - selectedTemplate.split("_")[0] === "basic" - ? "express" - : selectedTemplate.split("_")[0]; - - const dockerTemplatePath = path.join( - parentDir, - "templates", - "Docker", - dockerTemplate, - "Dockerfile", - ); - - const destinationPath = path.join(targetDir); - const dockerFileDestination = path.join(destinationPath, "Dockerfile"); - - if (dockerCompose) { - try { - const userPrompt = await userPrompts(needDB); - if (needDB) { - runtimeNeedDB = userPrompt.runtimeNeedDB; - } - - const serviceData = await getServicesData( - packageName, - selectedTemplate, - runtimeNeedDB, - userPrompt.addCacheService, - ); - - console.log("Starting server initialization..."); - - const dockerSpinner = createSpinner( - `Creating Docker Compose File with Entered Services...`, - ).start(); - - const composeFileContent = generateDockerComposeFile( - runtimeNeedDB, - serviceData, - packageName, - selectedTemplate, - ); - const composeFilePath = path.join(targetDir, "docker-compose.yml"); - - fs.writeFileSync(composeFilePath, composeFileContent); - dockerSpinner.success({ - text: `Docker Compose file generated successfully.`, - }); - } catch (error) { - console.log(chalk.red("Error generating Docker Compose file")); - console.error(error.message); - return; - } - } else { - console.log("Starting server initialization..."); - } - - const copySpinner = createSpinner("Creating server files...").start(); - try { - await fs.copy(templatePath, destinationPath); - if (dockerCompose) { - try { - await fs.copyFile(dockerTemplatePath, dockerFileDestination); - } catch (error) { - copySpinner.error({ text: "Error creating Dockerfile.\n" }); - console.error(error.message); - } - } - - copySpinner.success({ text: "Created server files successfully." }); - - if (removeNodemon) { - const nodemonSpinner = createSpinner("Removing nodemon...").start(); - try { - const packageJsonPath = path.join( - destinationPath, - "package.json", - ); - const packageJsonContent = fs.readFileSync( - packageJsonPath, - "utf8", - ); - const packageJson = JSON.parse(packageJsonContent); - - if ( - packageJson.devDependencies && - packageJson.devDependencies.nodemon - ) { - delete packageJson.devDependencies.nodemon; - if (!Object.keys(packageJson.devDependencies).length) { - delete packageJson.devDependencies; - } - } - if (packageJson.scripts && packageJson.scripts.dev) { - delete packageJson.scripts.dev; - } - - fs.writeFileSync( - packageJsonPath, - JSON.stringify(packageJson, null, 2), - ); - - nodemonSpinner.success({ - text: "Removed nodemon successfully.", - }); - } catch (err) { - nodemonSpinner.error({ text: "Error removing nodemon.\n" }); - console.error(err.message); - } - } - } catch (err) { - copySpinner.error({ text: "Error creating server files.\n" }); - console.error(err.message); - } - - const addNameAndTypeSpinner = createSpinner( - "Adding name and type declaration...", - ).start(); - try { - const packageJsonPath = path.join(targetDir, "package.json"); - const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8"); - const packageJson = JSON.parse(packageJsonContent); - packageJson.name = packageName; // Set custom package name - packageJson.type = "module"; // Define type as module - fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); - - addNameAndTypeSpinner.success({ - text: "Added name and type declaration successfully.", - }); - } catch (err) { - addNameAndTypeSpinner.error({ - text: "Error adding type declaration.\n", - }); - console.error(err.message); - } - - if (!removeDependencies) { - const installDependencies = createSpinner( - "Installing dependency packages...", - ).start(); - try { - execSync("npm i", { stdio: "ignore", cwd: targetDir }); - - installDependencies.success({ - text: "Installed dependencies successfully.", - }); - } catch (err) { - installDependencies.error({ - text: "Error installing dependencies.\n", - }); - console.error(err); - } - } - - console.log(chalk.green.bold("\nSetup complete! To run your server:")); - if (removeDependencies) { - console.log( - chalk.yellow("Install dependencies: "), - chalk.white.bold("npm i"), - ); - } - console.log(chalk.yellow("Run:"), chalk.white.bold("npm start")); - if (!removeNodemon) { - console.log( - chalk.yellow("Run with hot reloading:"), - chalk.white.bold("npm run dev"), - ); - } - if (dockerCompose) { - console.log( - chalk.yellow("To start your services with Docker Compose:"), - chalk.white.bold("docker compose up -d"), - ); - } - - if (dockerCompose && isUrl === true && runtimeNeedDB === true) { - console.log( - chalk.yellow("Important Note:"), - chalk.white("Use"), - chalk.blueBright.bold("host.docker.internal"), - chalk.white("instead of"), - chalk.blueBright.bold("localhost/127.0.0.1"), - chalk.white("in your Database Connection URL in the"), - chalk.blueBright.bold(".env"), - chalk.white("file for Docker to work correctly."), - ); - } -} +// export async function initCommand(options) { +// const selectedTemplate = options.template || "basic"; // Default to 'basic' if no template is specified +// const packageName = options.name || "qse-server"; // Default to 'qse-server' if no name is specified +// const removeNodemon = options.removeNodemon; +// const removeDependencies = options.removeDeps; +// const dockerCompose = options.dockerCompose; + +// console.log(options); + + +// if (!options.template) { +// initMenu(initCommand); +// return; +// } + +// if (packageName) { +// const validateResult = validate(packageName); +// if (validateResult.validForNewPackages === false) { +// const errors = validateResult.errors || validateResult.warnings; +// console.error( +// chalk.red.bold( +// `Invalid package name: ${errors.join( +// ", ", +// )}. Please provide a valid package name.`, +// ), +// ); +// return; +// } +// } + +// if (!templates[selectedTemplate]) { +// console.error( +// chalk.red( +// `Template ${chalk.bgRed.bold( +// selectedTemplate, +// )} does not exist. To see available templates use ${chalk.yellow( +// '"qse list"', +// )}.`, +// ), +// ); +// return; +// } + +// const targetDir = process.cwd(); +// const templatePath = path.join( +// parentDir, +// "templates", +// templates[selectedTemplate].name, +// ); + +// const isUrl = templates[selectedTemplate].isUrl; +// const needDB = templates[selectedTemplate].needDB; +// let runtimeNeedDB = false; + +// let dockerTemplate = +// selectedTemplate.split("_")[0] === "express" || +// selectedTemplate.split("_")[0] === "basic" +// ? "express" +// : selectedTemplate.split("_")[0]; + +// const dockerTemplatePath = path.join( +// parentDir, +// "templates", +// "Docker", +// dockerTemplate, +// "Dockerfile", +// ); + +// const destinationPath = path.join(targetDir); +// const dockerFileDestination = path.join(destinationPath, "Dockerfile"); + +// console.log(parentDir); +// console.log(templatePath); +// console.log(dockerTemplatePath); +// console.log(destinationPath); + + +// if (dockerCompose) { +// try { +// const userPrompt = await userPrompts(needDB); +// if (needDB) { +// runtimeNeedDB = userPrompt.runtimeNeedDB; +// } + +// const serviceData = await getServicesData( +// packageName, +// selectedTemplate, +// runtimeNeedDB, +// userPrompt.addCacheService, +// ); + +// console.log("Starting server initialization..."); + +// const dockerSpinner = createSpinner( +// `Creating Docker Compose File with Entered Services...`, +// ).start(); + +// const composeFileContent = generateDockerComposeFile( +// runtimeNeedDB, +// serviceData, +// packageName, +// selectedTemplate, +// ); +// const composeFilePath = path.join(targetDir, "docker-compose.yml"); + +// fs.writeFileSync(composeFilePath, composeFileContent); +// dockerSpinner.success({ +// text: `Docker Compose file generated successfully.`, +// }); +// } catch (error) { +// console.log(chalk.red("Error generating Docker Compose file")); +// console.error(error.message); +// return; +// } +// } else { +// console.log("Starting server initialization..."); +// } + +// const copySpinner = createSpinner("Creating server files...").start(); +// try { +// await fs.copy(templatePath, destinationPath); +// if (dockerCompose) { +// try { +// await fs.copyFile(dockerTemplatePath, dockerFileDestination); +// } catch (error) { +// copySpinner.error({ text: "Error creating Dockerfile.\n" }); +// console.error(error.message); +// } +// } + +// copySpinner.success({ text: "Created server files successfully." }); + +// if (removeNodemon) { +// const nodemonSpinner = createSpinner("Removing nodemon...").start(); +// try { +// const packageJsonPath = path.join( +// destinationPath, +// "package.json", +// ); +// const packageJsonContent = fs.readFileSync( +// packageJsonPath, +// "utf8", +// ); +// const packageJson = JSON.parse(packageJsonContent); + +// if ( +// packageJson.devDependencies && +// packageJson.devDependencies.nodemon +// ) { +// delete packageJson.devDependencies.nodemon; +// if (!Object.keys(packageJson.devDependencies).length) { +// delete packageJson.devDependencies; +// } +// } +// if (packageJson.scripts && packageJson.scripts.dev) { +// delete packageJson.scripts.dev; +// } + +// fs.writeFileSync( +// packageJsonPath, +// JSON.stringify(packageJson, null, 2), +// ); + +// nodemonSpinner.success({ +// text: "Removed nodemon successfully.", +// }); +// } catch (err) { +// nodemonSpinner.error({ text: "Error removing nodemon.\n" }); +// console.error(err.message); +// } +// } +// } catch (err) { +// copySpinner.error({ text: "Error creating server files.\n" }); +// console.error(err.message); +// } + +// const addNameAndTypeSpinner = createSpinner( +// "Adding name and type declaration...", +// ).start(); +// try { +// const packageJsonPath = path.join(targetDir, "package.json"); +// const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8"); +// const packageJson = JSON.parse(packageJsonContent); +// packageJson.name = packageName; // Set custom package name +// packageJson.type = "module"; // Define type as module +// fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); + +// addNameAndTypeSpinner.success({ +// text: "Added name and type declaration successfully.", +// }); +// } catch (err) { +// addNameAndTypeSpinner.error({ +// text: "Error adding type declaration.\n", +// }); +// console.error(err.message); +// } + +// if (!removeDependencies) { +// const installDependencies = createSpinner( +// "Installing dependency packages...", +// ).start(); +// try { +// execSync("npm i", { stdio: "ignore", cwd: targetDir }); + +// installDependencies.success({ +// text: "Installed dependencies successfully.", +// }); +// } catch (err) { +// installDependencies.error({ +// text: "Error installing dependencies.\n", +// }); +// console.error(err); +// } +// } + +// console.log(chalk.green.bold("\nSetup complete! To run your server:")); +// if (removeDependencies) { +// console.log( +// chalk.yellow("Install dependencies: "), +// chalk.white.bold("npm i"), +// ); +// } +// console.log(chalk.yellow("Run:"), chalk.white.bold("npm start")); +// if (!removeNodemon) { +// console.log( +// chalk.yellow("Run with hot reloading:"), +// chalk.white.bold("npm run dev"), +// ); +// } +// if (dockerCompose) { +// console.log( +// chalk.yellow("To start your services with Docker Compose:"), +// chalk.white.bold("docker compose up -d"), +// ); +// } + +// if (dockerCompose && isUrl === true && runtimeNeedDB === true) { +// console.log( +// chalk.yellow("Important Note:"), +// chalk.white("Use"), +// chalk.blueBright.bold("host.docker.internal"), +// chalk.white("instead of"), +// chalk.blueBright.bold("localhost/127.0.0.1"), +// chalk.white("in your Database Connection URL in the"), +// chalk.blueBright.bold(".env"), +// chalk.white("file for Docker to work correctly."), +// ); +// } +// } const toolIntro = () => { console.log( diff --git a/bin/init.js b/bin/init.js new file mode 100644 index 0000000..bf3b3dd --- /dev/null +++ b/bin/init.js @@ -0,0 +1,262 @@ +import fs from "fs-extra"; +import path from "path"; +import { fileURLToPath } from "url"; +import { execSync } from "child_process"; +import chalk from "chalk"; +import { createSpinner } from "nanospinner"; +import { templates } from "./configs.js"; +import validate from "validate-npm-package-name"; +import { + getServicesData, + generateDockerComposeFile, + userPrompts, +} from "./util/docker.js"; +import { initMenu } from "./util/menu.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const parentDir = path.dirname(__dirname); + +export async function initCommand(options) { + const selectedTemplate = options.template || "basic"; // Default to 'basic' if no template is specified + const packageName = options.name || "qse-server"; // Default to 'qse-server' if no name is specified + const removeNodemon = options.removeNodemon; + const removeDependencies = options.removeDeps; + const dockerCompose = options.dockerCompose; + + console.log(options); + + + if (!options.template) { + initMenu(initCommand); + return; + } + + if (packageName) { + const validateResult = validate(packageName); + if (validateResult.validForNewPackages === false) { + const errors = validateResult.errors || validateResult.warnings; + console.error( + chalk.red.bold( + `Invalid package name: ${errors.join( + ", ", + )}. Please provide a valid package name.`, + ), + ); + return; + } + } + + if (!templates[selectedTemplate]) { + console.error( + chalk.red( + `Template ${chalk.bgRed.bold( + selectedTemplate, + )} does not exist. To see available templates use ${chalk.yellow( + '"qse list"', + )}.`, + ), + ); + return; + } + + const targetDir = process.cwd(); + const templatePath = path.join( + parentDir, + "templates", + templates[selectedTemplate].name, + ); + + const isUrl = templates[selectedTemplate].isUrl; + const needDB = templates[selectedTemplate].needDB; + let runtimeNeedDB = false; + + let dockerTemplate = + selectedTemplate.split("_")[0] === "express" || + selectedTemplate.split("_")[0] === "basic" + ? "express" + : selectedTemplate.split("_")[0]; + + const dockerTemplatePath = path.join( + parentDir, + "templates", + "Docker", + dockerTemplate, + "Dockerfile", + ); + + const destinationPath = path.join(targetDir); + const dockerFileDestination = path.join(destinationPath, "Dockerfile"); + + if (dockerCompose) { + try { + const userPrompt = await userPrompts(needDB); + if (needDB) { + runtimeNeedDB = userPrompt.runtimeNeedDB; + } + + const serviceData = await getServicesData( + packageName, + selectedTemplate, + runtimeNeedDB, + userPrompt.addCacheService, + ); + + console.log("Starting server initialization..."); + + const dockerSpinner = createSpinner( + `Creating Docker Compose File with Entered Services...`, + ).start(); + + const composeFileContent = generateDockerComposeFile( + runtimeNeedDB, + serviceData, + packageName, + selectedTemplate, + ); + const composeFilePath = path.join(targetDir, "docker-compose.yml"); + + fs.writeFileSync(composeFilePath, composeFileContent); + dockerSpinner.success({ + text: `Docker Compose file generated successfully.`, + }); + } catch (error) { + console.log(chalk.red("Error generating Docker Compose file")); + console.error(error.message); + return; + } + } else { + console.log("Starting server initialization..."); + } + + const copySpinner = createSpinner("Creating server files...").start(); + try { + await fs.copy(templatePath, destinationPath); + if (dockerCompose) { + try { + await fs.copyFile(dockerTemplatePath, dockerFileDestination); + } catch (error) { + copySpinner.error({ text: "Error creating Dockerfile.\n" }); + console.error(error.message); + } + } + + copySpinner.success({ text: "Created server files successfully." }); + + if (removeNodemon) { + const nodemonSpinner = createSpinner("Removing nodemon...").start(); + try { + const packageJsonPath = path.join( + destinationPath, + "package.json", + ); + const packageJsonContent = fs.readFileSync( + packageJsonPath, + "utf8", + ); + const packageJson = JSON.parse(packageJsonContent); + + if ( + packageJson.devDependencies && + packageJson.devDependencies.nodemon + ) { + delete packageJson.devDependencies.nodemon; + if (!Object.keys(packageJson.devDependencies).length) { + delete packageJson.devDependencies; + } + } + if (packageJson.scripts && packageJson.scripts.dev) { + delete packageJson.scripts.dev; + } + + fs.writeFileSync( + packageJsonPath, + JSON.stringify(packageJson, null, 2), + ); + + nodemonSpinner.success({ + text: "Removed nodemon successfully.", + }); + } catch (err) { + nodemonSpinner.error({ text: "Error removing nodemon.\n" }); + console.error(err.message); + } + } + } catch (err) { + copySpinner.error({ text: "Error creating server files.\n" }); + console.error(err.message); + } + + const addNameAndTypeSpinner = createSpinner( + "Adding name and type declaration...", + ).start(); + try { + const packageJsonPath = path.join(targetDir, "package.json"); + const packageJsonContent = fs.readFileSync(packageJsonPath, "utf8"); + const packageJson = JSON.parse(packageJsonContent); + packageJson.name = packageName; // Set custom package name + packageJson.type = "module"; // Define type as module + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); + + addNameAndTypeSpinner.success({ + text: "Added name and type declaration successfully.", + }); + } catch (err) { + addNameAndTypeSpinner.error({ + text: "Error adding type declaration.\n", + }); + console.error(err.message); + } + + if (!removeDependencies) { + const installDependencies = createSpinner( + "Installing dependency packages...", + ).start(); + try { + execSync("npm i", { stdio: "ignore", cwd: targetDir }); + + installDependencies.success({ + text: "Installed dependencies successfully.", + }); + } catch (err) { + installDependencies.error({ + text: "Error installing dependencies.\n", + }); + console.error(err); + } + } + + console.log(chalk.green.bold("\nSetup complete! To run your server:")); + if (removeDependencies) { + console.log( + chalk.yellow("Install dependencies: "), + chalk.white.bold("npm i"), + ); + } + console.log(chalk.yellow("Run:"), chalk.white.bold("npm start")); + if (!removeNodemon) { + console.log( + chalk.yellow("Run with hot reloading:"), + chalk.white.bold("npm run dev"), + ); + } + if (dockerCompose) { + console.log( + chalk.yellow("To start your services with Docker Compose:"), + chalk.white.bold("docker compose up -d"), + ); + } + + if (dockerCompose && isUrl === true && runtimeNeedDB === true) { + console.log( + chalk.yellow("Important Note:"), + chalk.white("Use"), + chalk.blueBright.bold("host.docker.internal"), + chalk.white("instead of"), + chalk.blueBright.bold("localhost/127.0.0.1"), + chalk.white("in your Database Connection URL in the"), + chalk.blueBright.bold(".env"), + chalk.white("file for Docker to work correctly."), + ); + } +} \ No newline at end of file From e511ef03de793dea4a5d34580500860e714173f4 Mon Sep 17 00:00:00 2001 From: afmireski Date: Sun, 23 Feb 2025 19:38:37 -0300 Subject: [PATCH 05/10] feat: Implemented unit tests for docker-compose flag using cache and db --- test/init.test.js | 273 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 267 insertions(+), 6 deletions(-) diff --git a/test/init.test.js b/test/init.test.js index b4afc78..7400f80 100644 --- a/test/init.test.js +++ b/test/init.test.js @@ -12,7 +12,7 @@ import { fileURLToPath } from "url"; import { exec as execCallback } from "child_process"; import { promisify } from "util"; import stripAnsi from "strip-ansi"; -import { expect } from "@jest/globals"; +import { expect, jest } from "@jest/globals"; const exec = promisify(execCallback); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -92,12 +92,16 @@ function computeSHA256Hash(dirName) { const hash = createHash("sha256"); const files = readdirSync(dirName); + const ignoreFilesList = [ + "node_modules", + "package-lock.json", + "package.json", + "docker-compose.yml", + "Dockerfile", + ]; + for (const file of files) { - if ( - file === "node_modules" || - file === "package-lock.json" || - file === "package.json" - ) { + if (ignoreFilesList.includes(file)) { continue; } const filePath = path.join(dirName, file); @@ -113,6 +117,16 @@ function computeSHA256Hash(dirName) { return hash.digest("hex"); } +function verifyDockerFiles() { + const dockerComposePath = path.join(tempDir, "docker-compose.yml"); + const dockerfilePath = path.join(tempDir, "Dockerfile"); + + const existsBothDockerfiles = + existsSync(dockerComposePath) && existsSync(dockerfilePath); + + expect(existsBothDockerfiles).toBe(true); +} + // Verify if installing dependencies is happening by default // along with nodemon in package.json by default. describe("normal init with default settings", () => { @@ -643,3 +657,250 @@ describe("init without nodemon option without installing deps.", () => { }); // TODO: Add tests for docker-compose. + +const mockAskSelection = jest.fn(); +const mockAskConfirmation = jest.fn(); + +jest.unstable_mockModule("../bin/util/question/inquirer.js", () => ({ + askConfirmation: mockAskConfirmation, + askSelection: mockAskSelection, +})); + +// Import the module after mocking +const { initCommand } = await import("../bin/init.js"); +const { askConfirmation, askSelection } = await import( + "../bin/util/question/inquirer.js" +); + +describe("init with docker-compose with cache service and db", () => { + beforeEach(() => { + jest.resetModules(); + initTempDirectory(); + }); + + afterAll(() => { + clearTempDirectory(); + jest.clearAllMocks(); + }); + + test("basic with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "basic"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "basic", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("express_pg with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "express_pg"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "express_pg", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("express_pg_sequelize with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "express_pg_sequelize"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "express_pg_sequelize", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("express_mysql with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "express_mysql"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "express_mysql", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("express_oauth_microsoft with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "express_oauth_microsoft"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "express_oauth_microsoft", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("express_pg_prisma with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "express_pg_prisma"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "express_pg_prisma", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("express_mongo with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "express_mongo"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "express_mongo", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("express_oauth_google with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "express_oauth_google"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "express_oauth_google", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); + + test("basic_ts with docker configuration, cache and DB", async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); + + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", "basic_ts"), + ); + + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); + + await initCommand({ + template: "basic_ts", + dockerCompose: true, + removeDeps: true, + }); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 20000); +}); From b154771e346410f6f07b3b5b115ef87b16bac1f0 Mon Sep 17 00:00:00 2001 From: afmireski Date: Sun, 23 Feb 2025 19:53:20 -0300 Subject: [PATCH 06/10] docs: Updated README.md adding afmireski as a contributor --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index de5b637..7e7cca7 100644 --- a/README.md +++ b/README.md @@ -251,3 +251,4 @@ npm test - [Guilherme Almeida Lopes](https://github.com/alguiguilo098) - [Nitansh Shankar](https://github.com/BIJJUDAMA) - [Priyansh Narang](https://github.com/priyansh-narang2308) +- [afmireski](https://github.com/afmireski) From 5b6e15d7aeabd93c2ac7d38acd6c14ad7d68784d Mon Sep 17 00:00:00 2001 From: afmireski Date: Mon, 24 Feb 2025 22:20:33 -0300 Subject: [PATCH 07/10] refacto: Fixed merge changes --- bin/init.js | 2 +- bin/util/docker.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bin/init.js b/bin/init.js index ccdbc85..950f3f7 100644 --- a/bin/init.js +++ b/bin/init.js @@ -17,7 +17,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const parentDir = path.dirname(__dirname); -async function initCommand(options) { +export async function initCommand(options) { const selectedTemplate = options.template || "basic"; // Default to 'basic' if no template is specified const packageName = options.name || "qse-server"; // Default to 'qse-server' if no name is specified const removeNodemon = options.removeNodemon; diff --git a/bin/util/docker.js b/bin/util/docker.js index 45e0a30..ce17de9 100644 --- a/bin/util/docker.js +++ b/bin/util/docker.js @@ -1,6 +1,5 @@ import { supportedDockerComposeCacheImages, templates } from "../configs.js"; import chalk from "chalk"; -import { confirmationQuestion, selectionQuestion } from "./question/index.js"; import { askConfirmation, askSelection } from "./question/inquirer.js"; export async function userPrompts(needDB, cacheService) { From 35004aebc6b71f20a0994f2e553a6b567fbd28aa Mon Sep 17 00:00:00 2001 From: afmireski Date: Mon, 24 Feb 2025 22:21:25 -0300 Subject: [PATCH 08/10] refactor: Runned lint --- bin/util/docker.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/bin/util/docker.js b/bin/util/docker.js index ce17de9..4652431 100644 --- a/bin/util/docker.js +++ b/bin/util/docker.js @@ -9,17 +9,17 @@ export async function userPrompts(needDB, cacheService) { const runtimeNeedDB = needDB ? await askConfirmation( - "Do you wish to containerize DB service? (Default: Yes)", - true, - ) + "Do you wish to containerize DB service? (Default: Yes)", + true, + ) : false; const addCacheService = cacheService === undefined ? await askConfirmation( - "Do you want to add a cache service? (Default: No)", - false, - ) + "Do you want to add a cache service? (Default: No)", + false, + ) : cacheService !== "skip"; return { runtimeNeedDB, addCacheService }; @@ -29,9 +29,9 @@ async function promptCacheService(packageName, cacheService) { const image = cacheService === undefined ? await askSelection( - "Select the Docker image for the cache service:", - supportedDockerComposeCacheImages, - ) + "Select the Docker image for the cache service:", + supportedDockerComposeCacheImages, + ) : cacheService; let ports; From f58cb4de887102f0a575d70ccdadcc4f6cc5a843 Mon Sep 17 00:00:00 2001 From: Ashrockzzz2003 Date: Tue, 25 Feb 2025 11:39:42 +0530 Subject: [PATCH 09/10] Reduce number of lines in init test code by using template names from config. --- .vscode/settings.json | 5 - test/init.test.js | 883 +++++------------------------------------- 2 files changed, 94 insertions(+), 794 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index fa74c7a..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "jest.runMode": { - "type": "on-demand" - } -} diff --git a/test/init.test.js b/test/init.test.js index 31ce746..4303f54 100644 --- a/test/init.test.js +++ b/test/init.test.js @@ -13,6 +13,7 @@ import { exec as execCallback } from "child_process"; import { promisify } from "util"; import stripAnsi from "strip-ansi"; import { expect, jest } from "@jest/globals"; +import { templates } from "../bin/configs.js"; const exec = promisify(execCallback); const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -140,129 +141,21 @@ describe("normal init with default settings", () => { // TODO: Add test for cases where `inquirer` prompts are used for this. - test("basic with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic"), - ); - await exec(`node ../../bin/index.js init -t basic`, { cwd: tempDir }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); - - test("express_pg with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg"), - ); - await exec(`node ../../bin/index.js init -t express_pg`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); - - test("express_pg_sequelize with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_sequelize"), - ); - await exec(`node ../../bin/index.js init -t express_pg_sequelize`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); - - test("express_mysql with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mysql"), - ); - await exec(`node ../../bin/index.js init -t express_mysql`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); - - test("express_oauth_microsoft with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_microsoft"), - ); - await exec(`node ../../bin/index.js init -t express_oauth_microsoft`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); - - test("express_pg_prisma with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_prisma"), - ); - await exec(`node ../../bin/index.js init -t express_pg_prisma`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); - - test("express_mongo with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mongo"), - ); - await exec(`node ../../bin/index.js init -t express_mongo`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); - - test("express_oauth_google with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_google"), - ); - await exec(`node ../../bin/index.js init -t express_oauth_google`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); + Object.entries(templates).forEach(([templateName, _]) => { + test(`${templateName} with nodemon and deps installed`, async () => { + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", templateName), + ); + await exec(`node ../../bin/index.js init -t ${templateName}`, { + cwd: tempDir, + }); + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); - test("basic_ts with nodemon", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic_ts"), - ); - await exec(`node ../../bin/index.js init -t basic_ts`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(true); - }, 25000); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(true); + }, 25000); + }); }); describe("init --remove-deps", () => { @@ -276,149 +169,24 @@ describe("init --remove-deps", () => { // TODO: Add test for cases where `inquirer` prompts are used for this. - test("basic with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic"), - ); - await exec(`node ../../bin/index.js init -t basic --remove-deps`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("express_pg with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg"), - ); - await exec(`node ../../bin/index.js init -t express_pg --remove-deps`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("express_pg_sequelize with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_sequelize"), - ); - await exec( - `node ../../bin/index.js init -t express_pg_sequelize --remove-deps`, - { - cwd: tempDir, - }, - ); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("express_mysql with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mysql"), - ); - await exec( - `node ../../bin/index.js init -t express_mysql --remove-deps`, - { - cwd: tempDir, - }, - ); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("express_oauth_microsoft with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_microsoft"), - ); - await exec( - `node ../../bin/index.js init -t express_oauth_microsoft --remove-deps`, - { - cwd: tempDir, - }, - ); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("express_pg_prisma with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_prisma"), - ); - await exec( - `node ../../bin/index.js init -t express_pg_prisma --remove-deps`, - { - cwd: tempDir, - }, - ); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("express_mongo with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mongo"), - ); - await exec( - `node ../../bin/index.js init -t express_mongo --remove-deps`, - { - cwd: tempDir, - }, - ); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("express_oauth_google with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_google"), - ); - await exec( - `node ../../bin/index.js init -t express_oauth_google --remove-deps`, - { - cwd: tempDir, - }, - ); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); - - test("basic_ts with nodemon without deps installed", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic_ts"), - ); - await exec(`node ../../bin/index.js init -t basic_ts --remove-deps`, { - cwd: tempDir, - }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - }, 25000); + Object.entries(templates).forEach(([templateName, _]) => { + test(`${templateName} with nodemon without deps installed`, async () => { + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", templateName), + ); + await exec( + `node ../../bin/index.js init -t ${templateName} --remove-deps`, + { + cwd: tempDir, + }, + ); + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + }, 25000); + }); }); // Not installing dependencies as it takes time and is already tested above. @@ -520,140 +288,24 @@ describe("init without nodemon option without installing deps.", () => { // TODO: Add test for cases where `inquirer` prompts are used for this. - test("basic without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t basic --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("express_pg without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t express_pg --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("express_pg_sequelize without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t express_pg_sequelize --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("express_mysql without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t express_mysql --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("express_oauth_microsoft without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t express_oauth_microsoft --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("express_pg_prisma without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t express_pg_prisma --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("express_mongo without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t express_mongo --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("express_oauth_google without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t express_oauth_google --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); - - test("basic_ts without nodemon", async () => { - await exec( - "node ../../bin/index.js init -t basic_ts --remove-nodemon --remove-deps", - { cwd: tempDir }, - ); - const packageJson = readPackageJson(); - - expect(packageJson.scripts.start).not.toContain("nodemon"); - expect(packageJson.scripts.dev).toBeUndefined(); - - if (packageJson.devDependencies) { - expect(packageJson.devDependencies).not.toHaveProperty("nodemon"); - } - }, 25000); + Object.entries(templates).forEach(([templateName, _]) => { + test(`${templateName} without nodemon`, async () => { + await exec( + `node ../../bin/index.js init -t ${templateName} --remove-nodemon --remove-deps`, + { cwd: tempDir }, + ); + const packageJson = readPackageJson(); + + expect(packageJson.scripts.start).not.toContain("nodemon"); + expect(packageJson.scripts.dev).toBeUndefined(); + + if (packageJson.devDependencies) { + expect(packageJson.devDependencies).not.toHaveProperty( + "nodemon", + ); + } + }, 25000); + }); }); describe("init with docker-compose without cache service and db", () => { @@ -665,180 +317,30 @@ describe("init with docker-compose without cache service and db", () => { clearTempDirectory(); }); - test("basic with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic"), - ); - await exec( - "node ../../bin/index.js init -t basic --remove-deps --docker-compose --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("express_pg with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg"), - ); - await exec( - "node ../../bin/index.js init -t express_pg --remove-deps --docker-compose --skip-db --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("express_pg_sequelize with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_sequelize"), - ); - await exec( - "node ../../bin/index.js init -t express_pg_sequelize --remove-deps --docker-compose --skip-db --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("express_mysql with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mysql"), - ); - await exec( - "node ../../bin/index.js init -t express_mysql --remove-deps --docker-compose --skip-db --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("express_oauth_microsoft with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_microsoft"), - ); - await exec( - "node ../../bin/index.js init -t express_oauth_microsoft --remove-deps --docker-compose --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("express_pg_prisma with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_prisma"), - ); - await exec( - "node ../../bin/index.js init -t express_pg_prisma --remove-deps --docker-compose --skip-db --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("express_mongo with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mongo"), - ); - await exec( - "node ../../bin/index.js init -t express_mongo --remove-deps --docker-compose --skip-db --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("express_oauth_google with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_google"), - ); - await exec( - "node ../../bin/index.js init -t express_oauth_google --remove-deps --docker-compose --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); - - test("basic_ts with docker configuration", async () => { - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic_ts"), - ); - await exec( - "node ../../bin/index.js init -t basic_ts --remove-deps --docker-compose --cache-service skip", - { - cwd: tempDir, - }, - ); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 25000); + Object.entries(templates).forEach(([templateName, _]) => { + test(`${templateName} with docker configuration`, async () => { + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", templateName), + ); + await exec( + `node ../../bin/index.js init -t ${templateName} --remove-deps --docker-compose --skip-db --cache-service skip`, + { + cwd: tempDir, + }, + ); + + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); + + verifyDockerFiles(); + }, 25000); + }); }); -// TODO: Add tests for init with docker-compose with cache service specified. -// TODO: Add tests for init with docker-compose with db enabled. +// TODO: Add tests for init with docker-compose with only cache service specified. +// TODO: Add tests for init with docker-compose with only db enabled. const mockAskSelection = jest.fn(); const mockAskConfirmation = jest.fn(); @@ -850,9 +352,6 @@ jest.unstable_mockModule("../bin/util/question/inquirer.js", () => ({ // Import the module after mocking const { initCommand } = await import("../bin/init.js"); -const { askConfirmation, askSelection } = await import( - "../bin/util/question/inquirer.js" -); describe("init with docker-compose with cache service and db", () => { beforeEach(() => { @@ -865,224 +364,30 @@ describe("init with docker-compose with cache service and db", () => { jest.clearAllMocks(); }); - test("basic with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); + Object.entries(templates).forEach(([templateName, _]) => { + test(`${templateName} with docker configuration, cache and DB`, async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic"), - ); + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", templateName), + ); - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); - await initCommand({ - template: "basic", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("express_pg with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg"), - ); - - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); - - await initCommand({ - template: "express_pg", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("express_pg_sequelize with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_sequelize"), - ); - - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); - - await initCommand({ - template: "express_pg_sequelize", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("express_mysql with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mysql"), - ); + await initCommand({ + template: templateName, + dockerCompose: true, + removeDeps: true, + }); - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); - await initCommand({ - template: "express_mysql", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("express_oauth_microsoft with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_microsoft"), - ); - - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); - - await initCommand({ - template: "express_oauth_microsoft", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("express_pg_prisma with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_pg_prisma"), - ); - - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); - - await initCommand({ - template: "express_pg_prisma", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("express_mongo with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_mongo"), - ); - - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); - - await initCommand({ - template: "express_mongo", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("express_oauth_google with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "express_oauth_google"), - ); - - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); - - await initCommand({ - template: "express_oauth_google", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); - - test("basic_ts with docker configuration, cache and DB", async () => { - jest.spyOn(process, "cwd").mockReturnValue(tempDir); - - const originalHash = computeSHA256Hash( - path.join(__dirname, "..", "templates", "basic_ts"), - ); - - mockAskConfirmation.mockResolvedValueOnce(true); - mockAskSelection.mockResolvedValueOnce("redis:latest"); - - await initCommand({ - template: "basic_ts", - dockerCompose: true, - removeDeps: true, - }); - - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); - - verifyDockerFiles(); - }, 20000); + verifyDockerFiles(); + }, 25000); + }); }); From f88eee8ab56e31114b7dd885f60b3a515da79e65 Mon Sep 17 00:00:00 2001 From: Ashrockzzz2003 Date: Tue, 25 Feb 2025 11:47:42 +0530 Subject: [PATCH 10/10] Bump QSE version to 2.0.1. --- bin/configs.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/configs.js b/bin/configs.js index db14ab1..1947a84 100644 --- a/bin/configs.js +++ b/bin/configs.js @@ -1,7 +1,7 @@ export const metadata = { command: "qse", name: "Quick Start Express", - version: "v2.0.0", + version: "v2.0.1", description: "A simple CLI tool to generate Express servers from multiple available templates.", oneLineDescription: diff --git a/package.json b/package.json index 5a306af..28f0922 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "qse", - "version": "2.0.0", + "version": "2.0.1", "description": "A simple CLI tool to generate Express servers from multiple available templates.", "type": "module", "main": "index.js",