feat: Init
This commit is contained in:
140
scripts/cmd.ts
Normal file
140
scripts/cmd.ts
Normal 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
86
scripts/dev.ts
Normal 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
146
scripts/logo.ts
Normal 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);
|
||||
});
|
Reference in New Issue
Block a user