@faisalrmdhn08/allin-cli
Version:
A modern full-stack CLI tool based on Typescript designed to accelerate your app development process โ setup your entire stack in one seamless command.
419 lines โข 20.7 kB
JavaScript
import { __basePath, __userRealName } from '../../config.js';
import { __pathNotExist } from '../../exceptions/trigger.js';
import chalk from 'chalk';
import fse from 'fs-extra';
import fs from 'fs';
import path from 'path';
import { UnidentifiedTemplateError } from '../../exceptions/error.js';
import { execa } from 'execa';
import boxen from 'boxen';
import inquirer from 'inquirer';
import { BACKEND_FRAMEWORKS, FRONTEND_FRAMEWORKS, LICENSES, } from '../../constants/default.js';
import { TYPESCRIPT_DEPENDENCIES } from '../../constants/packages/general.js';
export class MicroGenerator {
static #instance;
constructor() { }
static get instance() {
if (!MicroGenerator.#instance) {
MicroGenerator.#instance = new MicroGenerator();
}
return MicroGenerator.#instance;
}
async setupProject(params) {
const isPathExist = fs.existsSync(params.desPath);
if (!isPathExist) {
await fse.copy(params.sourcePath, params.desPath);
return;
}
if (params.optionValues.force) {
await fse.remove(params.desPath);
await fse.copy(params.sourcePath, params.desPath);
return;
}
const __forceOverwriteProject = await inquirer.prompt({
name: 'forceOverwrite',
type: 'confirm',
message: `Are you sure to overwrite ${params.projectName} that exist at ${params.desPath} path? (optional)`,
default: false,
});
if (__forceOverwriteProject.forceOverwrite) {
await fse.remove(params.desPath);
await fse.copy(params.sourcePath, params.desPath);
}
}
async setupDocker(params) {
if (!params.isAddingDocker)
return;
if (!params.isAddingBake)
await this.__addDocker({
spinner: params.spinner,
desPath: params.desPath,
selectedPackageManager: params.selectedPackageManager,
});
else
await this.__addDockerBake({
spinner: params.spinner,
desPath: params.desPath,
selectedPackageManager: params.selectedPackageManager,
});
}
async setupOthers(params) {
if (params.optionValues.git) {
await this.__addGit(params.spinner, params.desPath);
}
if (params.optionValues.pm && params.optionValues.pm !== '') {
await this.__switchPackageManager({
spinner: params.spinner,
selectedPackageManager: params.optionValues.pm,
projectName: params.projectName,
desPath: params.desPath,
});
}
if (params.optionValues.typescript) {
await this.__useTypescript({
spinner: params.spinner,
projectType: params.projectType.toLowerCase(),
projectName: params.projectName,
selectedframework: params.selectedFramework,
selectedPackageManager: params.optionValues.pm,
desPath: params.desPath,
});
}
if (params.optionValues.author !== '' ||
params.optionValues.description !== '') {
await this.__updatePackageMetadata({
spinner: params.spinner,
optionValues: params.optionValues,
projectName: params.projectName,
desPath: params.desPath,
});
}
await this.__addLicense({
spinner: params.spinner,
optionValues: params.optionValues,
projectName: params.projectName,
desPath: params.desPath,
});
}
async setupInstallation(params) {
if (params.selectedDependencies.length < 1) {
return;
}
await this.__installDependencies({
spinner: params.spinner,
selectedDependencies: params.selectedDependencies,
selectedPackageManager: params.selectedPackageManager,
desPath: params.desPath,
});
await this.__updateDependencies({
spinner: params.spinner,
selectedPackageManager: params.selectedPackageManager,
projectName: params.projectName,
desPath: params.desPath,
});
}
async __addDocker(params) {
const __dockerComposeSources = this.__getDockerPaths('compose.yml', params.desPath);
params.spinner.start(`Copying ${chalk.bold('docker compose file')} ๐ณ into ${chalk.bold(params.desPath)}, please wait for a moment...`);
await fse.copy(__dockerComposeSources.sourcePath, __dockerComposeSources.desPath);
params.spinner.succeed(`Copying ${chalk.bold('docker compose file')} succeed โ
`);
const __dockerfileBaseOnePackageManager = params.selectedPackageManager === 'npm'
? 'npm.Dockerfile'
: 'pnpm.Dockerfile';
const __dockerfileSources = this.__getDockerPaths(__dockerfileBaseOnePackageManager, params.desPath);
params.spinner.start(`Copying ${chalk.bold('dockerfile')} ๐ณ into ${chalk.bold(params.desPath)}, please wait for a moment...`);
await fse.copy(__dockerfileSources.sourcePath, __dockerfileSources.desPath);
params.spinner.succeed(`Copying ${chalk.bold('dockerfile')} succeed โ
`);
}
async __addDockerBake(params) {
const __dockerComposePaths = this.__getDockerPaths('compose.yml', params.desPath);
params.spinner.start(`Copying ${chalk.bold('docker compose file')} ๐ณ into ${chalk.bold(params.desPath)}, please wait for a moment...`);
await fse.copy(__dockerComposePaths.sourcePath, __dockerComposePaths.desPath);
params.spinner.succeed(`Copying ${chalk.bold('docker compose file')} succeed โ
`);
const __dockerfileBaseOnePackageManager = params.selectedPackageManager === 'npm'
? 'npm.Dockerfile'
: 'pnpm.Dockerfile';
const __dockerfilePaths = this.__getDockerPaths(__dockerfileBaseOnePackageManager, params.desPath);
params.spinner.start(`Copying ${chalk.bold('docker compose file')} ๐ณ into ${chalk.bold(params.desPath)}, please wait for a moment...`);
await fse.copy(__dockerfilePaths.sourcePath, __dockerfilePaths.desPath);
params.spinner.succeed(`Copying ${chalk.bold('dockerfile')} succeed โ
`);
const __dockerBakePaths = this.__getDockerPaths('docker-bake.hcl', params.desPath);
params.spinner.start(`Copying ${chalk.bold('docker bake file')} ๐ into ${chalk.bold(params.desPath)}, please wait for a moment...`);
await fse.copy(__dockerBakePaths.sourcePath, __dockerBakePaths.desPath);
params.spinner.succeed(`Copying ${chalk.bold('docker bake file')} succeed โ
`);
}
async __addGit(spinner, desPath) {
const __initializeGitQuestion = await inquirer.prompt({
name: 'addGit',
type: 'confirm',
message: `Do you want us to run ${chalk.bold('git init')}? (optional)`,
default: false,
});
if (!__initializeGitQuestion.addGit) {
console.warn(boxen(chalk.white(`โ ๏ธ ${chalk.bold((await __userRealName()).split(' ')[0])}, you can run ${chalk.bold('git init')} later.`), {
title: 'โ Warning Information โ',
titleAlignment: 'center',
padding: 1,
margin: 1,
borderColor: 'yellow',
}));
return;
}
spinner.start(`Initializing Git repository ๐, please wait for a moment...`);
await execa('git', ['init'], {
cwd: desPath,
});
spinner.succeed(`Git repository successfully initialized โ
`);
}
async __addLicense(params) {
const __licenseSelection = await inquirer.prompt({
name: 'license',
type: 'list',
message: 'Which license do you want to use:',
choices: LICENSES.licenses
.sort((i, e) => i.name.toLowerCase().localeCompare(e.name.toLowerCase(), 'en-US'))
.map((l) => l.actualName),
default: 'MIT License',
loop: false,
when: () => typeof params.optionValues.license === 'undefined',
});
const __licenseFile = typeof params.optionValues.license !== 'undefined'
? LICENSES.licenses.find((l) => l.name === params.optionValues.license)
: LICENSES.licenses.find((l) => l.actualName === __licenseSelection.license);
if (!__licenseFile) {
throw new UnidentifiedTemplateError(`${chalk.bold('Unidentified template')}: ${chalk.bold(__licenseSelection.license)} file template is not defined.`);
}
params.spinner.start(`Start adding ${chalk.bold(__licenseFile.actualName)} file into ${chalk.bold(params.projectName)} ๐งพ...`);
const __licenseSourcePath = path.join(__basePath, __licenseFile.path);
await fse.copy(__licenseSourcePath, params.desPath);
params.spinner.succeed(`Adding ${chalk.bold(__licenseFile.actualName)} file on ${chalk.bold(params.projectName)} succeed โ
`);
}
async __switchPackageManager(params) {
const __lockFiles = [
'package-lock.json',
'yarn.lock',
'pnpm-lock.yaml',
'bun.lock',
];
for (const file of __lockFiles) {
const __fullPath = path.join(params.desPath, file);
if (await fse.exists(__fullPath)) {
await fse.remove(__fullPath);
}
}
const __nodeModulesPath = path.join(params.desPath, 'node_modules');
if (await fse.exists(__nodeModulesPath)) {
await fse.remove(__nodeModulesPath);
}
const __executeCommand = params.selectedPackageManager === 'npm' ? 'npm' : 'pnpm';
await execa(__executeCommand, ['install'], {
cwd: params.desPath,
stdio: 'inherit',
});
}
async __useTypescript(params) {
const __frameworkList = params.projectType !== 'backend'
? FRONTEND_FRAMEWORKS.frameworks
: BACKEND_FRAMEWORKS.frameworks;
const __frameworkFile = __frameworkList.find((f) => f.name === params.selectedframework);
if (!__frameworkFile) {
throw new UnidentifiedTemplateError(`${chalk.bold('Unidentified template')}: ${chalk.bold(params.selectedframework)} file template is not defined.`);
}
const _frameworkPath = path.join(__basePath, __frameworkFile.path);
const _frameworkFiles = fs.readdirSync(_frameworkPath, {
withFileTypes: true,
});
const __tsConfigFile = _frameworkFiles.find((f) => f.name === 'tsconfig.json');
if (__tsConfigFile !== undefined) {
console.warn(boxen(chalk.white(`โ ๏ธ ${chalk.bold('tsconfig.json')} is exist on ${chalk.bold(params.projectName)}, means that ${chalk.bold('Typescript')} already installed.`), {
title: 'โ Warning Information โ',
titleAlignment: 'center',
padding: 1,
margin: 1,
borderColor: 'yellow',
}));
return;
}
await this.__installTypescript({
spinner: params.spinner,
projectType: params.projectType,
selectedFramework: params.selectedframework,
selectedPackageManager: params.selectedPackageManager,
desPath: params.desPath,
});
const __executeCommand = params.selectedPackageManager === 'npm' ? 'npx' : 'pnpm';
const __initializeTypescriptQuestion = await inquirer.prompt({
name: 'addTsConfig',
type: 'confirm',
message: `Do you want us to execute ${chalk.bold(`${__executeCommand} tsc --init`)} in your project? (optional)`,
default: false,
});
if (!__initializeTypescriptQuestion.addTsConfig) {
console.warn(boxen(chalk.white(`โ ๏ธ ${chalk.bold((await __userRealName()).split(' ')[0])}, you can initialize ${chalk.bold('Typescript')} later.`), {
title: 'โ Warning Information โ',
titleAlignment: 'center',
padding: 1,
margin: 1,
borderColor: 'yellow',
}));
return;
}
params.spinner.start(`Initializing ${chalk.bold('Typescript')} into ${chalk.bold(params.projectName)}, please wait for a moment...`);
await execa(__executeCommand, ['tsc', '--init'], {
cwd: params.desPath,
});
params.spinner.succeed(`Initializing ${chalk.bold('Typescript')} succeed โ
`);
params.spinner.start(`Start renaming .js files to .ts`);
const renamePairs = params.projectType === 'backend'
? [
[
path.join(params.desPath, 'index.js'),
path.join(params.desPath, 'index.ts'),
],
]
: [
[
path.join(params.desPath, 'src', 'main.js'),
path.join(params.desPath, 'src', 'main.ts'),
],
[
path.join(params.desPath, 'src', 'counter.js'),
path.join(params.desPath, 'src', 'counter.ts'),
],
];
for (const [__sourcePath, __desPath] of renamePairs) {
if (fs.existsSync(__sourcePath)) {
params.spinner.start(`Renaming ${chalk.bold(__sourcePath)} to ${chalk.bold(__desPath)}...`);
fs.renameSync(__sourcePath, __desPath);
params.spinner.succeed(`Renamed ${chalk.bold(__sourcePath)} โ ${chalk.bold(__desPath)} โ
`);
}
}
params.spinner.succeed(`All file renames complete for ${chalk.bold(params.projectName)} โ
`);
}
__getDockerPaths(filename, desPath) {
const __dockerTemplatesPath = path.join(__basePath, 'templates/docker');
__pathNotExist(__dockerTemplatesPath);
const __templates = fs.readdirSync(__dockerTemplatesPath, {
withFileTypes: true,
});
const __dockerFile = __templates.find((f) => f.name === filename);
if (!__dockerFile) {
throw new UnidentifiedTemplateError(`${chalk.bold('Unidentified template')}: ${chalk.bold(filename)} file template is not defined.`);
}
const __dockerFileSourcePath = path.join(__dockerFile.parentPath, __dockerFile.name);
const __dockerFileDesPath = path.join(desPath, __dockerFile.name);
return {
sourcePath: __dockerFileSourcePath,
desPath: __dockerFileDesPath,
};
}
async __installTypescript(params) {
const __executeCommand = params.selectedPackageManager === 'npm' ? 'npm' : 'pnpm';
for (const p of TYPESCRIPT_DEPENDENCIES[params.projectType][params.selectedFramework]) {
params.spinner.start(`Start installing ${chalk.bold(p)} package...`);
if (params.selectedPackageManager === 'npm') {
await execa(__executeCommand, ['install', '-D', p], {
cwd: params.desPath,
});
}
else {
await execa(__executeCommand, ['add', '-D', p], {
cwd: params.desPath,
});
}
params.spinner.succeed(`Installing ${chalk.bold(p)} package succeed โ
`);
}
}
async __installDependencies(params) {
const __executeCommand = params.selectedPackageManager === 'npm' ? 'npm' : 'pnpm';
params.spinner.start(`Installing ${chalk.bold(params.selectedDependencies.join(', '))}, please wait for a moment...`);
for (const p of params.selectedDependencies) {
params.spinner.start(`Start installing ${chalk.bold(p)} dependency...`);
await execa(__executeCommand, ['install', '--save', p], {
cwd: params.desPath,
});
params.spinner.succeed(`Installing ${chalk.bold(p)} dependency succeed โ
`);
}
const __isPrettierSelected = params.selectedDependencies.includes('prettier');
const __isEsLintSelected = params.selectedDependencies.includes('eslint');
if (__isPrettierSelected) {
const __prettierrcTemplatesPath = path.join(__basePath, 'templates/configs');
__pathNotExist(__prettierrcTemplatesPath);
const __templates = fs.readdirSync(__prettierrcTemplatesPath, {
withFileTypes: true,
});
const __prettierrcFile = __templates.find((f) => f.name === '.prettierrc');
if (!__prettierrcFile) {
throw new UnidentifiedTemplateError(`${chalk.bold('Unidentified template')}: ${chalk.bold('.prettierrc')} file template is not defined.`);
}
const __prettierrcFileSourcePath = path.join(__prettierrcFile.parentPath, __prettierrcFile.name);
const __prettierrcFileDesPath = path.join(params.desPath, __prettierrcFile.name);
params.spinner.start(`Initializing ${chalk.bold('.prettierrc')} file...`);
await fse.copy(__prettierrcFileSourcePath, __prettierrcFileDesPath);
params.spinner.succeed(`Adding ${chalk.bold('.prettierrc')} configuration completed โ
`);
}
if (__isEsLintSelected) {
const __executeCommand = params.selectedPackageManager === 'npm' ? 'npx' : 'pnpx';
const __initializeESLintQuestion = await inquirer.prompt({
name: 'addESLintConfig',
type: 'confirm',
message: `Do you want us to execute ${chalk.bold(`${__executeCommand} eslint --init`)} in your project? (optional)`,
default: false,
});
if (!__initializeESLintQuestion.addESLintConfig) {
console.warn(boxen(chalk.white(`โ ๏ธ ${chalk.bold((await __userRealName()).split(' ')[0])}, you can execute ${chalk.bold(`${__executeCommand} eslint --init`)} later.`), {
title: 'โ Warning Information โ',
titleAlignment: 'center',
padding: 1,
margin: 1,
borderColor: 'yellow',
}));
return;
}
await execa(`${__executeCommand}`, ['eslint', '--init'], {
cwd: params.desPath,
stdio: 'inherit',
});
}
params.spinner.succeed(`Installing all dependencies succeed โ
`);
}
async __updateDependencies(params) {
const __updateDependenciesQuestion = await inquirer.prompt({
name: 'updatePackages',
type: 'confirm',
message: `Do you want us to run ${chalk.bold(`${params.selectedPackageManager} update`)}? (optional)`,
default: false,
});
if (!__updateDependenciesQuestion.updatePackages) {
console.warn(boxen(chalk.white(`โ ๏ธ ${chalk.bold((await __userRealName()).split(' ')[0])}, you can update the dependencies later.`), {
title: 'โ Warning Information โ',
titleAlignment: 'center',
padding: 1,
margin: 1,
borderColor: 'yellow',
}));
return;
}
params.spinner.start(`Updating ${chalk.bold(params.projectName)} dependencies, please wait for a moment ๐...`);
await execa(`${params.selectedPackageManager}`, ['update'], {
cwd: params.desPath,
});
params.spinner.succeed(`Updating ${chalk.bold(params.projectName)} dependencies succeed โ
`);
}
async __updatePackageMetadata(params) {
params.spinner.start(`Updating ${chalk.bold(params.projectName)} package metadata, please wait for a moment ๐...`);
const jsonPackagePath = path.join(params.desPath, 'package.json');
const jsonPackage = await fse.readJSON(jsonPackagePath);
jsonPackage.author =
typeof params.optionValues.author !== 'undefined'
? params.optionValues.author
: '';
jsonPackage.description =
typeof params.optionValues.description !== 'undefined'
? params.optionValues.description
: '';
await fse.writeJSON(jsonPackagePath, jsonPackage, { spaces: 2 });
params.spinner.succeed(`Updating ${chalk.bold(params.projectName)} package metadata succeed โ
`);
}
}
//# sourceMappingURL=micro.js.map