create-mint-app
Version:
Mintkit is a web framework designed to deliver efficient, scalable, and maintainable web development experiences.
438 lines (381 loc) • 14.6 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
// ANSI Color
const colors = {
reset: '\x1b[0m',
white: '\x1b[37m',
lightGreen: '\x1b[92m',
lightBlue: '\x1b[94m'
};
const log = (message, color = 'reset') => {
console.log(`${colors[color]}${message}${colors.reset}`);
};
const logSuccess = message => log(`${message}`, 'lightGreen');
const logError = message => log(`${message}`, 'white');
const logInfo = message => log(`${message}`, 'white');
const askInput = (question) => {
return new Promise(resolve => {
const rl = require('readline').createInterface({
input: process.stdin,
output: process.stdout
});
rl.question(`${colors.lightBlue}${question}${colors.reset} `, (answer) => {
rl.close();
resolve(answer);
});
});
};
const showSpinner = message => {
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let i = 0;
return setInterval(() => {
process.stdout.write(`\r${colors.lightBlue}${frames[i]} ${message}${colors.reset}`);
i = (i + 1) % frames.length;
}, 80);
};
const stopSpinner = spinner => {
clearInterval(spinner);
process.stdout.write('\r' + ' '.repeat(50) + '\r');
};
const askList = (question, choices, defaultIndex = 0) => {
return new Promise(resolve => {
let selected = defaultIndex;
const render = () => {
process.stdout.write('\x1Bc');
console.log(`${colors.lightBlue}${question}${colors.reset}\n`);
choices.forEach((choice, i) => {
if (i === selected) {
process.stdout.write(`${colors.lightGreen}❯ ${choice}${colors.reset}\n`);
} else {
process.stdout.write(` ${choice}\n`);
}
});
process.stdout.write('\n(Use arrow keys and Enter)\n');
};
const onKey = key => {
if (key === '\u0003') process.exit();
if (key === '\u001B[A') {
selected = (selected - 1 + choices.length) % choices.length;
render();
}
if (key === '\u001B[B') {
selected = (selected + 1) % choices.length;
render();
}
if (key === '\r') {
process.stdin.setRawMode(false);
process.stdin.removeListener('data', onKey);
process.stdout.write('\n');
resolve(choices[selected]);
}
};
render();
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding('utf8');
process.stdin.on('data', onKey);
});
};
const runCommand = (command, args, options) => {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { stdio: 'inherit', shell: true, ...options });
child.on('close', code => {
if (code !== 0) {
reject({ command: `${command} ${args.join(' ')}` });
return;
}
resolve();
});
});
};
const copyDirectory = (src, dest, filterFn) => {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(src, { withFileTypes: true });
entries.forEach(entry => {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (filterFn && !filterFn(entry, srcPath)) return;
if (entry.isDirectory()) {
copyDirectory(srcPath, destPath, filterFn);
} else {
fs.copyFileSync(srcPath, destPath);
}
});
};
(async () => {
console.log("Let's build your Mintkit application\n");
const args = process.argv.slice(2);
let projectName = args.find(arg => !arg.startsWith('--'));
const flags = new Set(args.filter(arg => arg.startsWith('--')));
if (!projectName) {
projectName = await askInput('What is your project name?');
if (!projectName) {
logError('Project name cannot be empty.');
process.exit(1);
}
}
if (!/^[a-zA-Z0-9-_]+$/.test(projectName)) {
logError('Project name can only contain letters, numbers, hyphens, and underscores!');
process.exit(1);
}
let lang;
if (flags.has('--typescript')) {
lang = 'typescript';
} else if (flags.has('--javascript')) {
lang = 'javascript';
} else {
lang = (await askList('Which language would you like to use?', ['JavaScript', 'TypeScript'])).toLowerCase();
}
let useVite;
if (flags.has('--vite')) {
useVite = true;
} else if (flags.has('--no-vite')) {
useVite = false;
} else {
const useViteAnswer = await askList('Do you want to use Vite for development?', ['Yes', 'No']);
useVite = useViteAnswer === 'Yes';
}
let useLinter;
if (flags.has('--lint')) {
useLinter = true;
} else if (flags.has('--no-lint')) {
useLinter = false;
} else {
const useLinterAnswer = await askList('Do you want to add ESLint and Prettier for code quality?', ['Yes', 'No']);
useLinter = useLinterAnswer === 'Yes';
}
console.log('\nCreating your Mintkit application...');
const spinner = showSpinner('Setting up project files');
const currentDir = process.cwd();
const projectPath = path.join(currentDir, projectName);
if (fs.existsSync(projectPath)) {
stopSpinner(spinner);
logError(`Directory "${projectName}" already exists!`);
process.exit(1);
}
// Find template directory
let templateDir = path.join(__dirname, 'mintkit-app');
if (!fs.existsSync(templateDir)) {
templateDir = path.join(__dirname, 'template');
}
if (!fs.existsSync(templateDir)) {
stopSpinner(spinner);
logError('Template directory not found.');
logError(`Searched: ${path.join(__dirname, 'mintkit-app')}`);
logError(`Also tried: ${path.join(__dirname, 'template')}`);
process.exit(1);
}
// Create project directory
fs.mkdirSync(projectPath, { recursive: true });
// Copy common files (exclude language folders and unwanted files)
copyDirectory(templateDir, projectPath, (entry, srcPath) => {
const rel = path.relative(templateDir, srcPath).replace(/\\/g, '/');
const excludeDirs = [
'liveserver/',
'node_modules/',
'javascript/',
'typescript/',
'.git/',
'dist/',
'package-lock.json',
'package.json',
'tsconfig.json',
'vite.config.js'
];
return !excludeDirs.some(dir =>
rel.startsWith(dir) || rel === dir.replace('/', '')
);
});
// Copy language-specific src files
const langSourceDir = path.join(templateDir, lang, 'src');
const projectSrcDir = path.join(projectPath, 'src');
if (fs.existsSync(langSourceDir)) {
if (!fs.existsSync(projectSrcDir)) {
fs.mkdirSync(projectSrcDir, { recursive: true });
}
copyDirectory(langSourceDir, projectSrcDir);
} else {
stopSpinner(spinner);
logError(`${lang} source directory not found!`);
logError(`Expected: ${langSourceDir}`);
process.exit(1);
}
// Copy index.html from language folder
const langIndexHtml = path.join(templateDir, lang, 'index.html');
if (fs.existsSync(langIndexHtml)) {
fs.copyFileSync(langIndexHtml, path.join(projectPath, 'index.html'));
} else {
stopSpinner(spinner);
logError(`index.html not found in ${lang} template!`);
logError(`Expected: ${langIndexHtml}`);
process.exit(1);
}
// Create package.json
const packageJson = {
name: projectName,
version: "1.0.0",
description: `A Mintkit framework application - ${projectName}`,
main: "index.html",
keywords: ["mintkit", "vanilla-js", "web-framework"],
author: "Created with create-mint-app",
license: "MIT",
};
// Vite configuration
if (useVite) {
const viteConfigContent = `import { defineConfig } from 'vite';
export default defineConfig({
server: {
open: true,
},
});
`;
fs.writeFileSync(path.join(projectPath, 'vite.config.js'), viteConfigContent);
packageJson.scripts = {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
};
packageJson.devDependencies = {
"vite": "^5.3.5"
};
}
// TypeScript configuration
if (useVite && lang === 'typescript') {
const tsConfigContent = `{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}`;
fs.writeFileSync(path.join(projectPath, 'tsconfig.json'), tsConfigContent);
packageJson.devDependencies.typescript = "^5.5.3";
packageJson.scripts['type-check'] = "tsc --noEmit";
}
// ESLint and Prettier
if (useLinter) {
if (!packageJson.scripts) packageJson.scripts = {};
if (!packageJson.devDependencies) packageJson.devDependencies = {};
Object.assign(packageJson.devDependencies, {
"eslint": "^8.57.0",
"prettier": "^3.3.3",
"eslint-config-prettier": "^9.1.0"
});
const lintExtensions = lang === 'typescript' ? '{js,ts}' : 'js';
packageJson.scripts.lint = `eslint "src/**/*.${lintExtensions}"`;
packageJson.scripts.format = `prettier --write "src/**/*.{js,ts,html,css}"`;
const prettierrcContent = `{
"semi": true,
"tabWidth": 4,
"singleQuote": true,
"trailingComma": "es5"
}`;
fs.writeFileSync(path.join(projectPath, '.prettierrc'), prettierrcContent);
const prettierignoreContent = `node_modules\ndist\n`;
fs.writeFileSync(path.join(projectPath, '.prettierignore'), prettierignoreContent);
let eslintrcContent;
if (lang === 'typescript') {
Object.assign(packageJson.devDependencies, {
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0"
});
eslintrcContent = {
"parser": "@typescript-eslint/parser",
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"],
"plugins": ["@typescript-eslint"],
"env": { "browser": true, "es2021": true, "node": true },
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
"rules": {}
};
} else {
eslintrcContent = {
"extends": ["eslint:recommended", "prettier"],
"env": { "browser": true, "es2021": true, "node": true },
"parserOptions": { "ecmaVersion": "latest", "sourceType": "module" },
"rules": {}
};
}
fs.writeFileSync(
path.join(projectPath, '.eslintrc.json'),
JSON.stringify(eslintrcContent, null, 2)
);
}
// Write package.json
fs.writeFileSync(
path.join(projectPath, 'package.json'),
JSON.stringify(packageJson, null, 2)
);
stopSpinner(spinner);
logSuccess(`\nMintkit application ${colors.lightBlue}${projectName}${colors.reset} created successfully!`);
logInfo('');
if (useVite) {
logInfo('Installing dependencies...');
try {
await runCommand('npm', ['install'], { cwd: projectPath });
logSuccess('Dependencies installed successfully!');
let startDevServer = flags.has('--live');
if (!startDevServer) {
const runDevAnswer = await askList('Do you want to start the development server now?', ['Yes', 'No']);
if (runDevAnswer === 'Yes') {
startDevServer = true;
}
}
if (startDevServer) {
logInfo('\nStarting development server...');
await runCommand('npm', ['run', 'dev'], { cwd: projectPath });
} else {
logInfo('\nNext steps:');
logInfo(` cd ${projectName}`);
logInfo(' npm run dev');
}
} catch (error) {
logError('Failed to install dependencies.\nRun "npm install" manually.');
process.exit();
}
} else {
logInfo('Next steps:');
logInfo(` cd ${projectName}`);
logInfo(' code .');
logInfo(' Right-click in VS Code and select "Open with Live Server"');
}
logInfo('');
process.exit();
})();
/*
# To create Mintkit framework follow this instraction
> Create Javascript + Vite with ESLint & Prettier
npx create-mint-app my-mintkit-application --javascript --vite --lint
> Create TypeScript + Vite with ESLint & Prettier
npx create-mint-app my-mintkit-application --typescript --vite --lint
# Install Mintkit with just your 1 command
> With lint and Vite
npx create-mint-app my-mintkit-application --typescript --vite --lint --live
> Without lint and Vite
npx create-mint-app my-mintkit-application --typescript --no-vite --no-lint --live
All flags
| --vite
| --typescript
| --javascript
| --live
| --lint
| --no-vite
| --no-lint
> Default Mintkit install
npx create-mint-app@latest
*/