diff --git a/config.json b/config.json index 030bb9a..3b69e7b 100644 --- a/config.json +++ b/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" -} \ No newline at end of file + "pushEmail": "taolin@taoya.art" +} diff --git a/config.xml b/config.xml index 52cfce5..c20bd73 100644 --- a/config.xml +++ b/config.xml @@ -1,33 +1,28 @@ - - APP - app - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + 测试 + cordova apk build + taolin + + + + + + + + + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 17e56f0..378f845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 3a3273d..bcd6504 100644 --- a/package.json +++ b/package.json @@ -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" } \ No newline at end of file diff --git a/scripts/build.ts b/scripts/build.ts new file mode 100644 index 0000000..e0cc1e4 --- /dev/null +++ b/scripts/build.ts @@ -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() diff --git a/scripts/cmd.ts b/scripts/cmd.ts index 2594d79..7557cd7 100644 --- a/scripts/cmd.ts +++ b/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); }); \ No newline at end of file diff --git a/scripts/dist.ts b/scripts/dist.ts new file mode 100644 index 0000000..82eb85e --- /dev/null +++ b/scripts/dist.ts @@ -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 { + 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() diff --git a/scripts/index.ts b/scripts/index.ts new file mode 100644 index 0000000..c522db0 --- /dev/null +++ b/scripts/index.ts @@ -0,0 +1,2 @@ +export * from './dist' // 导出 dist 脚本 +export * from './logo' // 替换 Logo \ No newline at end of file diff --git a/scripts/platform.ts b/scripts/platform.ts new file mode 100644 index 0000000..80e7121 --- /dev/null +++ b/scripts/platform.ts @@ -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 { + 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() \ No newline at end of file