依赖冲突处理(conflict-resolve.ts)
操作步骤详解
- 依赖清理规则设定
// 精确匹配待删除依赖项
const packageNamesToRemove = [ ... ];
// 前缀匹配待删除依赖项
const packagePrefixesToRemove = [ ... ];
// 示例说明:
// - 精确匹配项:eslint、prettier、husky 等
// - 前缀匹配项:eslint- 插件、@typescript-eslint/ 等
⚠️ 注意:这些依赖项若存在,可能与 PKG_NAME 内置工具产生冲突,需要移除。
配置文件检查
const checkUselessConfig = (cwd: string): string[] => {
return [
...glob.sync('.eslintrc?...', { cwd }),
...glob.sync('.stylelintrc?...', { cwd }),
...glob.sync('.markdownlint...', { cwd }),
...glob.sync('.prettierrc?...', { cwd }),
...glob.sync('tslint.@(yaml|yml|json)', { cwd }),
...glob.sync('.kylerc?...', { cwd })
];
};
🔍 功能说明:扫描项目目录,查找以下旧配置文件:
- 各类 lint 配置(eslint/stylelint/prettier)
- tslint 相关配置
- 其他工具配置文件
需重写配置检查
const checkReWriteConfig = (cwd: string) => {
return glob
.sync('**/*.ejs', { cwd: path.resolve(__dirname, '../config') })
.map(name => name.replace(/^_/, '.').replace(/\.ejs$/, ''))
.filter(filename => fs.existsSync(path.resolve(cwd, filename)));
};
🔄 处理流程:
- 从模板目录查找 .ejs 配置文件
- 转换为实际配置文件名(如 _eslintrc.ejs → .eslintrc)
- 筛选出项目中已存在的配置文件
结果汇总
const willRemovePackage = dependencies.filter(...); // 待移除依赖
const uselessConfig = checkUselessConfig(cwd); // 待删除配置
const reWriteConfig = checkReWriteConfig(cwd); // 待覆盖配置
const willChangeCount = willRemovePackage.length + uselessConfig.length + reWriteConfig.length;
用户确认环节
if (willChangeCount > 0) {
log.warn(`检测到 ${PKG_NAME} 可能存在配置冲突...`);
const { isOverWrite } = await inquirer.prompt({
type: 'confirm',
name: 'isOverWrite',
message: '是否继续执行?'
});
if (!isOverWrite) process.exit(0);
}
📌 交互逻辑:
- 展示待修改内容清单
- 获取用户确认
- 拒绝则终止流程
执行清理操作
// 删除旧配置文件
uselessConfig.forEach(name => {
fs.removeSync(path.resolve(cwd, name));
});
// 清理 package.json
delete pkg.eslintConfig;
delete pkg.eslintIgnore;
delete pkg.stylelint;
// 移除冲突依赖
willRemovePackage.forEach(name => {
delete (pkg.dependencies || {})[name];
delete (pkg.devDependencies || {})[name];
});
// 保存修改
fs.writeFileSync(path.resolve(cwd, 'package.json'), JSON.stringify(pkg, null, 2));
本流程用于确保环境清洁:
- 检测冲突配置和依赖
- 获取用户授权
- 执行清理操作
- 避免与新工具产生冲突
完整代码:
import path from 'path';
import fs from 'fs-extra';
import glob from 'glob';
import inquirer from 'inquirer';
import log from './log';
import { PKG_NAME } from './constants';
import type { PKG } from '../types';
// 精确移除依赖
const packageNamesToRemove = [
'@babel/eslint-parser',
'@commitlint/cli',
'@iceworks/spec',
'babel-eslint',
'eslint',
'husky',
'markdownlint',
'prettier',
'stylelint',
'tslint',
];
// 按前缀移除依赖
const packagePrefixesToRemove = [
'@commitlint/',
'@typescript-eslint/',
'eslint-',
'stylelint-',
'markdownlint-',
'commitlint-',
];
/**
* 待删除的无用配置
* @param cwd
*/
const checkUselessConfig = (cwd: string): string[] => {
return []
.concat(glob.sync('.eslintrc?(.@(yaml|yml|json))', { cwd }))
.concat(glob.sync('.stylelintrc?(.@(yaml|yml|json))', { cwd }))
.concat(glob.sync('.markdownlint@(rc|.@(yaml|yml|jsonc))', { cwd }))
.concat(
glob.sync('.prettierrc?(.@(cjs|config.js|config.cjs|yaml|yml|json|json5|toml))', { cwd }),
)
.concat(glob.sync('tslint.@(yaml|yml|json)', { cwd }))
.concat(glob.sync('.kylerc?(.@(yaml|yml|json))', { cwd }));
};
/**
* 待重写的配置
* @param cwd
*/
const checkReWriteConfig = (cwd: string) => {
return glob
.sync('**/*.ejs', { cwd: path.resolve(__dirname, '../config') })
.map((name) => name.replace(/^_/, '.').replace(/\.ejs$/, ''))
.filter((filename) => fs.existsSync(path.resolve(cwd, filename)));
};
export default async (cwd: string, rewriteConfig?: boolean) => {
const pkgPath = path.resolve(cwd, 'package.json');
const pkg: PKG = fs.readJSONSync(pkgPath);
const dependencies = [].concat(
Object.keys(pkg.dependencies || {}),
Object.keys(pkg.devDependencies || []),
);
const willRemovePackage = dependencies.filter(
(name) =>
packageNamesToRemove.includes(name) ||
packagePrefixesToRemove.some((prefix) => name.startsWith(prefix)),
);
const uselessConfig = checkUselessConfig(cwd);
const reWriteConfig = checkReWriteConfig(cwd);
const willChangeCount = willRemovePackage.length + uselessConfig.length + reWriteConfig.length;
// 提示是否移除原配置
if (willChangeCount > 0) {
log.warn(`检测到项目中存在可能与 ${PKG_NAME} 冲突的依赖和配置,为保证正常运行将`);
if (willRemovePackage.length > 0) {
log.warn('删除以下依赖:');
log.warn(JSON.stringify(willRemovePackage, null, 2));
}
if (uselessConfig.length > 0) {
log.warn('删除以下配置文件:');
log.warn(JSON.stringify(uselessConfig, null, 2));
}
if (reWriteConfig.length > 0) {
log.warn('覆盖以下配置文件:');
log.warn(JSON.stringify(reWriteConfig, null, 2));
}
if (typeof rewriteConfig === 'undefined') {
const { isOverWrite } = await inquirer.prompt({
type: 'confirm',
name: 'isOverWrite',
message: '请确认是否继续:',
});
if (!isOverWrite) process.exit(0);
} else if (!reWriteConfig) {
process.exit(0);
}
}
// 删除配置文件
for (const name of uselessConfig) {
fs.removeSync(path.resolve(cwd, name));
}
// 修正 package.json
delete pkg.eslintConfig;
delete pkg.eslintIgnore;
delete pkg.stylelint;
for (const name of willRemovePackage) {
delete (pkg.dependencies || {})[name];
delete (pkg.devDependencies || {})[name];
}
fs.writeFileSync(path.resolve(cwd, 'package.json'), JSON.stringify(pkg, null, 2), 'utf8');
return pkg;
};
EJS 模板引擎
1️⃣ EJS 简介
EJS(Embedded JavaScript)是一种高效的模板引擎,它允许开发者在文本文件(如 HTML 或配置文件)中嵌入 JavaScript 代码,实现动态内容生成。典型应用场景包括:填充用户选项、循环生成列表、条件渲染等。
2️⃣ ejs.render 基础用法
import ejs from 'ejs';
const template = `
Hello <%= name %>!
<% if (age >= 18) { %>
You are an adult.
<% } else { %>
You are a minor.
<% } %>
`;
const data = { name: 'Alice', age: 20 };
const result = ejs.render(template, data);
console.log(result);
输出结果:
Hello Alice!
You are an adult.
语法说明:
<%= ... %>
:输出表达式值(自动转义)<% ... %>
:执行 JavaScript 逻辑(不输出)data
:模板渲染时传入的数据对象
3️⃣ 实际应用示例
let content = ejs.render(
fs.readFileSync(path.resolve(templatePath, name), 'utf8'),
{
eslintIgnores: ESLINT_IGNORE_PATTERN,
stylelintExt: STYLELINT_FILE_EXT,
stylelintIgnores: STYLELINT_IGNORE_PATTERN,
markdownLintIgnores: MARKDOWN_LINT_IGNORE_PATTERN,
...data, // 合并用户配置选项
}
);
实现功能:
- 读取指定路径的 EJS 模板文件
- 注入多类配置数据:
- ESLint 忽略规则
- Stylelint 相关配置(文件扩展名和忽略规则)
- Markdownlint 忽略规则
- 用户自定义选项(如是否启用 Prettier、Stylelint 等)
- 生成最终文件内容:
- 替换模板中的变量占位符
- 执行条件判断和循环逻辑
根据模板生成项目配置文件(generate-template.ts)
1️⃣ 核心功能
配置文件生成
- 根据用户选择(ESLint/Stylelint/Prettier/Markdownlint等)生成对应配置文件
- 支持生成格式:
.eslintrc
、.stylelintrc
、.prettierrc
、.vscode/settings.json
等
智能配置合并
- 当项目存在VSCode配置时自动进行深度合并
- 采用lodash.mergeWith实现智能合并
- 特殊处理数组类型配置(如
files.exclude
/eslint.validate
),自动去重
模板化渲染
- 基于EJS模板引擎动态生成文件内容
- 自动注入用户选项和预设忽略模式
- 支持文件名转换规则:
_filename
→.filename
(如_eslintrc.ejs
转.eslintrc
)
2️⃣ 实现逻辑
模板定位
const templatePath = path.resolve(__dirname, '../config');
模板匹配
- 根据vscode标识动态选择模板目录
const templates = glob.sync(`${vscode ? '_vscode' : '**'}/*.ejs`, { cwd: templatePath });
模板渲染
ejs.render(fs.readFileSync(templatePath + name), {
eslintIgnores,
stylelintExt,
stylelintIgnores,
markdownLintIgnores,
...data
});
配置合并处理
if (/^_vscode/.test(name)) {
content = mergeVSCodeConfig(filepath, content);
}
文件写入
fs.outputFileSync(filepath, content, 'utf8');
3️⃣ 核心优势
◾ 模板化架构:通过.ejs模板实现配置灵活扩展
◾ 安全合并机制:智能保留现有VSCode配置
◾ 自动命名转换:_
前缀文件自动转为.
配置文件
◾ 智能规则注入:自动集成各lint工具的忽略规则
基于模板引擎的智能配置生成器,可安全合并现有VSCode配置,避免覆盖用户自定义设置。
完整代码:
import path from 'path';
import fs from 'fs-extra';
import _ from 'lodash';
import glob from 'glob';
import ejs from 'ejs'; /
import {
ESLINT_IGNORE_PATTERN, // ESLint 忽略文件模式
STYLELINT_FILE_EXT, // Stylelint 支持的文件扩展名
STYLELINT_IGNORE_PATTERN, // Stylelint 忽略文件模式
MARKDOWN_LINT_IGNORE_PATTERN, // Markdownlint 忽略文件模式
} from './constants';
/**
* VSCode 配置合并
* 将新生成的 VSCode 配置与现有配置进行智能合并
* @param filepath 目标配置文件路径
* @param content 新生成的配置内容
* @returns 合并后的配置内容
*/
const mergeVSCodeConfig = (filepath: string, content: string) => {
// 如果目标文件不存在,直接返回新内容
if (!fs.existsSync(filepath)) return content;
try {
// 读取现有配置文件
const targetData = fs.readJSONSync(filepath);
// 解析新生成的配置内容
const sourceData = JSON.parse(content);
// 使用 lodash 的 mergeWith 进行深度合并
return JSON.stringify(
_.mergeWith(targetData, sourceData, (target, source) => {
// 如果两个值都是数组,则合并并去重
if (Array.isArray(target) && Array.isArray(source)) {
return [...new Set(source.concat(target))];
}
}),
null,
2, // 缩进 2 个空格
);
} catch (e) {
// 解析失败时返回空字符串
return '';
}
};
/**
* 生成配置文件模板
* 根据用户选择的配置选项生成相应的配置文件
* @param cwd 当前工作目录
* @param data 配置数据(用户选择的各种选项)
* @param vscode 是否只生成 VSCode 相关配置,默认为 false
*/
export default (cwd: string, data: Record<string, any>, vscode?: boolean) => {
// 模板文件所在目录
const templatePath = path.resolve(__dirname, '../config');
// 根据 vscode 参数决定匹配的模板文件
// 如果 vscode 为 true,只匹配 _vscode 目录下的模板
// 否则匹配所有目录下的 .ejs 模板文件
const templates = glob.sync(`${vscode ? '_vscode' : '**'}/*.ejs`, { cwd: templatePath });
// 遍历所有模板文件
for (const name of templates) {
// 生成目标文件路径
// 移除 .ejs 扩展名,将 _ 开头的文件名改为 . 开头
const filepath = path.resolve(cwd, name.replace(/\.ejs$/, '').replace(/^_/, '.'));
// 使用 EJS 模板引擎渲染文件内容
let content = ejs.render(fs.readFileSync(path.resolve(templatePath, name), 'utf8'), {
eslintIgnores: ESLINT_IGNORE_PATTERN, // ESLint 忽略模式
stylelintExt: STYLELINT_FILE_EXT, // Stylelint 文件扩展名
stylelintIgnores: STYLELINT_IGNORE_PATTERN, // Stylelint 忽略模式
markdownLintIgnores: MARKDOWN_LINT_IGNORE_PATTERN, // Markdownlint 忽略模式
...data, // 用户选择的配置选项
});
// 如果是 VSCode 配置文件,需要与现有配置合并
if (/^_vscode/.test(name)) {
content = mergeVSCodeConfig(filepath, content);
}
// 跳过空文件
if (!content.trim()) continue;
// 写入文件到目标路径
fs.outputFileSync(filepath, content, 'utf8');
}
};