module-migration-tool
Version:
分析项目文件依赖并迁移到新项目的工具
265 lines (225 loc) • 7.51 kB
JavaScript
/**
* 迁移核心模块
*/
const path = require("path")
const fs = require("fs")
const { analyzeFile } = require("./analyzers")
const { resolveDependencyPath } = require("./resolvers")
const { generateReport } = require("./reporters")
const { getAllFiles, copyFile, getRelativePath } = require("./utils/fileUtils")
const { detectProjectConfig, mergeConfig } = require("./utils/configUtils")
const chalk = require("chalk")
/**
* 迁移工具类
*/
class Migrator {
/**
* 创建迁移工具实例
* @param {Object} options - 选项
*/
constructor(options = {}) {
// 源路径
this.sourcePath = path.resolve(options.sourcePath || "./src")
// 入口点
this.entryPoint = path.resolve(options.entryPoint || "./src/index.js")
// 目标路径
this.targetPath = path.resolve(options.targetPath || "./dist")
// 检测项目配置
const detectedConfig = detectProjectConfig(path.dirname(this.sourcePath))
// 合并配置
this.config = mergeConfig({
...options,
...detectedConfig,
})
// 存储已处理的文件路径
this.processedFiles = new Set()
// 存储依赖关系
this.dependencyMap = new Map()
}
/**
* 执行迁移流程
*/
async migrate() {
console.log(chalk.blue("=== 文件迁移工具 ==="))
console.log(`源路径: ${chalk.yellow(this.sourcePath)}`)
console.log(`入口点: ${chalk.yellow(this.entryPoint)}`)
console.log(`目标路径: ${chalk.yellow(this.targetPath)}`)
// 确保目标目录存在
if (!fs.existsSync(this.targetPath)) {
fs.mkdirSync(this.targetPath, { recursive: true })
}
const startTime = Date.now()
try {
// 检查入口点是否存在
if (!fs.existsSync(this.entryPoint)) {
throw new Error(`入口点不存在: ${this.entryPoint}`)
}
// 分析入口点
if (fs.statSync(this.entryPoint).isDirectory()) {
// 目录入口
await this.processDirectoryEntry()
} else {
// 文件入口
await this.processFileEntry()
}
// 生成报告
if (this.config.report.generateReport) {
generateReport(this.dependencyMap, this.config.report.outputPath, {
projectRoot: this.sourcePath,
})
}
const duration = ((Date.now() - startTime) / 1000).toFixed(2)
console.log(chalk.green("\n迁移完成!"))
console.log(`共迁移 ${chalk.cyan(this.processedFiles.size)} 个文件`)
console.log(`用时: ${chalk.cyan(duration)} 秒`)
} catch (error) {
console.error(chalk.red("迁移过程中出错:"), error.message)
process.exit(1)
}
}
/**
* 处理目录入口
*/
async processDirectoryEntry() {
console.log(chalk.yellow("入口点是目录,查找所有文件..."))
// 获取目录下的所有文件
const files = getAllFiles(this.entryPoint, {
extensions: this.config.extensions,
ignorePaths: this.config.ignorePaths,
})
console.log(`共找到 ${chalk.cyan(files.length)} 个文件`)
// 分析所有文件依赖
let allDependencies = new Set()
for (const file of files) {
if (this.config.extensions.includes(path.extname(file))) {
console.log(`处理入口文件: ${chalk.cyan(file)}`)
const deps = await this.analyzeDependencies(file)
deps.forEach((d) => allDependencies.add(d))
} else {
// 非代码文件直接添加
allDependencies.add(path.resolve(file))
}
}
// 复制入口目录结构
const entryDirRelative = path.relative(
path.resolve(this.sourcePath),
path.resolve(this.entryPoint)
)
const entryDirTarget = path.join(this.targetPath, entryDirRelative)
// 确保入口目录结构被复制
if (!fs.existsSync(entryDirTarget)) {
fs.mkdirSync(entryDirTarget, { recursive: true })
}
// 复制所有依赖文件
await this.copyFilesToTarget(Array.from(allDependencies))
}
/**
* 处理文件入口
*/
async processFileEntry() {
console.log(`分析入口文件: ${chalk.cyan(this.entryPoint)}`)
// 分析依赖
const dependencies = await this.analyzeDependencies(this.entryPoint)
// 复制文件
await this.copyFilesToTarget(dependencies)
}
/**
* 递归分析文件依赖
* @param {string} filePath - 文件路径
* @returns {Array<string>} 依赖文件路径数组
*/
async analyzeDependencies(filePath) {
const queue = [path.resolve(filePath)]
const allDependencies = new Set()
while (queue.length > 0) {
const currentFile = queue.shift()
// 跳过已处理文件
if (this.processedFiles.has(currentFile)) {
continue
}
// 标记文件为已处理
this.processedFiles.add(currentFile)
allDependencies.add(currentFile)
// 查找文件依赖
const importPaths = await this.findDependencies(currentFile)
// 将依赖信息存入映射表
this.dependencyMap.set(
currentFile,
importPaths.map((p) => p.source)
)
// 将解析到的依赖添加到队列
for (const importInfo of importPaths) {
if (
importInfo.resolved &&
!this.config.ignorePaths.some((p) => importInfo.resolved.includes(p))
) {
queue.push(importInfo.resolved)
}
}
}
return Array.from(allDependencies)
}
/**
* 查找文件中的依赖
* @param {string} filePath - 文件路径
* @returns {Array<Object>} 依赖信息数组,包含源路径和解析后路径
*/
async findDependencies(filePath) {
try {
// 分析文件中的import语句
const dependencies = analyzeFile(filePath)
// 解析依赖路径
return dependencies.map((dep) => {
const resolved = resolveDependencyPath(
dep,
filePath,
this.config,
this.sourcePath
)
return {
source: dep,
resolved,
}
})
} catch (error) {
console.error(
chalk.red(`分析文件 ${filePath} 依赖时出错:`),
error.message
)
return []
}
}
/**
* 复制文件到目标目录
* @param {Array<string>} filePaths - 文件路径数组
*/
async copyFilesToTarget(filePaths) {
console.log(chalk.yellow("开始复制文件..."))
let successCount = 0
let failCount = 0
for (const filePath of filePaths) {
try {
// 获取相对于源目录的路径
const relativePath = getRelativePath(this.sourcePath, filePath)
const targetFilePath = path.join(this.targetPath, relativePath)
// 复制文件
const success = copyFile(filePath, targetFilePath)
if (success) {
successCount++
console.log(`已复制: ${chalk.green(relativePath)}`)
} else {
failCount++
}
} catch (error) {
failCount++
console.error(chalk.red(`复制文件 ${filePath} 时出错:`), error.message)
}
}
console.log(
`成功复制 ${chalk.green(successCount)} 个文件,失败 ${chalk.red(
failCount
)} 个文件`
)
}
}
module.exports = Migrator