UNPKG

@yanhe-su/cli

Version:

CLI tool for DAO Style projects - providing project scaffolding, template generation and dependency management

233 lines (232 loc) 7.92 kB
import * as path from 'path'; import ejs from 'ejs'; import fs from 'fs-extra'; export function createTemplateData(data) { return { ...data, helpers: { raw: (options) => options.fn(), capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1), formatDate: (date) => date.toLocaleDateString(), }, }; } // 根据文件扩展名判断是否需要模板渲染 function shouldRenderTemplate(filePath) { const ext = path.extname(filePath); return (ext === '.ejs' || ext === '.html' || ext === '.json' || ext === '.js' || ext === '.ts'); } // 新增函数:处理 package.json function processPackageJson(content, data) { try { const pkg = JSON.parse(content); // 合并来自 data 的配置 if (data.scripts) { pkg.scripts = { ...pkg.scripts, ...data.scripts }; } if (data.dependencies) { pkg.dependencies = { ...pkg.dependencies, ...data.dependencies }; } if (data.devDependencies) { pkg.devDependencies = { ...pkg.devDependencies, ...data.devDependencies }; } if (data.gitHooks) { pkg.gitHooks = { ...pkg.gitHooks, ...data.gitHooks }; } if (data['lint-staged']) { pkg['lint-staged'] = { ...pkg['lint-staged'], ...data['lint-staged'] }; } if (data.config) { pkg.config = { ...pkg.config, ...data.config }; } // 对依赖进行排序 if (pkg.dependencies) { pkg.dependencies = Object.keys(pkg.dependencies) .sort() .reduce((acc, key) => { const value = pkg.dependencies?.[key]; if (value) acc[key] = value; return acc; }, {}); } if (pkg.devDependencies) { pkg.devDependencies = Object.keys(pkg.devDependencies) .sort() .reduce((acc, key) => { const value = pkg.devDependencies?.[key]; if (value) acc[key] = value; return acc; }, {}); } // 对 scripts 进行排序 if (pkg.scripts) { pkg.scripts = Object.keys(pkg.scripts) .sort() .reduce((acc, key) => { const value = pkg.scripts?.[key]; if (value) acc[key] = value; return acc; }, {}); } // 对配置类字段进行排序 if (pkg.config) { pkg.config = Object.keys(pkg.config) .sort() .reduce((acc, key) => { const value = pkg.config?.[key]; if (value !== undefined) acc[key] = value; return acc; }, {}); } if (pkg.gitHooks) { pkg.gitHooks = Object.keys(pkg.gitHooks) .sort() .reduce((acc, key) => { const value = pkg.gitHooks?.[key]; if (value) acc[key] = value; return acc; }, {}); } if (pkg['lint-staged']) { pkg['lint-staged'] = Object.keys(pkg['lint-staged']) .sort() .reduce((acc, key) => { const value = pkg['lint-staged']?.[key]; if (value) acc[key] = value; return acc; }, {}); } // 对 keywords 进行排序 if (pkg.keywords) { pkg.keywords.sort(); } // 对其他可能的配置字段进行排序 const configFields = [ 'peerDependencies', 'optionalDependencies', 'resolutions', 'overrides', ]; for (const field of configFields) { if (pkg[field]) { pkg[field] = Object.keys(pkg[field]) .sort() .reduce((acc, key) => { acc[key] = pkg[field][key]; return acc; }, {}); } } return JSON.stringify(pkg, null, 2); } catch (error) { console.error('Error processing package.json:', error); return content; } } export async function renderTemplate(content, data, templateDir, filePath) { if (!shouldRenderTemplate(filePath)) { return content; } try { const rendered = await ejs.render(content, createTemplateData(data), { filename: filePath, async: true, }); // 如果是 package.json 文件,进行特殊处理 if (path.basename(filePath) === 'package.json') { return processPackageJson(rendered, data); } return rendered; } catch (error) { console.error(`Error rendering template ${filePath}:`, error); throw error; } } // 判断是否是二进制文件或系统文件 function shouldSkipFile(filename) { const skipFiles = [ '.DS_Store', 'Thumbs.db', '.git', '.svn', '.hg', '.idea', '.vscode', 'node_modules', ]; return (skipFiles.includes(filename) || /^\._/.test(filename) || // 跳过以 ._ 开头的文件 /^\./.test(filename)); // 跳过所有隐藏文件 } // 处理文件名中的模板变量 function processFileName(fileName, data) { let processedName = fileName; // 处理 _file.ext 形式的文件名 if (processedName.startsWith('_')) { processedName = `.${processedName.slice(1)}`; } try { // 使用 ejs 处理文件名中的模板变量 return ejs.render(processedName.replace(/_([^_]+)_/g, '<%= $1 %>'), data); } catch (error) { console.error(`Error processing filename ${fileName}:`, error); return processedName; } } export async function renderDirectory(sourcePath, targetPath, data) { try { await fs.ensureDir(targetPath); const files = await fs.readdir(sourcePath); for (const file of files) { // 跳过系统文件和二进制文件 if (shouldSkipFile(file)) { continue; } const sourceFilePath = path.join(sourcePath, file); // 处理文件名中的模板变量 const processedFileName = processFileName(file, data); const targetFilePath = path.join(targetPath, processedFileName); const stats = await fs.stat(sourceFilePath); if (stats.isDirectory()) { // 处理目录名中的模板变量 const processedDirName = processFileName(file, data); const targetDirPath = path.join(targetPath, processedDirName); await renderDirectory(sourceFilePath, targetDirPath, data); } else { try { const content = await fs.readFile(sourceFilePath, 'utf-8'); const rendered = await renderTemplate(content, data, path.dirname(sourceFilePath), sourceFilePath); await fs.writeFile(targetFilePath, rendered); } catch (error) { // 如果文件无法作为文本读取,直接复制 if (error instanceof Error && error.message.includes('Invalid or incomplete UTF-8')) { await fs.copy(sourceFilePath, targetFilePath); } else { throw error; } } } } } catch (error) { console.error('Error in renderDirectory:', error); throw error; } }