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/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/bin/index.js b/bin/index.js index e299aa2..7fedd26 100755 --- a/bin/index.js +++ b/bin/index.js @@ -1,31 +1,16 @@ #!/usr/bin/env node import { Option, 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, supportedDockerComposeCacheImages, } 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) @@ -113,252 +98,6 @@ 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; - - if (!options.template) { - initMenu(initCommand, options); - return; - } - - // Docker Compose options. - const dockerCompose = options.dockerCompose; - const cacheService = options.cacheService; - const skipDb = options.skipDb || false; - - 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 && !skipDb; - - 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"); - - let runtimeNeedDB = false; - if (dockerCompose) { - try { - console.log(); - const userPrompt = await userPrompts(needDB, cacheService); - runtimeNeedDB = userPrompt.runtimeNeedDB; - - const serviceData = await getServicesData( - packageName, - selectedTemplate, - runtimeNeedDB, - userPrompt.addCacheService, - cacheService, - ); - - 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(); - 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( figlet.textSync(metadata.name, { diff --git a/bin/init.js b/bin/init.js new file mode 100644 index 0000000..950f3f7 --- /dev/null +++ b/bin/init.js @@ -0,0 +1,264 @@ +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; + + if (!options.template) { + initMenu(initCommand, options); + return; + } + + // Docker Compose options. + const dockerCompose = options.dockerCompose; + const cacheService = options.cacheService; + const skipDb = options.skipDb || false; + + 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 && !skipDb; + + 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"); + + let runtimeNeedDB = false; + if (dockerCompose) { + try { + console.log(); + const userPrompt = await userPrompts(needDB, cacheService); + runtimeNeedDB = userPrompt.runtimeNeedDB; + + const serviceData = await getServicesData( + packageName, + selectedTemplate, + runtimeNeedDB, + userPrompt.addCacheService, + cacheService, + ); + + 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(); + 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."), + ); + } +} diff --git a/bin/util/docker.js b/bin/util/docker.js index dec6bf3..4652431 100644 --- a/bin/util/docker.js +++ b/bin/util/docker.js @@ -1,6 +1,6 @@ -import { confirm, select } from "@inquirer/prompts"; import { supportedDockerComposeCacheImages, templates } from "../configs.js"; import chalk from "chalk"; +import { askConfirmation, askSelection } from "./question/inquirer.js"; export async function userPrompts(needDB, cacheService) { if (needDB || cacheService === undefined) { @@ -8,18 +8,18 @@ export async function userPrompts(needDB, cacheService) { } const runtimeNeedDB = needDB - ? await confirm({ - message: "Do you wish to containerize DB service? (Default: Yes)", - default: true, - }) + ? await askConfirmation( + "Do you wish to containerize DB service? (Default: Yes)", + true, + ) : false; const addCacheService = cacheService === undefined - ? await confirm({ - message: "Do you want to add a cache service? (Default: No)", - default: false, - }) + ? await askConfirmation( + "Do you want to add a cache service? (Default: No)", + false, + ) : cacheService !== "skip"; return { runtimeNeedDB, addCacheService }; @@ -28,13 +28,10 @@ export async function userPrompts(needDB, cacheService) { async function promptCacheService(packageName, cacheService) { const image = cacheService === undefined - ? await select({ - message: "Select the Docker image for the cache service:", - choices: supportedDockerComposeCacheImages.map((img) => ({ - name: img, - value: img, - })), - }) + ? await askSelection( + "Select the Docker image for the cache service:", + supportedDockerComposeCacheImages, + ) : cacheService; let ports; 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 })), + }); +} 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", diff --git a/test/init.test.js b/test/init.test.js index ebde57f..4303f54 100644 --- a/test/init.test.js +++ b/test/init.test.js @@ -12,7 +12,8 @@ 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"; +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,177 +317,77 @@ 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); + 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); + }); +}); - 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, - }, - ); +// 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 commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); +const mockAskSelection = jest.fn(); +const mockAskConfirmation = jest.fn(); - verifyDockerFiles(); - }, 25000); +jest.unstable_mockModule("../bin/util/question/inquirer.js", () => ({ + askConfirmation: mockAskConfirmation, + askSelection: mockAskSelection, +})); - 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, - }, - ); +// Import the module after mocking +const { initCommand } = await import("../bin/init.js"); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); +describe("init with docker-compose with cache service and db", () => { + beforeEach(() => { + jest.resetModules(); + initTempDirectory(); + }); - verifyDockerFiles(); - }, 25000); + afterAll(() => { + clearTempDirectory(); + jest.clearAllMocks(); + }); - 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, - }, - ); + Object.entries(templates).forEach(([templateName, _]) => { + test(`${templateName} with docker configuration, cache and DB`, async () => { + jest.spyOn(process, "cwd").mockReturnValue(tempDir); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); + const originalHash = computeSHA256Hash( + path.join(__dirname, "..", "templates", templateName), + ); - verifyDockerFiles(); - }, 25000); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskConfirmation.mockResolvedValueOnce(true); + mockAskSelection.mockResolvedValueOnce("redis:latest"); - 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, - }, - ); + await initCommand({ + template: templateName, + dockerCompose: true, + removeDeps: true, + }); - const commandHash = computeSHA256Hash(tempDir); - expect(commandHash).toEqual(originalHash); - expect(hasNodemon()).toBe(true); - expect(nodeModulesExist()).toBe(false); + const commandHash = computeSHA256Hash(tempDir); + expect(commandHash).toEqual(originalHash); + expect(hasNodemon()).toBe(true); + expect(nodeModulesExist()).toBe(false); - verifyDockerFiles(); - }, 25000); + 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.