feat: Init

This commit is contained in:
2024-12-16 20:56:14 +08:00
commit 8cc0770753
15 changed files with 10378 additions and 0 deletions

140
scripts/cmd.ts Normal file
View File

@@ -0,0 +1,140 @@
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({
homeUrl: z.string().url({
message: "URL格式不正确"
}),
appId: z.string().min(1, {
message: "应用ID不能为空"
}).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" })
}),
pushEmail: z.string().email({
message: "邮箱格式不正确"
})
});
type Config = z.infer<typeof ConfigSchema>;
async function main() {
intro('欢迎使用Cali配置工具');
const config = await group(
{
homeUrl: () => text({
message: '请输入主页URL',
validate(value) {
try {
new URL(value);
} catch {
return '请输入正确的网址';
}
},
}),
appId: () => text({
message: '请输入应用ID',
validate(value) {
if (!value.length) return '应用ID不能为空';
if (!/^[a-zA-Z0-9._-]+$/.test(value)) {
return '应用ID只能包含字母、数字、点、下划线和横线';
}
},
}),
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: [
{ value: 'release', label: 'release' },
{ value: 'debug', label: 'debug' },
],
}),
pushEmail: () => text({
message: '请输入推送邮箱',
validate(value) {
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
return '请输入正确的邮箱地址';
}
},
}),
},
{
onCancel: () => {
cancel('操作已取消');
process.exit(0);
},
}
);
// 检查是否有任何输入被取消
for (const [key, value] of Object.entries(config)) {
if (isCancel(value)) {
cancel('操作已取消');
process.exit(0);
}
}
try {
// 使用zod验证配置
const result = ConfigSchema.safeParse(config);
if (!result.success) {
cancel('配置验证失败');
result.error.errors.forEach(error => {
console.error(bgRed(` ${error.path.join('.')}: ${error.message} `));
});
process.exit(1);
}
await fs.writeFile(
path.join(process.cwd(), 'config.json'),
JSON.stringify(result.data, null, 2)
);
outro('配置文件保存成功!');
} catch (error) {
cancel('保存配置文件时出错');
if (error instanceof Error) {
console.error(bgRed(` ${error.message} `));
}
process.exit(1);
}
}
main().catch(error => {
cancel('发生意外错误');
if (error instanceof Error) {
console.error(bgRed(` ${error.message} `));
}
process.exit(1);
});

86
scripts/dev.ts Normal file
View File

@@ -0,0 +1,86 @@
/**
* @author Taoya
* @date 2022/5/23
* @Description: 构建开发环境包
*/
import * as fs from 'fs';
import { resolve } from 'path';
import { exec, ChildProcess } from 'child_process';
import chalk from 'chalk';
import logger from 'consola'
interface BuildResult {
success: boolean;
message: string;
}
async function buildAndroid(): Promise<BuildResult> {
return new Promise((resolve) => {
const buildProcess = exec('npx cordova run android', (error, stdout, stderr) => {
if (error) {
resolve({
success: false,
message: error.message
});
return;
}
if (stdout.includes('BUILD SUCCESSFUL')) {
resolve({
success: true,
message: 'BUILD SUCCESSFUL'
});
} else {
resolve({
success: false,
message: stderr || '构建失败'
});
}
});
// 输出构建日志
buildProcess.stdout?.on('data', (data: string) => {
console.log(chalk.green(data));
});
});
}
async function main(): Promise<void> {
try {
const packageName: string = 'dev.apk';
const distPath: string = './dist';
const outputPath: string = './platforms/android/app/build/outputs/apk/debug/app-debug.apk';
// 检查dist目录是否存在不存在则创建
if (!fs.existsSync(distPath)) {
fs.mkdirSync(distPath, { recursive: true });
}
// 构建android应用
const buildResult = await buildAndroid();
if (buildResult.success) {
logger.log('info', '构建成功');
// 如果目标文件已存在则删除
if (fs.existsSync(`${distPath}/${packageName}`)) {
fs.unlinkSync(`${distPath}/${packageName}`);
}
// 移动构建产物
fs.renameSync(outputPath, `${distPath}/${packageName}`);
} else {
logger.log('error', `构建失败: ${buildResult.message}`);
process.exit(1);
}
} catch (error) {
logger.log('error', `执行失败: ${error instanceof Error ? error.message : '未知错误'}`);
process.exit(1);
}
}
// 执行主函数
main().catch(error => {
logger.log('error', `意外错误: ${error instanceof Error ? error.message : '未知错误'}`);
process.exit(1);
});

146
scripts/logo.ts Normal file
View File

@@ -0,0 +1,146 @@
import * as fs from 'fs';
import { resolve } from 'path';
import { execSync } from 'child_process';
import * as path from 'path';
import consola from 'consola';
import axios from 'axios';
import { URL } from 'url';
// 错误信息常量
const ErrorMessages = {
NO_SOURCE: '请提供源文件路径或URL',
INVALID_FILE_TYPE: '无效的文件类型,仅支持 PNG、JPG、JPEG 和 GIF',
FILE_NOT_EXIST: '源文件不存在',
DOWNLOAD_FAILED: '文件下载失败:',
COPY_FAILED: '文件复制失败:',
UNEXPECTED_ERROR: '发生意外错误:',
};
/**
* 从URL下载文件到指定目录
* @param url 文件的URL地址
* @param destination 目标保存路径
*/
async function downloadFile(url: string, destination: string): Promise<void> {
try {
const response = await axios({
url,
method: 'GET',
responseType: 'stream'
});
const writer = fs.createWriteStream(destination);
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on('finish', resolve);
writer.on('error', reject);
});
} catch (error) {
throw new Error(`${ErrorMessages.DOWNLOAD_FAILED} ${error.message}`);
}
}
/**
* 复制文件到指定目录
* @param source 源文件路径
* @param destination 目标路径
*/
function copy(source: string, destination: string): void {
try {
execSync(`cp ${source} ${destination}`);
} catch (error) {
throw new Error(`${ErrorMessages.COPY_FAILED} ${error.message}`);
}
}
/**
* 验证字符串是否为有效URL
* @param string 待验证的字符串
* @returns boolean 是否为有效URL
*/
function isValidUrl(string: string): boolean {
try {
new URL(string);
return true;
} catch {
return false;
}
}
/**
* 验证文件是否为支持的图片类型
* @param filePath 文件路径
* @returns boolean 是否为支持的图片类型
*/
function isValidImageFile(filePath: string): boolean {
const validExtensions = ['.png', '.jpg', '.jpeg', '.gif'];
const ext = path.extname(filePath).toLowerCase();
return validExtensions.includes(ext);
}
/**
* 主函数 - 处理Logo替换逻辑
*/
async function main(): Promise<void> {
try {
// 获取命令行参数中的源文件路径
const sourcePath = process.argv[2];
if (!sourcePath) {
throw new Error(ErrorMessages.NO_SOURCE);
}
const baseDir: string = resolve('./');
const destinationPath = './res/icon/android/logo.png';
// 打印初始信息
consola.info('开始替换Logo');
consola.info('当前目录: ' + baseDir);
consola.info('源文件: ' + sourcePath);
// 验证文件类型
if (!isValidImageFile(sourcePath)) {
throw new Error(ErrorMessages.INVALID_FILE_TYPE);
}
// 根据源文件类型处理URL或本地文件
if (isValidUrl(sourcePath)) {
consola.info('正在从URL下载文件...');
const tempFile = path.join(baseDir, 'temp-logo' + path.extname(sourcePath));
try {
// 下载并替换文件
await downloadFile(sourcePath, tempFile);
copy(tempFile, destinationPath);
consola.success('Logo替换成功');
} finally {
// 清理临时文件
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
}
} else {
// 处理本地文件
const localPath = path.resolve(sourcePath);
if (!fs.existsSync(localPath)) {
throw new Error(ErrorMessages.FILE_NOT_EXIST);
}
// 复制文件
copy(localPath, destinationPath);
consola.success('Logo替换成功');
}
} catch (error) {
consola.error('Logo替换失败:', error.message);
process.exit(1);
}
}
// 执行主函数
main().catch(error => {
consola.error(ErrorMessages.UNEXPECTED_ERROR, error);
process.exit(1);
});