@hughcube/dev-toolkit
Version:
一套完整的开发工具集,包含小程序版本管理、代码上传、开发配置等功能
560 lines (489 loc) • 21 kB
JavaScript
/**
* UniApp 支付宝小程序开发助手
*/
const fs = require('fs')
const path = require('path')
const JSON5 = require('json5')
class UniappMpAlipayDevHelper {
constructor() {
this.parseArgs()
this.initPaths()
this.initConfig()
}
/**
* 解析命令行参数
*/
parseArgs() {
const args = process.argv.slice(2)
if (args.includes('--help') || args.includes('-h')) {
this.showHelp()
process.exit(0)
}
// CLI 参数解析
const getArgValue = (flags) => {
for (const flag of flags) {
const index = args.findIndex(arg => arg === flag)
if (index !== -1 && args[index + 1]) {
return args[index + 1]
}
}
return null
}
const getBooleanArg = (flags, defaultValue = null) => {
for (const flag of flags) {
const index = args.findIndex(arg => arg === flag)
if (index !== -1) {
const nextArg = args[index + 1]
if (nextArg && ['true', 'false'].includes(nextArg.toLowerCase())) {
return nextArg.toLowerCase() === 'true'
}
return defaultValue !== null ? defaultValue : true
}
}
return defaultValue
}
// 解析基本参数
this.mode = getArgValue(['--mode', '-m']) || 'dev'
this.customDistDir = getArgValue(['--dist-dir', '-d']) || null
this.watchMode = args.includes('--watch') || args.includes('-w')
// 解析 --dump-pages 参数
this.dumpPages = false
this.dumpPagesFilter = null
const dumpPagesIndex = args.findIndex(arg => arg === '--dump-pages')
if (dumpPagesIndex !== -1) {
this.dumpPages = true
// 检查是否有过滤参数
const nextArg = args[dumpPagesIndex + 1]
if (nextArg && !nextArg.startsWith('--')) {
this.dumpPagesFilter = nextArg
}
}
// 解析配置参数 - 核心配置默认为true,其他配置只有明确指定时才设置
this.ignoreHttpDomainCheck = getBooleanArg(['--ignore-http'], true)
this.ignoreWebViewDomainCheck = getBooleanArg(['--ignore-webview'], true)
this.ignoreCertificateDomainCheck = getBooleanArg(['--ignore-certificate'], true)
this.ignoreHttpsProtocol = getBooleanArg(['--ignore-https-protocol'], true)
// 其他配置项只有明确指定时才设置
this.minifyJS = getBooleanArg(['--minify-js'])
this.minifyCSS = getBooleanArg(['--minify-css'])
this.minifyWXML = getBooleanArg(['--minify-wxml'])
this.es6 = getBooleanArg(['--es6'])
this.postcss = getBooleanArg(['--postcss'])
this.minified = getBooleanArg(['--minified'])
// 解析自定义配置属性
this.customConfig = {}
args.forEach((arg, index) => {
if (arg.startsWith('--custom-')) {
const propertyName = arg.replace('--custom-', '')
const nextArg = args[index + 1]
if (nextArg) {
// 尝试解析为布尔值
if (['true', 'false'].includes(nextArg.toLowerCase())) {
this.customConfig[propertyName] = nextArg.toLowerCase() === 'true'
}
// 尝试解析为数字
else if (!isNaN(nextArg)) {
this.customConfig[propertyName] = Number(nextArg)
}
// 作为字符串处理
else {
this.customConfig[propertyName] = nextArg
}
}
}
})
// 验证必填参数
if (!this.mode) {
console.error('❌ 缺少构建模式参数')
console.error('使用方法: hctoolkit-uniapp-mp-alipay-dev-helper --mode dev --watch')
process.exit(1)
}
// 验证mode参数
if (!['dev', 'build'].includes(this.mode)) {
console.error('❌ 构建模式参数错误,只支持: dev, build')
process.exit(1)
}
}
/**
* 显示帮助信息
*/
showHelp() {
console.log(`
UniApp 支付宝小程序开发助手
使用方法:
hctoolkit-uniapp-mp-alipay-dev-helper --mode <模式> [选项]
参数:
--mode, -m 构建模式 (必填: dev 或 build)
--dist-dir, -d 构建产物目录 (可选,默认根据mode自动配置)
--ignore-http 忽略HTTP域名检查 (可选,默认true)
--ignore-webview 忽略WebView域名检查 (可选,默认true)
--ignore-certificate 忽略证书域名检查 (可选,默认true)
--ignore-https-protocol 忽略HTTPS协议检查 (可选,默认true)
--minify-js 压缩JS代码 (可选,仅明确指定时设置)
--minify-css 压缩CSS代码 (可选,仅明确指定时设置)
--minify-wxml 压缩WXML代码 (可选,仅明确指定时设置)
--es6 启用ES6转换 (可选,仅明确指定时设置)
--postcss 启用PostCSS处理 (可选,仅明确指定时设置)
--minified 启用代码压缩 (可选,仅明确指定时设置)
--custom-<property> 自定义配置属性 (如: --custom-myProperty true)
--dump-pages [filter] 导出页面配置到compileMode.json
支持路径过滤,如: --dump-pages pages/user
支持通配符,如: --dump-pages *user
--watch, -w 监听模式,启动构建并监听配置文件变化
--help, -h 显示帮助信息
示例:
# 开发模式 + 监听
hctoolkit-uniapp-mp-alipay-dev-helper --mode dev --watch
hctoolkit-uniapp-mp-alipay-dev-helper -m dev -w
# 构建模式,只生成配置文件
hctoolkit-uniapp-mp-alipay-dev-helper --mode build
hctoolkit-uniapp-mp-alipay-dev-helper -m build -d ./custom/dist
# 导出页面配置
hctoolkit-uniapp-mp-alipay-dev-helper --mode dev --dump-pages
# 导出特定路径的页面配置
hctoolkit-uniapp-mp-alipay-dev-helper --mode dev --dump-pages pages/user
hctoolkit-uniapp-mp-alipay-dev-helper --mode dev --dump-pages *user
# 自定义配置选项
hctoolkit-uniapp-mp-alipay-dev-helper \\
--mode dev \\
--dist-dir ./custom/path \\
--ignore-http false \\
--ignore-webview true \\
--custom-bundleAnalyzer true \\
--custom-pluginResolution local \\
--watch
功能:
1. 根据模式自动配置构建产物目录
2. 生成支付宝小程序开发所需的配置文件
3. 支持监听模式,自动重新生成被删除的配置文件
4. 支持所有标准配置属性和自定义配置属性
5. 根据模式执行对应的uni构建命令 (npx uni -p mp-alipay 或 npx uni build -p mp-alipay)
6. 支持导出页面配置到compileMode.json文件,支持路径过滤和通配符匹配
配置文件:
project-ide.json 生成位置: <dist-dir>/.mini-ide/project-ide.json
compileMode.json 生成位置: <dist-dir>/.mini-ide/compileMode.json (使用 --dump-pages 时)
核心配置 (默认启用):
- ignoreHttpDomainCheck: 忽略HTTP域名检查
- ignoreWebViewDomainCheck: 忽略WebView域名检查
- ignoreCertificateDomainCheck: 忽略证书域名检查
- ignoreHttpsProtocol: 忽略HTTPS协议检查
可选配置 (仅明确指定时添加):
- minifyJS: 压缩JS代码
- minifyCSS: 压缩CSS代码
- minifyWXML: 压缩WXML代码
- es6: 启用ES6转换
- postcss: 启用PostCSS处理
- minified: 启用代码压缩
自定义配置:
使用 --custom-<property> <value> 格式添加任意配置属性
`)
}
/**
* 初始化路径配置
*/
initPaths() {
// 如果指定了自定义目录,使用自定义目录;否则根据模式自动配置
if (this.customDistDir) {
this.distDir = path.resolve(this.customDistDir)
} else {
this.distDir = path.join(process.cwd(), `dist/${this.mode}/mp-alipay`)
}
this.configDir = path.join(this.distDir, '/.mini-ide')
this.configFile = path.join(this.configDir, '/project-ide.json')
this.compileModeFile = path.join(this.configDir, '/compileMode.json')
this.pagesConfigFile = path.join(process.cwd(), 'src/pages.json')
}
/**
* 初始化配置对象
*/
initConfig() {
this.config = {}
// 添加标准配置(仅当不为null时)
if (this.ignoreHttpDomainCheck !== null) {
this.config.ignoreHttpDomainCheck = this.ignoreHttpDomainCheck
}
if (this.ignoreWebViewDomainCheck !== null) {
this.config.ignoreWebViewDomainCheck = this.ignoreWebViewDomainCheck
}
if (this.ignoreCertificateDomainCheck !== null) {
this.config.ignoreCertificateDomainCheck = this.ignoreCertificateDomainCheck
}
if (this.ignoreHttpsProtocol !== null) {
this.config.ignoreHttpsProtocol = this.ignoreHttpsProtocol
}
if (this.minifyJS !== null) {
this.config.minifyJS = this.minifyJS
}
if (this.minifyCSS !== null) {
this.config.minifyCSS = this.minifyCSS
}
if (this.minifyWXML !== null) {
this.config.minifyWXML = this.minifyWXML
}
if (this.es6 !== null) {
this.config.es6 = this.es6
}
if (this.postcss !== null) {
this.config.postcss = this.postcss
}
if (this.minified !== null) {
this.config.minified = this.minified
}
// 添加自定义配置
Object.assign(this.config, this.customConfig)
}
/**
* 跨平台安全启动进程
*/
safeSpawn(command, args, options) {
const spawn = require('child_process').spawn
if (process.platform === 'win32') {
return spawn('cmd', ['/c', command, ...args], options)
} else {
return spawn(command, args, options)
}
}
/**
* 读取 pages.json 文件
*/
readPagesConfig() {
try {
if (!fs.existsSync(this.pagesConfigFile)) {
console.log('⚠️ pages.json 文件不存在:', this.pagesConfigFile)
return null
}
const content = fs.readFileSync(this.pagesConfigFile, 'utf8')
// 使用 JSON5 解析,支持注释和尾随逗号
const pagesConfig = JSON5.parse(content)
if (!pagesConfig.pages || !Array.isArray(pagesConfig.pages)) {
console.log('⚠️ pages.json 格式错误:缺少 pages 数组')
return null
}
return pagesConfig
} catch (error) {
console.error('❌ 读取 pages.json 失败:', error.message)
return null
}
}
/**
* 检查页面路径是否匹配过滤器
*/
isPagePathMatched(pagePath, filter) {
if (!filter) {
return true // 没有过滤器,匹配所有页面
}
// 移除可能存在的引号
const cleanFilter = filter.replace(/^['"](.*)['"]$/, '$1');
// 如果过滤器包含通配符 *
if (cleanFilter.includes('*')) {
// 将通配符转换为正则表达式
const regexPattern = cleanFilter.replace(/\*/g, '.*')
const regex = new RegExp(`^${regexPattern}`, 'i')
return regex.test(pagePath)
}
// 直接路径匹配(包含匹配)
return pagePath.includes(cleanFilter)
}
/**
* 生成 compileMode.json 文件
*/
generateCompileModeConfig() {
try {
const pagesConfig = this.readPagesConfig()
if (!pagesConfig) {
return
}
// 确保配置目录存在
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, {recursive: true})
}
// 读取现有的 compileMode.json
let existingModes = []
let existingConfig = {}
if (fs.existsSync(this.compileModeFile)) {
try {
const existingContent = fs.readFileSync(this.compileModeFile, 'utf8')
existingConfig = JSON5.parse(existingContent)
if (existingConfig.modes && Array.isArray(existingConfig.modes)) {
existingModes = existingConfig.modes
}
} catch (error) {
console.log('⚠️ 读取现有 compileMode.json 失败,将创建新文件')
existingConfig = {}
}
}
// 根据过滤器过滤页面
const filteredPages = pagesConfig.pages.filter(page =>
this.isPagePathMatched(page.path, this.dumpPagesFilter)
)
// 生成新的模式列表
const newModes = filteredPages.map(page => {
const title = page.style && page.style.navigationBarTitleText
? `${page.style.navigationBarTitleText}(helper)`
: `${page.path}(helper)`
return {
title: title,
page: page.path,
query: ""
}
})
// 合并并去重:优先保留现有的配置,然后添加新的配置
const allModes = [...existingModes]
newModes.forEach(newMode => {
// 检查是否已存在相同页面路径的配置
const existingIndex = allModes.findIndex(existingMode => existingMode.page === newMode.page)
if (existingIndex !== -1) {
// 如果存在,以新配置为主进行合并
allModes[existingIndex] = {...allModes[existingIndex], ...newMode}
} else {
// 如果不存在,添加新配置
allModes.push(newMode)
}
})
// 生成最终配置:保留原有的所有信息,只更新 modes 数组
const compileModeConfig = {
...existingConfig,
modes: allModes
}
// 写入文件
fs.writeFileSync(this.compileModeFile, JSON.stringify(compileModeConfig, null, 2), 'utf8')
console.log('✅ 生成 compileMode.json 文件:', this.compileModeFile)
console.log(` 添加了 ${newModes.length} 个页面配置,过滤器: ${this.dumpPagesFilter} ,原始页面数: ${pagesConfig.pages.length},过滤后: ${filteredPages.length}`)
} catch (error) {
console.error('❌ 生成 compileMode.json 失败:', error.message)
}
}
/**
* 生成配置文件
*/
generateConfig() {
try {
// 确保目录存在
if (!fs.existsSync(this.configDir)) {
fs.mkdirSync(this.configDir, {recursive: true})
}
// 读取并合并现有的 project-ide.json
let finalConfig = {...this.config}
if (fs.existsSync(this.configFile)) {
try {
const existingContent = fs.readFileSync(this.configFile, 'utf8')
const existingConfig = JSON5.parse(existingContent)
// 合并配置,以新配置为主
finalConfig = {...existingConfig, ...this.config}
} catch (error) {
console.log('⚠️ 读取现有 project-ide.json 失败,使用新配置')
}
}
// 写入配置文件
fs.writeFileSync(this.configFile, JSON.stringify(finalConfig, null, 2), 'utf8')
console.log('✅ 生成支付宝小程序配置文件:', this.configFile)
console.log(' 配置内容:', finalConfig)
// 如果开启了 dump-pages 选项,生成 compileMode.json
if (this.dumpPages) {
this.generateCompileModeConfig()
}
} catch (error) {
console.error('❌ 生成配置文件失败:', error.message)
}
}
/**
* 监听dist目录变化
*/
watchDistDir() {
if (!fs.existsSync(this.distDir)) {
console.log('⏳ 等待构建目录创建:', this.distDir)
setTimeout(() => this.watchDistDir(), 1000)
return
}
if (!fs.existsSync(this.configFile)) {
this.generateConfig()
}
try {
console.log('👀 开始监听配置文件变化:', this.configDir)
let watcher = fs.watch(this.configDir, {recursive: true}, (eventType, filename) => {
if (eventType === 'rename' && filename) {
setTimeout(() => {
if (fs.existsSync(this.distDir)) {
if (filename.includes('project-ide.json') && !fs.existsSync(this.configFile)) {
console.log('🔍 检测到 project-ide.json 被删除:', filename)
this.generateConfig()
}
if (this.dumpPages && filename.includes('compileMode.json') && !fs.existsSync(this.compileModeFile)) {
console.log('🔍 检测到 compileMode.json 被删除:', filename)
this.generateCompileModeConfig()
}
}
}, 500)
}
})
// 进程退出时清理监听器
const cleanup = () => {
console.log('\n🛑 停止监听...')
watcher?.close()
process.exit(0)
}
process.on('SIGINT', cleanup)
process.on('SIGTERM', cleanup)
} catch (error) {
console.error('❌ 监听目录失败:', error.message)
}
}
/**
* 启动开发模式
*/
startDev() {
console.log('🚀 启动 UniApp 支付宝小程序开发模式')
console.log('============================')
console.log('🖥️ 运行平台:', process.platform)
console.log('📱 构建模式:', this.mode)
console.log('📂 构建目录:', this.distDir)
console.log('⚙️ 配置选项:', this.config)
console.log('============================\n')
// 先监听目录
this.watchDistDir()
// 启动uni构建 - 使用安全的跨平台方式
const uniArgs = this.mode === 'dev' ? ['uni', '-p', 'mp-alipay'] : ['uni', 'build', '-p', 'mp-alipay']
console.log('🔨 启动uni构建进程...')
console.log(` 执行命令: npx ${uniArgs.join(' ')}`)
const uniProcess = this.safeSpawn('npx', uniArgs, {
stdio: 'inherit'
})
uniProcess.on('error', (error) => {
console.error('❌ 启动uni构建失败:', error.message)
console.error(' 请确保已安装项目依赖: yarn install 或 npm install')
console.error(' 检查是否有 @dcloudio/vite-plugin-uni 依赖')
process.exit(1)
})
uniProcess.on('close', (code) => {
console.log(`\n🏁 uni构建进程退出,代码: ${code}`)
process.exit(code)
})
}
/**
* 执行主逻辑
*/
run() {
try {
if (this.watchMode) {
this.startDev()
} else {
console.log('🚀 UniApp 支付宝小程序开发助手')
console.log('============================')
console.log('🖥️ 运行平台:', process.platform)
console.log('📱 构建模式:', this.mode)
console.log('📂 构建目录:', this.distDir)
console.log('⚙️ 配置选项:', this.config)
console.log('============================\n')
this.generateConfig()
console.log('\n✅ 配置文件生成完成!')
if (this.mode === 'dev') {
console.log('💡 提示:使用 --watch 参数可以启动监听模式')
}
}
} catch (error) {
console.error('❌ 执行失败:', error.message)
process.exit(1)
}
}
}
module.exports = UniappMpAlipayDevHelper;