feat: Cli 优化
This commit is contained in:
104
scripts/build.ts
Normal file
104
scripts/build.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { XMLParser, XMLBuilder } from 'fast-xml-parser'
|
||||
import consola from 'consola'
|
||||
|
||||
// 获取当前文件的目录路径
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
// 获取项目根目录路径
|
||||
const root = join(__dirname, '..')
|
||||
|
||||
// 定义配置文件的类型接口
|
||||
interface AppConfig {
|
||||
name: string // 应用名称
|
||||
version: string // 应用版本号
|
||||
description: string // 应用描述
|
||||
author: { // 作者信息
|
||||
name: string // 作者名称
|
||||
email: string // 作者邮箱
|
||||
}
|
||||
homeUrl: string // 应用主页 URL
|
||||
appId: string // 应用 ID
|
||||
packType: 'debug' | 'release' // 打包类型
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新 config.xml 文件的配置
|
||||
* @param config 应用配置对象
|
||||
*/
|
||||
async function updateConfigXml(config: AppConfig) {
|
||||
// 构建 config.xml 的完整路径
|
||||
const xmlPath = join(root, 'config.xml')
|
||||
|
||||
try {
|
||||
// 读取现有的 config.xml 文件内容
|
||||
const xmlContent = await readFile(xmlPath, 'utf-8')
|
||||
|
||||
// 创建 XML 解析器实例,配置属性处理选项
|
||||
const parser = new XMLParser({
|
||||
ignoreAttributes: false, // 不忽略 XML 属性
|
||||
attributeNamePrefix: '@_' // 属性名称前缀
|
||||
})
|
||||
// 解析 XML 内容为 JavaScript 对象
|
||||
const xmlObj = parser.parse(xmlContent)
|
||||
|
||||
// 更新配置信息
|
||||
xmlObj.widget['@_version'] = config.version // 更新版本号
|
||||
xmlObj.widget['@_id'] = config.appId // 更新应用 ID
|
||||
xmlObj.widget.name = config.name // 更新应用名称
|
||||
xmlObj.widget.description = config.description // 更新应用描述
|
||||
xmlObj.widget.author = {
|
||||
'#text': config.author.name, // 作者名称作为节点文本
|
||||
'@_email': config.author.email // 邮箱作为属性
|
||||
}
|
||||
|
||||
// 更新或添加 content 标签的 src 属性
|
||||
if (!xmlObj.widget.content) {
|
||||
xmlObj.widget.content = { '@_src': config.homeUrl }
|
||||
} else {
|
||||
xmlObj.widget.content['@_src'] = config.homeUrl
|
||||
}
|
||||
|
||||
|
||||
// 创建 XML 构建器实例
|
||||
const builder = new XMLBuilder({
|
||||
ignoreAttributes: false,
|
||||
attributeNamePrefix: '@_',
|
||||
format: true // 启用格式化输出
|
||||
})
|
||||
// 将对象转换回 XML 字符串
|
||||
const newXmlContent = builder.build(xmlObj)
|
||||
|
||||
// 将更新后的内容写回文件
|
||||
await writeFile(xmlPath, newXmlContent, 'utf-8')
|
||||
consola.success('config.xml has been updated')
|
||||
} catch (err) {
|
||||
consola.error('Failed to update config.xml:', err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 主函数:读取配置并执行更新
|
||||
*/
|
||||
async function main() {
|
||||
try {
|
||||
// 读取 config.json 配置文件
|
||||
const configPath = join(root, 'config.json')
|
||||
const configContent = await readFile(configPath, 'utf-8')
|
||||
const config: AppConfig = JSON.parse(configContent)
|
||||
|
||||
// 使用配置更新 config.xml
|
||||
await updateConfigXml(config)
|
||||
|
||||
// TODO: 这里可以添加其他构建步骤
|
||||
consola.success('Build completed successfully')
|
||||
} catch (err) {
|
||||
consola.error('Build failed:', err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// 执行主函数
|
||||
main()
|
102
scripts/cmd.ts
102
scripts/cmd.ts
@@ -2,9 +2,25 @@ import { z } from 'zod';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { intro, outro, text, select, isCancel, cancel, group } from '@clack/prompts';
|
||||
import { bgRed } from 'picocolors';
|
||||
|
||||
const ConfigSchema = z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "应用名称不能为空"
|
||||
}),
|
||||
version: z.string().regex(/^\d+\.\d+\.\d+$/, {
|
||||
message: "版本号格式必须为 x.x.x"
|
||||
}),
|
||||
description: z.string().min(1, {
|
||||
message: "应用描述不能为空"
|
||||
}),
|
||||
author: z.object({
|
||||
name: z.string().min(1, {
|
||||
message: "作者名称不能为空"
|
||||
}),
|
||||
email: z.string().email({
|
||||
message: "作者邮箱格式不正确"
|
||||
})
|
||||
}),
|
||||
homeUrl: z.string().url({
|
||||
message: "URL格式不正确"
|
||||
}),
|
||||
@@ -13,12 +29,6 @@ const ConfigSchema = z.object({
|
||||
}).regex(/^[a-zA-Z0-9._-]+$/, {
|
||||
message: "应用ID只能包含字母、数字、点、下划线和横线"
|
||||
}),
|
||||
appName: z.string().min(1, {
|
||||
message: "应用名称不能为空"
|
||||
}),
|
||||
appVersion: z.string().regex(/^\d+\.\d+\.\d+$/, {
|
||||
message: "版本号格式必须为 x.x.x"
|
||||
}),
|
||||
packType: z.enum(['release', 'debug'], {
|
||||
errorMap: () => ({ message: "打包类型必须是 release 或 debug" })
|
||||
}),
|
||||
@@ -34,6 +44,45 @@ async function main() {
|
||||
|
||||
const config = await group(
|
||||
{
|
||||
name: () => text({
|
||||
message: '请输入应用名称',
|
||||
validate(value) {
|
||||
if (!value.length) return '应用名称不能为空';
|
||||
},
|
||||
}),
|
||||
|
||||
version: () => text({
|
||||
message: '请输入应用版本号',
|
||||
validate(value) {
|
||||
if (!/^\d+\.\d+\.\d+$/.test(value)) {
|
||||
return '请输入正确的版本号,例如: 1.0.0';
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
description: () => text({
|
||||
message: '请输入应用描述',
|
||||
validate(value) {
|
||||
if (!value.length) return '应用描述不能为空';
|
||||
},
|
||||
}),
|
||||
|
||||
authorName: () => text({
|
||||
message: '请输入作者名称',
|
||||
validate(value) {
|
||||
if (!value.length) return '作者名称不能为空';
|
||||
},
|
||||
}),
|
||||
|
||||
authorEmail: () => text({
|
||||
message: '请输入作者邮箱',
|
||||
validate(value) {
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
|
||||
return '请输入正确的邮箱地址';
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
homeUrl: () => text({
|
||||
message: '请输入主页URL',
|
||||
validate(value) {
|
||||
@@ -55,22 +104,6 @@ async function main() {
|
||||
},
|
||||
}),
|
||||
|
||||
appName: () => text({
|
||||
message: '请输入应用名称',
|
||||
validate(value) {
|
||||
if (!value.length) return '应用名称不能为空';
|
||||
},
|
||||
}),
|
||||
|
||||
appVersion: () => text({
|
||||
message: '请输入应用版本号',
|
||||
validate(value) {
|
||||
if (!/^\d+\.\d+\.\d+$/.test(value)) {
|
||||
return '请输入正确的版本号,例如: 1.0.0';
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
packType: () => select({
|
||||
message: '请选择打包类型',
|
||||
options: [
|
||||
@@ -104,14 +137,29 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// 重构配置对象以匹配所需格式
|
||||
const formattedConfig = {
|
||||
name: config.name,
|
||||
version: config.version,
|
||||
description: config.description,
|
||||
author: {
|
||||
name: config.authorName,
|
||||
email: config.authorEmail
|
||||
},
|
||||
homeUrl: config.homeUrl,
|
||||
appId: config.appId,
|
||||
packType: config.packType,
|
||||
pushEmail: config.pushEmail
|
||||
};
|
||||
|
||||
try {
|
||||
// 使用zod验证配置
|
||||
const result = ConfigSchema.safeParse(config);
|
||||
const result = ConfigSchema.safeParse(formattedConfig);
|
||||
|
||||
if (!result.success) {
|
||||
cancel('配置验证失败');
|
||||
result.error.errors.forEach(error => {
|
||||
console.error(bgRed(` ${error.path.join('.')}: ${error.message} `));
|
||||
console.error((` ${error.path.join('.')}: ${error.message} `));
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -125,7 +173,7 @@ async function main() {
|
||||
} catch (error) {
|
||||
cancel('保存配置文件时出错');
|
||||
if (error instanceof Error) {
|
||||
console.error(bgRed(` ${error.message} `));
|
||||
console.error((` ${error.message} `));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -134,7 +182,7 @@ async function main() {
|
||||
main().catch(error => {
|
||||
cancel('发生意外错误');
|
||||
if (error instanceof Error) {
|
||||
console.error(bgRed(` ${error.message} `));
|
||||
console.error((` ${error.message} `));
|
||||
}
|
||||
process.exit(1);
|
||||
});
|
53
scripts/dist.ts
Normal file
53
scripts/dist.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { mkdir, copyFile, access } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import consola from 'consola'
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const root = join(__dirname, '..')
|
||||
|
||||
// 检查文件是否存在的辅助函数
|
||||
async function fileExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
await access(path)
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// 确保 dist 目录存在
|
||||
try {
|
||||
await mkdir(join(root, 'dist'))
|
||||
consola.info('dist directory created')
|
||||
} catch (err) {
|
||||
consola.info('dist directory already exists')
|
||||
}
|
||||
|
||||
// 定义 APK 文件路径
|
||||
const apkPaths = {
|
||||
debug: {
|
||||
source: join(root, 'platforms/android/app/build/outputs/apk/debug/app-debug.apk'),
|
||||
target: join(root, 'dist/app-debug.apk')
|
||||
},
|
||||
release: {
|
||||
source: join(root, 'platforms/android/app/build/outputs/apk/release/app-release.apk'),
|
||||
target: join(root, 'dist/app-release.apk')
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试复制 debug 和 release 版本的 APK
|
||||
for (const [type, paths] of Object.entries(apkPaths)) {
|
||||
if (await fileExists(paths.source)) {
|
||||
try {
|
||||
await copyFile(paths.source, paths.target)
|
||||
consola.success(`${type} APK copied to dist/app-${type}.apk`)
|
||||
} catch (err) {
|
||||
consola.warn(`Failed to copy ${type} APK:`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
2
scripts/index.ts
Normal file
2
scripts/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './dist' // 导出 dist 脚本
|
||||
export * from './logo' // 替换 Logo
|
73
scripts/platform.ts
Normal file
73
scripts/platform.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { exec } from 'node:child_process'
|
||||
import { promisify } from 'node:util'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { access } from 'node:fs/promises'
|
||||
import consola from 'consola'
|
||||
|
||||
const execAsync = promisify(exec)
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||
const root = join(__dirname, '..')
|
||||
|
||||
async function checkPlatformExists(platform: string): Promise<boolean> {
|
||||
try {
|
||||
await access(join(root, 'platforms', platform))
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function addPlatform(platform: string) {
|
||||
try {
|
||||
// 检查平台是否已存在
|
||||
const exists = await checkPlatformExists(platform)
|
||||
if (exists) {
|
||||
consola.info(`Platform ${platform} already exists`)
|
||||
return
|
||||
}
|
||||
|
||||
// 添加平台
|
||||
consola.info(`Adding ${platform} platform...`)
|
||||
const { stdout, stderr } = await execAsync(`cordova platform add ${platform}`, {
|
||||
cwd: root,
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_ENV: 'development'
|
||||
}
|
||||
})
|
||||
|
||||
if (stderr) {
|
||||
consola.warn(stderr)
|
||||
}
|
||||
|
||||
if (stdout) {
|
||||
consola.info(stdout)
|
||||
}
|
||||
|
||||
consola.success(`Platform ${platform} added successfully`)
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('Failed to add platform:', error.message)
|
||||
} else {
|
||||
consola.error('An unknown error occurred while adding platform')
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// 添加 Android 平台
|
||||
await addPlatform('android')
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
consola.error('Platform addition failed:', error.message)
|
||||
} else {
|
||||
consola.error('An unknown error occurred')
|
||||
}
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
Reference in New Issue
Block a user