create-crack
Version:
CLI tool for creating crack projects
775 lines (754 loc) • 27.1 kB
JavaScript
import { Command } from 'commander';
import kleur from 'kleur';
import { resolveApp } from '@verve-kit/utils';
import { select, confirm, intro } from '@clack/prompts';
import fs from 'fs-extra';
import { dirname, join } from 'node:path';
import { fileURLToPath as fileURLToPath$1 } from 'node:url';
import { createSpinner } from 'nanospinner';
import os from 'node:os';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'url';
import { execa } from 'execa';
import { execSync } from 'node:child_process';
import boxen from 'boxen';
/**
* 支持的项目模板类型
*/ const SUPPORTED_TEMPLATES = [
'react-web-js',
'react-web-ts'
];
/**
* 支持的包管理器
*/ const SUPPORTED_PACKAGE_MANAGERS = [
'npm',
'yarn',
'pnpm',
'cnpm'
];
/**
* 项目类型选择项配置
*/ const PROJECT_TYPE_OPTIONS = [
{
value: 'react-web-js',
label: 'react-web-js',
hint: 'React + JavaScript Web应用程序 🚀'
},
{
value: 'react-web-ts',
label: 'react-web-ts',
hint: 'React + TypeScript Web应用程序 🚀'
}
];
/**
* 包管理器选择项配置
*/ const PACKAGE_MANAGER_OPTIONS = [
{
value: 'npm',
label: 'npm'
},
{
value: 'yarn',
label: 'yarn'
},
{
value: 'pnpm',
label: 'pnpm'
},
{
value: 'cnpm',
label: 'cnpm'
}
];
/**
* 用户界面文本常量
*/ const UI_MESSAGES = {
INTRO: ' 🚧 Create Your App - 项目脚手架工具 ',
SELECT_PROJECT_TYPE: '🎯 选择项目类型:',
SELECT_PACKAGE_MANAGER: '📦 选择包管理器:',
ENABLE_ESLINT: '🔍 是否启用 ESLint 代码检查?',
ENABLE_COMMIT_LINT: '📝 是否启用 Commit Lint 配置?',
OVERWRITE_CONFIRM: 'Target directory already exists. Overwrite?',
CONFIG_SELECTION: '\n📋 请选择项目配置:\n',
CREATING_PROJECT: '\n🔧 正在创建项目...',
CTRL_C_EXIT: '⌨️ Ctrl+C pressed - Exiting the program'
};
/**
* 错误信息常量
*/ const ERROR_MESSAGES = {
INVALID_TEMPLATE: (template)=>`❌ 无效的模板类型: ${template}`,
INVALID_PACKAGE_MANAGER: (manager)=>`❌ 无效的包管理器: ${manager}`,
AVAILABLE_TEMPLATES: '可用的模板: react-web-js, react-web-ts',
AVAILABLE_PACKAGE_MANAGERS: '可用的包管理器: npm, yarn, pnpm, cnpm'
};
/**
* 成功信息常量
*/ const SUCCESS_MESSAGES = {
PROJECT_CREATED: '🎉 项目创建成功!\n',
FEATURES_INSTALLED: '📦 已安装的功能:',
ESLINT_CREATED: '✅ ESLint 配置文件已创建',
ESLINT_WARNING: '⚠️ ESLint 配置模板文件未找到',
TEMPLATE_COPY_SUCCESS: '✅ 项目模板复制成功',
DEPENDENCY_INSTALL_SUCCESS: '✅ Project initialization complete'
};
/**
* 验证项目模板类型是否有效
*/ function validateTemplate(template) {
return SUPPORTED_TEMPLATES.includes(template);
}
/**
* 验证包管理器是否有效
*/ function validatePackageManager(manager) {
return SUPPORTED_PACKAGE_MANAGERS.includes(manager);
}
/**
* 验证并处理模板选项
*/ function validateTemplateOption(template) {
if (!template) return undefined;
if (!validateTemplate(template)) {
console.error(kleur.red(ERROR_MESSAGES.INVALID_TEMPLATE(template)));
console.error(kleur.yellow(ERROR_MESSAGES.AVAILABLE_TEMPLATES));
process.exit(1);
}
return template;
}
/**
* 验证并处理包管理器选项
*/ function validatePackageManagerOption(manager) {
if (!manager) return undefined;
if (!validatePackageManager(manager)) {
console.error(kleur.red(ERROR_MESSAGES.INVALID_PACKAGE_MANAGER(manager)));
console.error(kleur.yellow(ERROR_MESSAGES.AVAILABLE_PACKAGE_MANAGERS));
process.exit(1);
}
return manager;
}
/**
* 验证并规范化所有选项
*/ function validateOptions(options) {
const validated = {
force: Boolean(options.force)
};
// 只有在明确提供了值时才设置属性
const template = validateTemplateOption(options.template);
if (template) {
validated.template = template;
}
const packageManager = validatePackageManagerOption(options.packageManager);
if (packageManager) {
validated.packageManager = packageManager;
}
if (options.eslint !== undefined) {
validated.eslint = Boolean(options.eslint);
}
if (options.commitLint !== undefined) {
validated.commitLint = Boolean(options.commitLint);
}
return validated;
}
/**
* 收集用户的项目配置选择
*/ async function collectUserConfiguration(options) {
let projectType;
let packageManager;
let enableEslint;
let commitLint;
// 获取项目类型
if (options.template) {
projectType = options.template;
} else {
projectType = await select({
message: UI_MESSAGES.SELECT_PROJECT_TYPE,
options: PROJECT_TYPE_OPTIONS
});
}
// 获取包管理器
if (options.packageManager) {
packageManager = options.packageManager;
} else {
packageManager = await select({
message: UI_MESSAGES.SELECT_PACKAGE_MANAGER,
options: PACKAGE_MANAGER_OPTIONS
});
}
// 获取 ESLint 选项
if (options.eslint !== undefined) {
enableEslint = options.eslint;
} else {
enableEslint = await confirm({
message: UI_MESSAGES.ENABLE_ESLINT
});
}
// 获取 Commit Lint 选项
if (options.commitLint !== undefined) {
commitLint = options.commitLint;
} else {
commitLint = await confirm({
message: UI_MESSAGES.ENABLE_COMMIT_LINT
});
}
return {
projectType,
packageManager,
enableEslint,
commitLint
};
}
/**
* 检查是否为非交互模式
*/ function isNonInteractiveMode(options) {
return !!(options.template || options.packageManager || options.eslint !== undefined || options.commitLint !== undefined);
}
/**
* 创建项目根目录
* 如果目录已存在且未强制覆盖,则询问用户是否覆盖
*/ async function createProjectDirectory(name, options) {
const root = resolveApp(name);
if (await fs.pathExists(root) && !options.force) {
const shouldOverwrite = await confirm({
message: UI_MESSAGES.OVERWRITE_CONFIRM
});
if (!shouldOverwrite) {
console.log(kleur.yellow('项目创建已取消'));
process.exit(1);
}
await fs.remove(root);
}
await fs.ensureDir(root);
return root;
}
const __filename$3 = fileURLToPath(import.meta.url);
const __dirname$3 = dirname(__filename$3);
/**
* 获取并解析指定路径下的 package.json 文件。
*
* @param relativePath - 相对于当前模块目录的路径(当 `isFromCurrentDir` 为 `true` 时)
* @param isFromCurrentDir - 如果为 `true`,则路径基于当前文件目录;否则视为绝对路径或调用方自定义路径
* @returns 返回解析后的 package.json 内容对象
*
* @example
* ```ts
* const pkg = await getPackageJsonInfo('../package.json', true);
* console.log(pkg.name);
* ```
*/ async function getPackageJsonInfo(relativePath, isFromCurrentDir) {
const filePath = isFromCurrentDir ? join(__dirname$3, relativePath) : relativePath;
return await fs.readJson(filePath);
}
/**
* 包版本管理配置
*/ // 需要动态更新版本的自有包列表
const PACKAGES_TO_UPDATE = [
'@verve-kit/react-script'
];
const __filename$2 = fileURLToPath$1(import.meta.url);
const __dirname$2 = dirname(__filename$2);
/**
* 获取 npm 包的最新版本
*
* @param packageName - 包名
* @returns Promise<string> - 最新版本号,如果获取失败则返回默认版本
*/ async function getLatestPackageVersion(packageName) {
try {
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return `^${data.version}`;
} catch (error) {
console.warn(kleur.yellow(`⚠️ 无法获取 ${packageName} 的最新版本,跳过更新`));
console.warn(kleur.gray(`错误信息: ${error}`));
return null;
}
}
/**
* 更新包依赖的版本号为最新版本
*
* @param packageJson - package.json 对象
* @returns Promise<PackageJsonType> - 更新后的 package.json 对象
*/ async function updatePackageVersions(packageJson) {
console.log(kleur.cyan('🔄 正在获取最新包版本...'));
const packagesToUpdate = PACKAGES_TO_UPDATE;
// 并发获取所有包的最新版本
const versionPromises = packagesToUpdate.map(async (packageName)=>{
const version = await getLatestPackageVersion(packageName);
return {
packageName,
version
};
});
try {
const versionResults = await Promise.all(versionPromises);
// 更新 dependencies
if (packageJson.dependencies) {
for (const { packageName, version } of versionResults){
if (version && packageJson.dependencies[packageName]) {
packageJson.dependencies[packageName] = version;
console.log(kleur.green(`✅ 更新 ${packageName}: ${version}`));
}
}
}
// 更新 devDependencies
if (packageJson.devDependencies) {
for (const { packageName, version } of versionResults){
if (version && packageJson.devDependencies[packageName]) {
packageJson.devDependencies[packageName] = version;
console.log(kleur.green(`✅ 更新 ${packageName}: ${version}`));
}
}
}
console.log(kleur.green('🎉 包版本更新完成'));
} catch (error) {
console.error(kleur.red('❌ 更新包版本时出错:'), error);
}
return packageJson;
}
/**
* 合并 ESLint 配置到 package.json 中
*
* @param packageJson - 基础的 package.json 对象
* @returns 合并了 ESLint 配置的 package.json 对象
*/ async function mergeEslintConfig(packageJson) {
try {
const eslintConfigPath = join(__dirname$2, './package/eslint.json');
const eslintConfig = await getPackageJsonInfo(eslintConfigPath, false);
if (!eslintConfig) {
console.warn('⚠️ ESLint 配置文件未找到,跳过 ESLint 配置合并');
return packageJson;
}
// 合并 scripts
if (eslintConfig.scripts) {
packageJson.scripts = {
...packageJson.scripts,
...eslintConfig.scripts
};
}
// 合并 devDependencies
if (eslintConfig.devDependencies) {
packageJson.devDependencies = {
...packageJson.devDependencies,
...eslintConfig.devDependencies
};
}
// 合并 lint-staged
if (eslintConfig['lint-staged']) {
packageJson['lint-staged'] = eslintConfig['lint-staged'];
}
console.log('✅ ESLint 配置已成功合并到 package.json');
return packageJson;
} catch (error) {
console.error('❌ 合并 ESLint 配置时出错:', error);
return packageJson;
}
}
/**
* 创建指定类型项目的 `package.json` 对象。
*
* @param projectType - 模板类型(如:react、vue、node 等)
* @param projectName - 项目名称,会被写入到 `package.json.name`
* @param enableEslint - 是否启用 ESLint 配置
* @returns 返回已定制的 `package.json` 对象
*
* @example
* ```ts
* const pkg = createPackageJson('react-web-ts', 'my-app', true);
* console.log(pkg.name); // 'my-app'
* ```
*/ async function createPackageJson(projectType, projectName, enableEslint = false) {
try {
// 从 package 目录读取对应项目类型的 JSON 文件
const templatePath = join(__dirname$2, `./package/${projectType}.json`);
console.log(`尝试读取模板: ${templatePath}`);
const packageInfo = await getPackageJsonInfo(templatePath, false);
if (!packageInfo) throw new Error('Package info is undefined');
packageInfo.author = os.userInfo().username;
packageInfo.name = projectName;
// 更新包版本为最新版本
const updatedPackageInfo = await updatePackageVersions(packageInfo);
// 如果启用了 ESLint,合并 ESLint 配置
if (enableEslint) {
return await mergeEslintConfig(updatedPackageInfo);
}
return updatedPackageInfo;
} catch (error) {
console.error(`❌ Failed to create package.json for "${projectType}"`);
console.error(error);
process.exit(1);
}
}
/**
* 读取模板目录中的任意 JSON 文件为字符串内容。
*
* @param fileName - 模板文件名(例如:`config.json`)
* @returns 返回文件内容的字符串
*
* @example
* ```ts
* const config = createTemplateFile('vite.config.json');
* console.log(JSON.parse(config));
* ```
*/ function createTemplateFile(fileName) {
const filePath = join(__dirname$2, `./package/${fileName}`);
return readFileSync(filePath, 'utf-8');
}
const __filename$1 = fileURLToPath$1(import.meta.url);
const __dirname$1 = dirname(__filename$1);
// 获取模板目录路径 - 总是相对于包根目录
const getTemplateDir$1 = async ()=>{
// 向上查找到包含 package.json 的目录(包根目录)
let currentDir = __dirname$1;
while(!await fs.pathExists(join(currentDir, 'package.json'))){
const parent = dirname(currentDir);
if (parent === currentDir) {
throw new Error('无法找到包根目录');
}
currentDir = parent;
}
return join(currentDir, 'template');
};
// 移除自己实现的复制函数,使用 fs-extra 的 copy 方法
/**
* 为指定项目集成 commitlint 和相关 husky 配置。
*
* @param projectName - 项目目录名
*
* @example
* ```ts
* await createCommitlint('my-app');
* ```
*/ async function createCommitlint(projectName) {
try {
// 复制 husky 模板文件
const huskyTemplateSource = join(await getTemplateDir$1(), 'template-husky');
if (await fs.pathExists(huskyTemplateSource)) {
await fs.copy(huskyTemplateSource, projectName);
console.log('✅ Husky 模板文件已复制');
} else {
console.warn('⚠️ Husky 模板目录未找到');
return;
}
// 使用绝对路径
const targetPackagePath = join(projectName, 'package.json');
const huskyTemplatePath = join(__dirname$1, './package/husky.json');
console.log(`读取 husky 模板: ${huskyTemplatePath}`);
console.log(`读取项目 package.json: ${targetPackagePath}`);
const huskyConfig = await fs.readJson(huskyTemplatePath);
const projectPackageJson = await fs.readJson(targetPackagePath);
// 合并 husky 配置到项目的 package.json 中
for(const key in huskyConfig){
const sourceValue = huskyConfig[key];
const targetValue = projectPackageJson[key];
if (typeof sourceValue === 'object' && !Array.isArray(sourceValue)) {
projectPackageJson[key] = {
...targetValue,
...sourceValue
};
} else if (Array.isArray(sourceValue)) {
projectPackageJson[key] = [
...sourceValue,
...targetValue ?? []
];
} else {
projectPackageJson[key] = sourceValue;
}
}
await fs.writeJson(targetPackagePath, projectPackageJson, {
spaces: 2
});
console.log('✅ Commit Lint 配置已成功合并到 package.json');
} catch (error) {
console.error('❌ 创建 Commit Lint 配置时出错:', error);
process.exit(1);
}
}
// 获取当前文件的目录路径
const __filename = fileURLToPath$1(import.meta.url);
const __dirname = dirname(__filename);
// 获取模板目录路径 - 总是相对于包根目录
const getTemplateDir = async ()=>{
// 向上查找到包含 package.json 的目录(包根目录)
let currentDir = __dirname;
while(!await fs.pathExists(join(currentDir, 'package.json'))){
const parent = dirname(currentDir);
if (parent === currentDir) {
throw new Error('无法找到包根目录');
}
currentDir = parent;
}
return join(currentDir, 'template');
};
/**
* 复制项目模板到目标目录
*/ async function copyProjectTemplate(projectType, projectRoot) {
const spinner = createSpinner(kleur.bold().cyan('正在复制项目模板...')).start();
try {
const templateSource = join(await getTemplateDir(), `template-${projectType}`);
if (!await fs.pathExists(templateSource)) {
throw new Error(`模板目录不存在: ${templateSource}`);
}
await fs.copy(templateSource, projectRoot);
spinner.success({
text: kleur.bold().green(SUCCESS_MESSAGES.TEMPLATE_COPY_SUCCESS)
});
} catch (error) {
spinner.error({
text: kleur.bold().red('❌ 项目模板复制失败')
});
console.error('Error:', error);
throw error;
}
}
/**
* 创建 ESLint 配置文件
*/ async function createEslintConfig(root) {
try {
const eslintConfigSource = join(await getTemplateDir(), 'eslint/eslint.config.mjs');
const eslintConfigDest = join(root, 'eslint.config.mjs');
if (await fs.pathExists(eslintConfigSource)) {
await fs.copy(eslintConfigSource, eslintConfigDest);
console.log(SUCCESS_MESSAGES.ESLINT_CREATED);
} else {
console.warn(SUCCESS_MESSAGES.ESLINT_WARNING);
}
} catch (error) {
console.error('❌ 创建 ESLint 配置文件时出错:', error);
throw error;
}
}
/**
* 创建项目的基础文件
*/ async function createProjectFiles(context) {
const { root, config, name } = context;
try {
// 创建 package.json
const pkg = await createPackageJson(config.projectType, name, config.enableEslint);
await fs.writeJson(join(root, 'package.json'), pkg, {
spaces: 2
});
// 创建 .gitignore
await fs.writeFile(join(root, '.gitignore'), createTemplateFile('gitignore'));
// 复制项目模板
await copyProjectTemplate(config.projectType, root);
// 创建 commit lint 配置
if (config.commitLint) {
await createCommitlint(root);
}
// 创建 ESLint 配置
if (config.enableEslint) {
await createEslintConfig(root);
}
} catch (error) {
console.error(kleur.red('❌ 创建项目文件时出错:'), error);
throw error;
}
}
/**
* 检查当前系统是否已安装 Git。
*
* @returns `true` 表示 Git 已安装,`false` 表示未安装。
*
* @example
* ```ts
* if (checkGitInstallation()) {
* console.log('Git is available.');
* } else {
* console.log('Please install Git.');
* }
* ```
*/ function checkGitInstallation() {
try {
// 尝试静默执行 git --version,如果命令执行失败将抛出异常
execSync('git --version', {
stdio: 'ignore'
});
return true;
} catch {
return false;
}
}
function createSuccessInfo(name, packageManage) {
const END_MSG = `${kleur.blue('🎉 created project ' + kleur.green(name) + ' Successfully')}\n\n 🙏 Thanks for using Create-Crack !`;
const BOXEN_CONFIG = {
padding: 1,
margin: {
top: 1,
bottom: 1
},
borderColor: 'cyan',
align: 'center',
borderStyle: 'double',
title: '🚀 Congratulations',
titleAlignment: 'center'
};
process.stdout.write(boxen(END_MSG, BOXEN_CONFIG));
console.log('👉 Get started with the following commands:');
console.log(`\n\r\r cd ${kleur.cyan(name)}`);
console.log(`\r\r ${kleur.cyan(packageManage)} start \r\n`);
}
/**
* 安装项目依赖
*/ async function installDependencies(packageManager, projectRoot) {
const spinner = createSpinner(kleur.bold('Installing dependencies...')).start();
try {
await execa(packageManager, [
'install'
], {
cwd: projectRoot
});
spinner.success({
text: kleur.green(SUCCESS_MESSAGES.DEPENDENCY_INSTALL_SUCCESS)
});
} catch (error) {
spinner.error({
text: kleur.red('Failed to install dependencies')
});
console.error(error);
throw error;
}
}
/**
* 初始化 Git 仓库
*/ async function initializeGitRepository(projectRoot) {
try {
if (checkGitInstallation()) {
await execa('git', [
'init'
], {
cwd: projectRoot,
stdio: 'ignore'
});
console.log(kleur.green('✅ Git 仓库初始化成功'));
} else {
console.warn(kleur.yellow('⚠️ Git 未安装,跳过 Git 仓库初始化'));
}
} catch (error) {
console.warn(kleur.yellow('⚠️ Git 仓库初始化失败:'), error);
}
}
/**
* 显示项目创建成功的信息
*/ function displaySuccessInfo(context) {
const { name, config } = context;
console.log(kleur.green(SUCCESS_MESSAGES.PROJECT_CREATED));
console.log(kleur.cyan(SUCCESS_MESSAGES.FEATURES_INSTALLED));
console.log(kleur.gray(` • ${config.projectType === 'react-web-ts' ? 'React + TypeScript' : 'React + JavaScript'} 项目模板`));
if (config.enableEslint) {
console.log(kleur.gray(' • ESLint 代码检查工具'));
}
if (config.commitLint) {
console.log(kleur.gray(' • Commit Lint 提交规范'));
}
createSuccessInfo(name, config.packageManager);
}
/**
* 执行后续设置:安装依赖、初始化Git、显示成功信息
*/ async function performPostSetup(context) {
try {
// 安装依赖
await installDependencies(context.config.packageManager, context.root);
// 初始化 Git 仓库
await initializeGitRepository(context.root);
// 显示成功信息
displaySuccessInfo(context);
} catch (error) {
console.error(kleur.red('❌ 项目后续设置过程中发生错误:'), error);
throw error;
}
}
/**
* 设置 Ctrl+C 退出监听(仅限终端环境)
*/ function setupExitHandler() {
if (process.stdin.isTTY) {
process.stdin.setRawMode(true);
process.stdin.on('data', (key)=>{
if (key[0] === 3) {
console.log(UI_MESSAGES.CTRL_C_EXIT);
process.exit(1);
}
});
}
}
/**
* 显示配置信息
*/ function displayConfiguration(context) {
const { name, config } = context;
console.log(kleur.yellow(UI_MESSAGES.CREATING_PROJECT));
console.log(kleur.gray(`📁 项目名称: ${name}`));
console.log(kleur.gray(`🎯 项目类型: ${config.projectType}`));
console.log(kleur.gray(`📦 包管理器: ${config.packageManager}`));
console.log(kleur.gray(`🔍 ESLint: ${config.enableEslint ? '启用' : '禁用'}`));
console.log(kleur.gray(`📝 Commit Lint: ${config.commitLint ? '启用' : '禁用'}`));
}
/**
* 创建项目上下文
*/ async function createProjectContext(name, rawOptions) {
const options = validateOptions(rawOptions);
const root = resolveApp(name);
const config = await collectUserConfiguration(options);
return {
name,
root,
config,
options
};
}
/**
* 创建项目主流程
*
* @param name - 项目名
* @param rawOptions - 控制参数
*/ async function createApp(name, rawOptions) {
try {
// 设置退出处理器
setupExitHandler();
// 显示欢迎信息
intro(kleur.green(UI_MESSAGES.INTRO));
// 创建项目上下文
const context = await createProjectContext(name, rawOptions);
// 如果是交互模式,显示配置选择提示
if (!isNonInteractiveMode(context.options)) {
console.log(kleur.cyan(UI_MESSAGES.CONFIG_SELECTION));
}
// 显示配置信息
displayConfiguration(context);
// 创建项目目录
context.root = await createProjectDirectory(name, context.options);
// 创建项目文件
await createProjectFiles(context);
// 执行后续设置(安装依赖、初始化Git、显示成功信息)
await performPostSetup(context);
} catch (error) {
console.error(kleur.red('❌ 项目创建过程中发生错误:'), error);
process.exit(1);
}
}
async function main() {
const program = new Command();
// 异步获取版本号
const packageInfo = await getPackageJsonInfo('../package.json', true);
program.version(kleur.green(packageInfo.version)).arguments('<project-name>').description(kleur.cyan('Create a directory for your project files')).option('-f, --force', 'Overwrite target directory if it exists').option('-t, --template <template>', 'Project template (react-web-js | react-web-ts)').option('-p, --package-manager <manager>', 'Package manager (npm | yarn | pnpm | cnpm)').option('-e, --eslint', 'Enable ESLint configuration').option('-c, --commit-lint', 'Enable Commit Lint configuration').option('--no-eslint', 'Disable ESLint configuration').option('--no-commit-lint', 'Disable Commit Lint configuration').addHelpText('after', `
${kleur.yellow('Examples:')}
${kleur.gray('# Interactive mode (default)')}
$ create-crack my-app
${kleur.gray('# Non-interactive mode with all options')}
$ create-crack my-app -t react-web-ts -p pnpm -e -c
${kleur.gray('# Create React JS project with npm and ESLint')}
$ create-crack my-app --template react-web-js --package-manager npm --eslint
${kleur.gray('# Create project without ESLint and Commit Lint')}
$ create-crack my-app -t react-web-ts -p yarn --no-eslint --no-commit-lint
${kleur.yellow('Available Templates:')}
${kleur.cyan('react-web-js')} - React + JavaScript Web应用程序
${kleur.cyan('react-web-ts')} - React + TypeScript Web应用程序
${kleur.yellow('Available Package Managers:')}
${kleur.cyan('npm')} - Node Package Manager
${kleur.cyan('yarn')} - Yarn Package Manager
${kleur.cyan('pnpm')} - PNPM Package Manager
${kleur.cyan('cnpm')} - CNPM Package Manager
`).action((name, options)=>{
createApp(name, options);
}).parse(process.argv);
}
main().catch(console.error);
//# sourceMappingURL=index.esm.js.map