feat: Cli 优化
This commit is contained in:
parent
c64c7e0ea0
commit
cb0e9763f0
13
config.json
13
config.json
@ -1,8 +1,13 @@
|
||||
{
|
||||
"name": "测试",
|
||||
"version": "1.1.1",
|
||||
"description": "cordova apk build",
|
||||
"author": {
|
||||
"name": "taolin",
|
||||
"email": "taolin@taoya.art"
|
||||
},
|
||||
"homeUrl": "https://www.baidu.com",
|
||||
"appId": "asd",
|
||||
"appName": "asd",
|
||||
"appVersion": "1.0.0",
|
||||
"appId": "com.taoya.test",
|
||||
"packType": "debug",
|
||||
"pushEmail": "asd@qq.com"
|
||||
"pushEmail": "taolin@taoya.art"
|
||||
}
|
57
config.xml
57
config.xml
@ -1,33 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" id="com.example.tao" version="1.0.0">
|
||||
<name>APP</name>
|
||||
<description>app</description>
|
||||
<author email="dev@cordova.apache.org" href="https://cordova.apache.org"></author>
|
||||
<content src="http://test.taoya.art/" />
|
||||
<access origin="*" />
|
||||
<allow-intent href="http://*/*"/>
|
||||
<allow-intent href="https://*/*"/>
|
||||
<allow-intent href="tel:*"/>
|
||||
<allow-intent href="sms:*"/>
|
||||
<allow-intent href="mailto:*"/>
|
||||
<allow-intent href="geo:*"/>
|
||||
<allow-intent href="market:*"/>
|
||||
|
||||
<platform name="android">
|
||||
<preference name="Fullscreen" value="true"/>
|
||||
<icon src="logo.png"></icon>
|
||||
</platform>
|
||||
|
||||
|
||||
<!-- 设置Java和Gradle版本 -->
|
||||
<preference name="android-minSdkVersion" value="24" />
|
||||
<preference name="android-targetSdkVersion" value="35" />
|
||||
<preference name="android-compileSdkVersion" value="35" />
|
||||
<preference name="GradleVersion" value="8.7" />
|
||||
<preference name="JavaVersion" value="17" />
|
||||
<preference name="AndroidXEnabled" value="true" />
|
||||
<!-- Kotlin -->
|
||||
<preference name="GradlePluginKotlinEnabled" value="true" />
|
||||
<preference name="GradlePluginKotlinCodeStyle" value="official" />
|
||||
<preference name="GradlePluginKotlinVersion" value="1.9.24" />
|
||||
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0" id="com.taoya.test" version="1.1.1">
|
||||
<name>测试</name>
|
||||
<description>cordova apk build</description>
|
||||
<author email="taolin@taoya.art">taolin</author>
|
||||
<content src="https://www.baidu.com"></content>
|
||||
<access origin="*"></access>
|
||||
<allow-intent href="http://*/*"></allow-intent>
|
||||
<allow-intent href="https://*/*"></allow-intent>
|
||||
<allow-intent href="tel:*"></allow-intent>
|
||||
<allow-intent href="sms:*"></allow-intent>
|
||||
<allow-intent href="mailto:*"></allow-intent>
|
||||
<allow-intent href="geo:*"></allow-intent>
|
||||
<allow-intent href="market:*"></allow-intent>
|
||||
<platform name="android">
|
||||
<preference name="Fullscreen"></preference>
|
||||
<icon src="logo.png"></icon>
|
||||
</platform>
|
||||
<preference name="android-minSdkVersion" value="24"></preference>
|
||||
<preference name="android-targetSdkVersion" value="35"></preference>
|
||||
<preference name="android-compileSdkVersion" value="35"></preference>
|
||||
<preference name="GradleVersion" value="8.7"></preference>
|
||||
<preference name="JavaVersion" value="17"></preference>
|
||||
<preference name="AndroidXEnabled"></preference>
|
||||
<preference name="GradlePluginKotlinEnabled"></preference>
|
||||
<preference name="GradlePluginKotlinCodeStyle" value="official"></preference>
|
||||
<preference name="GradlePluginKotlinVersion" value="1.9.24"></preference>
|
||||
</widget>
|
||||
|
31
package-lock.json
generated
31
package-lock.json
generated
@ -32,6 +32,7 @@
|
||||
"cordova-plugin-vibration": "^3.1.1",
|
||||
"es6-promise-plugin": "^4.2.2",
|
||||
"eslint": "^9.17.0",
|
||||
"fast-xml-parser": "^4.5.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"tsx": "^4.19.2",
|
||||
"typeorm": "^0.3.20",
|
||||
@ -3988,6 +3989,29 @@
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz",
|
||||
"integrity": "sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/naturalintelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^1.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.17.1.tgz",
|
||||
@ -8562,6 +8586,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/strnum/-/strnum-1.0.5.tgz",
|
||||
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -12,7 +12,8 @@
|
||||
"build": "cordova build android",
|
||||
"dev": "node scripts/dev.js",
|
||||
"logo": "tsx scripts/logo.ts",
|
||||
"allInOne": "npm run clean && npm i && npm run ad"
|
||||
"allInOne": "npm run clean && npm i && npm run ad",
|
||||
"platform": "tsx scripts/platform.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "taolin taolin@taoya.art",
|
||||
@ -38,6 +39,7 @@
|
||||
"cordova-plugin-vibration": "^3.1.1",
|
||||
"es6-promise-plugin": "^4.2.2",
|
||||
"eslint": "^9.17.0",
|
||||
"fast-xml-parser": "^4.5.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"tsx": "^4.19.2",
|
||||
"typeorm": "^0.3.20",
|
||||
@ -61,5 +63,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^12.1.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@8.10.2+sha1.e0b68270e89c817ff88b7be62466a2128c53af02"
|
||||
}
|
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()
|
Loading…
Reference in New Issue
Block a user