create-arena-project
Version:
神奇代码岛<->VSCode,从这里开始,创建一个神岛代码项目的脚手架。
409 lines (377 loc) • 11.9 kB
JavaScript
/**
* @fileoverview 项目依赖安装模块
* 负责处理项目依赖的安装,包括基础依赖和可选的代码规范工具
*/
import { default as chalk } from "chalk";
import { spawnSync } from "child_process";
import { createSources } from "./skeleton.js";
import { default as ora } from "ora";
import { errorHandler, ErrorType } from "./error-handler.js";
// 基础开发依赖列表
const requirements = [
"webpack",
"webpack-cli",
"acorn",
"terser-webpack-plugin",
"typescript",
"ts-loader",
];
// 需要全局安装的依赖
const globalRequirements = ["typescript"];
/**
* 检查Git是否可用
* @param {Object} spawnOptions - 执行选项
* @returns {boolean} Git是否可用
*/
function isGitAvailable(spawnOptions) {
try {
const gitCheck = spawnSync("git", ["--version"], {
...spawnOptions,
stdio: "pipe",
});
return gitCheck.status === 0;
} catch (error) {
console.warn(chalk.yellow("[警告] [Git检查] 检查Git可用性时出错"));
return false;
}
}
/**
* 执行npm命令的工具函数
* @param {string} command - npm命令
* @param {string[]} args - 命令参数
* @param {Object} options - 执行选项
* @returns {Object} 执行结果
* @throws {Error} 当命令执行失败时抛出错误
*/
function executeNpmCommand(command, args, options) {
try {
const result = spawnSync(command, args, options);
if (result.error) {
throw new Error(`执行命令失败: ${result.error.message}`);
}
if (result.status !== 0) {
throw new Error(`命令执行失败,退出码: ${result.status}`);
}
return result;
} catch (error) {
throw error;
}
}
/**
* 配置package.json的npm脚本
* @param {Object} spawnOptions - 执行选项
*/
function configurePackageScripts(spawnOptions) {
const pkgSetCmd = "npm";
const pkgSetArgs = [
"pkg",
"set",
'scripts.eslint:fix="eslint --fix"',
'scripts.prettier:write="prettier . --write"',
'scripts.prettier:check="prettier . --check"',
];
try {
const pkgSet = spawnSync(pkgSetCmd, pkgSetArgs, spawnOptions);
if (pkgSet.error || pkgSet.status !== 0) {
throw new Error("修改失败 package.json");
}
} catch (error) {
errorHandler.handleError(
error,
ErrorType.CONFIGURATION,
"配置package.json脚本"
);
}
}
/**
* 安装项目依赖
* @param {string} cwd - 当前工作目录,用于执行npm命令的目录
* @param {Object} answers - 包含用户选择的配置答案
* @throws {Error} 当安装过程中出现错误时抛出异常
*/
async function install(cwd, answers) {
const spawnOptions = {
stdio: "inherit",
shell: true,
cwd: cwd || process.cwd(),
};
const isPackage = answers.npmPackage.value === "神岛组件库";
// 创建package.json
console.log(chalk.blue("[信息] [初始化] 开始创建package.json"));
console.log(
`${chalk.cyanBright("创建 package.json...")}
> ${chalk.dim(`npm init ${isPackage ? "" : "-y"}`)}
`
);
if (isPackage) {
console.log(
chalk.yellowBright(
"你已选择当前项目为npm包,请填写 package.json 相关信息。\n注意:入口文件[entryPoint]无需修改,脚手架会自动生成。"
)
);
}
try {
executeNpmCommand(
"npm",
[
"--registry=https://registry.npmmirror.com",
"init",
isPackage ? "" : "-y",
],
spawnOptions
);
console.log(chalk.greenBright("✓ package.json 创建成功"));
console.log(chalk.blue("[信息] [初始化] package.json 创建成功"));
} catch (error) {
await errorHandler.handleError(
error,
ErrorType.CONFIGURATION,
"创建 package.json",
() => {
console.error(
chalk.redBright("创建 package.json 失败:"),
error.message
);
}
);
throw error;
}
// 配置npm包的入口文件
if (isPackage) {
console.log(chalk.blue("[信息] [配置] 开始配置npm包入口文件"));
const pkgSetArgs = ["pkg", "set"];
const isClient = answers.isServer.value === "客户端";
pkgSetArgs.push(
`files[0]=${isClient ? "client" : "server"}/dist/${
isClient ? "client" : "server"
}/src/**/*`,
`main=${isClient ? "client" : "server"}/dist/${
isClient ? "client" : "server"
}/src/${isClient ? "clientApp" : "App"}.js`,
`types=${isClient ? "client" : "server"}/dist/${
isClient ? "client" : "server"
}/src/${isClient ? "clientApp" : "App"}.d.ts`
);
try {
const pkgSet = spawnSync("npm", pkgSetArgs, spawnOptions);
if (pkgSet.error || pkgSet.status !== 0) {
throw new Error("修改失败 package.json");
}
console.log(chalk.blue("[信息] [配置] npm包入口文件配置成功"));
} catch (error) {
await errorHandler.handleError(
error,
ErrorType.CONFIGURATION,
"配置npm包入口文件",
() => {
console.error(chalk.redBright("修改失败 package.json"));
}
);
process.exit(1);
}
}
// 根据是否i18n选择额外的源码数组
if (answers.i18n.value === "配置") {
console.log(chalk.blue("[信息] [i18n] 开始配置i18n"));
requirements.push("i18next");
}
// 配置代码规范工具
if (answers.prettier.value === "配置") {
console.log(chalk.blue("[信息] [代码质量] 开始配置代码规范工具"));
const gitAvailable = isGitAvailable(spawnOptions);
if (gitAvailable) {
requirements.push(
"eslint",
"typescript-eslint",
"prettier",
"husky",
"lint-staged"
);
console.log(
chalk.cyanBright(
"\n你已选择配置ESLint + Prettier + husky + lint-staged包,正在配置各项数据及初始化Git仓库中...\n"
)
);
try {
const gitInit = spawnSync("git", ["init"], spawnOptions);
if (gitInit.error || gitInit.status !== 0) {
throw new Error("Git初始化失败");
}
console.log(chalk.blue("[信息] [Git] Git仓库初始化成功"));
} catch (error) {
await errorHandler.handleError(
error,
ErrorType.CONFIGURATION,
"初始化Git仓库",
() => {
console.error(chalk.redBright("Git初始化失败"));
}
);
process.exit(1);
}
configurePackageScripts(spawnOptions);
try {
const husky = spawnSync("npx", ["husky", "init"], spawnOptions);
if (husky.error || husky.status !== 0) {
throw new Error("husky运行失败");
}
console.log(chalk.blue("[信息] [Git Hooks] husky初始化成功"));
} catch (error) {
await errorHandler.handleError(
error,
ErrorType.CONFIGURATION,
"初始化husky",
() => {
console.error(chalk.redBright("husky运行失败"));
}
);
process.exit(1);
}
try {
await createSources(
cwd,
[
{
type: "file",
path: "./.husky/pre-commit",
src: "src/assets/code_quality/husky/pre-commit",
},
{
type: "file",
path: "./eslint.config.mjs",
src: "src/assets/code_quality/eslint/eslint.config.mjs",
},
{
type: "file",
path: "./.lintstagedrc",
src: "src/assets/code_quality/lintstaged/.lintstagedrc",
},
{
type: "file",
path: "./.prettierrc",
src: "src/assets/code_quality/prettier/.prettierrc",
},
{
type: "file",
path: "./.prettierignore",
src: "src/assets/code_quality/prettier/.prettierignore",
},
{
type: "file",
path: "./.vscode/settings.json",
src: "src/assets/ide_config/settings.json",
},
{
type: "file",
path: "./.vscode/extensions.json",
src: "src/assets/ide_config/extensions.json",
},
],
false
);
console.log(chalk.greenBright("Git仓库及各项数据配置成功!"));
console.log(chalk.blue("[信息] [代码质量] 代码质量配置文件创建成功"));
} catch (error) {
await errorHandler.handleError(
error,
ErrorType.FILE_SYSTEM,
"创建代码质量配置文件"
);
}
} else {
console.log(
chalk.yellowBright(
"\nGit命令不可用,将跳过husky和lint-staged的安装,仅安装ESLint和Prettier。"
)
);
console.warn(chalk.yellow("[警告] [Git] Git命令不可用,跳过Git相关配置"));
requirements.push("eslint", "typescript-eslint", "prettier");
configurePackageScripts(spawnOptions);
try {
await createSources(
cwd,
[
{
type: "file",
path: "./eslint.config.mjs",
src: "src/assets/code_quality/eslint/eslint.config.mjs",
},
{
type: "file",
path: "./.prettierrc",
src: "src/assets/code_quality/prettier/.prettierrc",
},
{
type: "file",
path: "./.prettierignore",
src: "src/assets/code_quality/prettier/.prettierignore",
},
],
false
);
console.log(chalk.greenBright("ESLint和Prettier配置成功!"));
console.log(chalk.blue("[信息] [代码质量] ESLint和Prettier配置成功"));
} catch (error) {
await errorHandler.handleError(
error,
ErrorType.FILE_SYSTEM,
"创建ESLint和Prettier配置文件"
);
}
}
}
// 安装依赖
console.log(chalk.cyanBright("坐和放宽,正在安装依赖项中..."));
console.log(
chalk.blue(
"[信息] [依赖安装] 开始安装项目依赖,使用" + answers.dependentManner.value
)
);
// 使用 import 动态导入 ora
const spinner = ora("安装项目必备的依赖中,请稍等...\n").start();
let retryCount = 0;
while (retryCount < 3) {
try {
const proc = spawnSync(
answers.dependentManner.value,
[
"--registry=https://registry.npmmirror.com",
answers.dependentManner.value === "npm" ? "--save-dev" : "-D",
answers.dependentManner.value === "npm" ? "install" : "add",
...requirements,
],
{ ...spawnOptions, stdio: "inherit" }
);
if (!proc.error && proc.status === 0) {
spinner.succeed("依赖项安装成功!");
console.log(chalk.blue("[信息] [依赖安装] 依赖项安装成功"));
return;
}
throw new Error(proc.error || `安装失败,退出码: ${proc.status}`);
} catch (error) {
retryCount++;
if (retryCount === 3) {
spinner.fail(`安装依赖项失败,已重试${retryCount}次`);
await errorHandler.handleError(
error,
ErrorType.DEPENDENCY,
"安装项目依赖",
() => {
console.error(
chalk.redBright(`安装依赖项失败,已重试${retryCount}次`)
);
}
);
process.exit(1);
}
spinner.text = `安装失败,正在进行第${retryCount}次重试...`;
console.warn(
chalk.yellow(
`[警告] [依赖安装] 依赖安装失败,准备第${retryCount}次重试`
)
);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
}
}
export { install, requirements, globalRequirements };