Files
qiming/qiming-xagi-frontend-templates/scripts/pack-template.js
2026-06-01 13:43:09 +08:00

212 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const { execSync } = require('child_process')
/**
* 模板打包脚本
* 用于将指定的模板项目打包成 zip 文件,排除 .gitignore 中指定的文件
*/
// 获取命令行参数
const templateName = process.argv[2]
const isRelease = process.argv.includes('--release')
if (!templateName) {
console.error('❌ 请指定模板名称')
console.log('用法: node scripts/pack-template.js <template-name> [--release]')
console.log('可用模板: react-vite, react-next, vue3-vite')
console.log(' --release 生成稳定文件名(无时间戳),用于 GitHub Release')
process.exit(1)
}
// 模板配置
const templates = {
'react-vite': {
name: 'React + Vite + TypeScript',
dir: 'packages/react-vite',
outputName: 'react-vite-template',
},
'react-next': {
name: 'React + Next.js + TypeScript',
dir: 'packages/react-next',
outputName: 'react-next-template',
},
'vue3-vite': {
name: 'Vue 3 + Vite + TypeScript',
dir: 'packages/vue3-vite',
outputName: 'vue3-vite-template',
},
}
const template = templates[templateName]
if (!template) {
console.error(`❌ 未找到模板: ${templateName}`)
console.log('可用模板:', Object.keys(templates).join(', '))
process.exit(1)
}
const templateDir = path.resolve(__dirname, '..', template.dir)
const outputDir = path.resolve(__dirname, '..', 'zip')
// 检查模板目录是否存在
if (!fs.existsSync(templateDir)) {
console.error(`❌ 模板目录不存在: ${templateDir}`)
process.exit(1)
}
// 创建输出目录
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true })
}
// 读取 package.json 获取版本号
let version = null
const packageJsonPath = path.join(templateDir, 'package.json')
if (fs.existsSync(packageJsonPath)) {
try {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
if (packageJson.version) {
version = packageJson.version
}
} catch (error) {
console.warn(`⚠️ 读取 package.json 失败: ${error.message}`)
}
}
// 生成时间戳YYYY_MM_DD_HH_MM_SS
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
const seconds = String(now.getSeconds()).padStart(2, '0')
const timestamp = `${year}_${month}_${day}_${hours}_${minutes}_${seconds}`
let fileName
if (isRelease) {
if (!version) {
console.error('❌ Release 模式需要 package.json 中有 version 字段')
process.exit(1)
}
fileName = `${template.outputName}_${version}.zip`
} else {
fileName = version
? `${template.outputName}_${version}_${timestamp}.zip`
: `${template.outputName}_${timestamp}.zip`
}
const outputFile = path.join(outputDir, fileName)
// 清除之前的包(匹配该模板的所有旧文件)
console.log(`🧹 清除之前的包...`)
try {
const files = fs.readdirSync(outputDir)
// 匹配该模板的所有旧文件(可能包含版本号,也可能不包含)
const oldFiles = files.filter(file => {
const baseName = file.replace(/\.zip$/, '')
// 匹配格式templateName_* 或 templateName_version_*
return (
file.endsWith('.zip') &&
(baseName === template.outputName ||
baseName.startsWith(`${template.outputName}_`))
)
})
if (oldFiles.length > 0) {
oldFiles.forEach(file => {
const filePath = path.join(outputDir, file)
fs.unlinkSync(filePath)
console.log(` ✓ 已删除: ${file}`)
})
console.log(`✅ 已清除 ${oldFiles.length} 个旧文件\n`)
} else {
console.log(` 没有找到旧文件\n`)
}
} catch (error) {
console.warn(`⚠️ 清除旧文件时出错: ${error.message}\n`)
}
console.log(`🚀 开始打包模板: ${template.name}`)
if (version) {
console.log(`📌 版本号: ${version}`)
}
console.log(`📁 源目录: ${templateDir}`)
console.log(`📦 输出文件: ${outputFile}`)
try {
// 读取 .gitignore 文件
const gitignorePath = path.join(templateDir, '.gitignore')
let ignorePatterns = []
// 强制排除的目录和文件
const forceExclude = [
'node_modules',
'dist',
'dist-ssr',
'build',
'.next',
'.nuxt',
'.cache',
'.parcel-cache',
'coverage',
'.nyc_output',
'*.log',
'.DS_Store',
'Thumbs.db',
'playground',
]
if (fs.existsSync(gitignorePath)) {
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf8')
ignorePatterns = gitignoreContent
.split('\n')
.map(line => line.trim())
.filter(line => line && !line.startsWith('#'))
.map(pattern => {
// 处理 .gitignore 模式
if (pattern.endsWith('/')) {
return pattern.slice(0, -1) // 移除末尾的 /
}
return pattern
})
}
// 合并强制排除和 .gitignore 中的模式
ignorePatterns = [...new Set([...forceExclude, ...ignorePatterns])]
// 构建 zip 命令 - 使用更可靠的排除方式
let zipCommand = `cd "${templateDir}" && zip -r "${outputFile}" .`
// 添加排除模式 - 使用多个 -x 参数,确保正确排除
if (ignorePatterns.length > 0) {
ignorePatterns.forEach(pattern => {
zipCommand += ` -x "${pattern}/*" -x "${pattern}"`
})
}
console.log('📋 排除模式:', ignorePatterns.length > 0 ? ignorePatterns.join(', ') : '无')
// 执行打包命令
console.log('⏳ 正在打包...')
execSync(zipCommand, { stdio: 'inherit' })
// 检查输出文件
if (fs.existsSync(outputFile)) {
const stats = fs.statSync(outputFile)
const fileSizeInMB = (stats.size / (1024 * 1024)).toFixed(2)
console.log('✅ 打包完成!')
console.log(`📦 输出文件: ${outputFile}`)
console.log(`📊 文件大小: ${fileSizeInMB} MB`)
} else {
console.error('❌ 打包失败: 输出文件未生成')
process.exit(1)
}
} catch (error) {
console.error('❌ 打包过程中发生错误:', error.message)
process.exit(1)
}