spring-boot-cli
Version:
CLI for Spring Boot
205 lines (188 loc) • 10 kB
text/typescript
import {Command} from "commander";
import inquirer from "inquirer";
import {isValidMavenArtifactId, isValidMavenGroupId, isValidProjectName} from "../utils/validator.utils.mjs";
import chalk from "chalk";
import path from "node:path";
import crypto from "crypto";
import {
copyFiles,
createFolder,
deleteFolder,
getJavaFiles,
readFile,
renameFile,
writeFile
} from "../utils/file-manager.utils.mjs";
import {cloneStarterPack, commitInitialProject} from "../utils/git.utils.mjs";
import ora, {Ora} from "ora";
import {parseObjectToXmlString, parseStringToXml} from "../utils/xml.utils.mjs";
import {Pom} from "../models/pom.model.mjs";
import {parseObjectToYamlString, parseStringToYaml} from "../utils/yaml.utils.mjs";
const createCommand: Command = new Command("create")
.command("create")
.alias("c")
.description("Create a new spring boot project with starter pack")
.argument("<name>", "Name of the project")
.action(async (name: string): Promise<void> => {
// =========================================
// Validate parameters
// =========================================
if (!validateName(name)) {
console.error(chalk.red("Please enter a valid project name"));
process.exit(1);
}
const groupId: string = await getGroupId();
const artifactId: string = await getArtifactId();
// =========================================
// Loading spinner
// =========================================
const spinner: Ora = ora(`Creating project folder...\n`).start(); // Start the spinner
// =========================================
// Create project folder
// =========================================
spinner.start(`Creating project folder...`);
const projectPath: string = path.join(process.cwd(), name);
createFolder(projectPath);
spinner.info(`Project folder created at ${projectPath}`);
// =========================================
// Clone starter pack from git
// =========================================
spinner.start(`Cloning starter pack...`);
await cloneStarterPack(projectPath);
deleteFolder(path.join(projectPath, ".git"));
spinner.info(`Starter pack cloned at ${projectPath}`);
// =========================================
// Edit pom.xml
// =========================================
spinner.start(`Generating project...`);
const pomPath: string = path.join(projectPath, "pom.xml");
const pom: string = readFile(pomPath);
let pomXml: Pom = await parseStringToXml(pom);
pomXml = editPom(pomXml, groupId, artifactId, name);
const pomString: string = await parseObjectToXmlString(pomXml);
writeFile(pomPath, pomString);
// =========================================
// Replace imports in java files with groupId
// =========================================
const javaFilePattern: string = path.posix.join(projectPath, "src", "main", "java", "**", "*.java");
const javaTestFilePattern: string = path.posix.join(projectPath, "src", "test", "java", "**", "*.java");
let javaFiles: string[] = getJavaFiles(javaFilePattern);
let javaTestFiles: string[] = getJavaFiles(javaTestFilePattern);
javaFiles.push(...javaTestFiles);
javaFiles.forEach((file: string) => {
let fileContent: string = readFile(file);
fileContent = fileContent.replaceAll("it.theapplegeek.spring_starter_pack", `${groupId.replaceAll("-", "_")}.${artifactId.replaceAll("-", "_")}`);
writeFile(file, fileContent);
});
// =========================================
// Rename main application class
// =========================================
const mainApplicationPath: string = path.join(projectPath, "src", "main", "java", "it", "theapplegeek", "spring_starter_pack", "Application.java");
let mainApplicationContent: string = readFile(mainApplicationPath);
const artifactIdCamelCase: string = artifactId.split(/[.-]/).map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
mainApplicationContent = mainApplicationContent.replaceAll("class Application", `class ${artifactIdCamelCase}Application`);
mainApplicationContent = mainApplicationContent.replaceAll("Application.class", `${artifactIdCamelCase}Application.class`);
writeFile(mainApplicationPath, mainApplicationContent);
const mainApplicationNewPath: string = path.join(projectPath, "src", "main", "java", "it", "theapplegeek", "spring_starter_pack", `${artifactIdCamelCase}Application.java`);
renameFile(mainApplicationPath, mainApplicationNewPath);
// =========================================
// Edit application.yml
// =========================================
const applicationYmlPath: string = path.join(projectPath, "src", "main", "resources", "application.yml");
const applicationYmlFile: string = readFile(applicationYmlPath);
let applicationYmlContent: any = parseStringToYaml(applicationYmlFile);
applicationYmlContent.spring.application.name = artifactId.split(/[.-]/).map((word: string) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
const secretKeyHex: string = crypto.randomBytes(32).toString('hex');
applicationYmlContent.application.security.jwt['secret-key'] = Buffer.from(secretKeyHex).toString('base64');
applicationYmlContent = parseObjectToYamlString(applicationYmlContent);
writeFile(applicationYmlPath, applicationYmlContent);
// =========================================
// Replace package path with groupId and artifactId
// =========================================
const groupIdSplit: string[] = groupId.replaceAll("-", "_").split(".");
const artifactIdSplit: string[] = artifactId.replaceAll("-", "_").split(".");
const defaultFolderPath: string = path.join(projectPath, "src", "main", "java", "it", "theapplegeek", "spring_starter_pack");
const newFolderPath: string = path.join(projectPath, "src", "main", "java", ...groupIdSplit, ...artifactIdSplit);
createFolder(newFolderPath, false);
copyFiles(defaultFolderPath, newFolderPath);
const defaultTestFolderPath: string = path.join(projectPath, "src", "test", "java", "it", "theapplegeek", "spring_starter_pack");
const newTestFolderPath: string = path.join(projectPath, "src", "test", "java", ...groupIdSplit, ...artifactIdSplit);
createFolder(newTestFolderPath, false);
copyFiles(defaultTestFolderPath, newTestFolderPath);
deleteDefaultFolder(groupId, artifactId, projectPath);
spinner.info(`Project created at ${projectPath}`);
// =========================================
// Commit initial project
// =========================================
spinner.start(`Initializing git...`);
await commitInitialProject(projectPath);
spinner.info(`Git initialized`);
spinner.succeed(chalk.green(`Project created successfully!`));
console.log(`\nNext steps:`);
console.log(`1. cd ${name}`);
console.log(`2. open the project in your favorite IDE`);
});
const validateName = (input: string): boolean => {
if (input === "") return false;
else if (!isValidProjectName(input)) return false;
return true;
}
const getGroupId = async (): Promise<string> => {
return inquirer.prompt({
type: "input",
name: "groupId",
message: "Enter the group ID: ",
validate: (input: string): boolean | string => {
if (input === "") {
return "Please enter a group ID";
} else if (!isValidMavenGroupId(input)) {
return "Please enter a valid group ID";
}
return true;
}
}).then((answers: { groupId: string }) => {
return answers.groupId as string
});
};
const getArtifactId = async (): Promise<string> => {
return inquirer.prompt({
type: "input",
name: "artifactId",
message: "Enter the artifact ID: ",
validate: (input: string): boolean | string => {
if (input === "") {
return "Please enter an artifact ID";
} else if (!isValidMavenArtifactId(input)) {
return "Please enter a valid artifact ID";
}
return true;
}
}).then((answers: { artifactId: string }) => {
return answers.artifactId as string
});
}
const editPom = (pom: Pom, groupId: string, artifactId: string, name: string): Pom => {
pom.project.groupId[0] = groupId;
pom.project.artifactId[0] = artifactId;
pom.project.name[0] = name;
pom.project.description[0] = 'Insert project description here';
delete pom.project.licenses[0];
delete pom.project.developers[0];
return pom;
}
const deleteDefaultFolder = (groupId: string, artifactId: string, projectPath: string): void => {
if (groupId === "it.theapplegeek" && artifactId === "spring-starter-pack") return;
if (groupId === "it.theapplegeek") {
deleteFolder(path.join(projectPath, "src", "main", "java", "it", "theapplegeek", "spring_starter_pack"));
deleteFolder(path.join(projectPath, "src", "test", "java", "it", "theapplegeek", "spring_starter_pack"));
} else if (groupId.startsWith("it.")) {
deleteFolder(path.join(projectPath, "src", "main", "java", "it", "theapplegeek"));
deleteFolder(path.join(projectPath, "src", "test", "java", "it", "theapplegeek"));
} else {
deleteFolder(path.join(projectPath, "src", "main", "java", "it"));
deleteFolder(path.join(projectPath, "src", "test", "java", "it"));
}
}
export {
createCommand
}