【新增】私有证书

This commit is contained in:
cai
2025-09-03 15:15:59 +08:00
parent efd052a297
commit 954cd1638d
442 changed files with 76787 additions and 7483 deletions

View File

@@ -0,0 +1,403 @@
import { src, dest, TaskFunction } from 'gulp';
import gulpZip from 'gulp-zip';
import { CompressConfig, TaskResult } from '../types.js';
import chalk from 'chalk';
import through2 from 'through2';
import archiver from 'archiver';
import fs from 'fs';
import path from 'path';
/**
* 创建 ZIP 压缩任务
* @param config 压缩配置
* @returns Gulp 任务函数
*/
export function createZipTask(config: CompressConfig): TaskFunction {
return function zipFiles(cb) {
let fileCount = 0;
console.log(chalk.blue('📦 开始创建 ZIP 压缩包...'));
console.log(chalk.gray(`源路径: ${Array.isArray(config.src) ? config.src.join(', ') : config.src}`));
console.log(chalk.gray(`压缩包: ${config.filename}`));
console.log(chalk.gray(`输出目录: ${config.dest}`));
const stream = src(config.src, { allowEmpty: true })
.pipe(through2.obj(function(file, enc, callback) {
fileCount++;
console.log(chalk.yellow(`添加文件: ${file.relative}`));
this.push(file);
callback();
}))
.pipe(gulpZip(config.filename))
.pipe(dest(config.dest));
stream.on('end', () => {
const zipPath = path.join(config.dest, config.filename);
const stats = fs.statSync(zipPath);
const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
console.log(chalk.green(`✅ ZIP 压缩完成`));
console.log(chalk.gray(` - 文件数量: ${fileCount}`));
console.log(chalk.gray(` - 压缩包大小: ${fileSizeMB} MB`));
console.log(chalk.gray(` - 保存路径: ${zipPath}`));
cb();
});
stream.on('error', (error) => {
console.error(chalk.red('❌ ZIP 压缩失败:'), error);
cb(error);
});
return stream;
};
}
/**
* 创建自定义压缩任务(支持 tar, gzip 等)
* @param config 压缩配置
* @returns Gulp 任务函数
*/
export function createCustomCompressTask(config: CompressConfig): TaskFunction {
return function compressFiles(cb) {
let fileCount = 0;
console.log(chalk.blue(`📦 开始创建 ${config.type?.toUpperCase()} 压缩包...`));
console.log(chalk.gray(`源路径: ${Array.isArray(config.src) ? config.src.join(', ') : config.src}`));
console.log(chalk.gray(`压缩包: ${config.filename}`));
console.log(chalk.gray(`输出目录: ${config.dest}`));
const outputPath = path.join(config.dest, config.filename);
const output = fs.createWriteStream(outputPath);
let archive: archiver.Archiver;
switch (config.type) {
case 'tar':
archive = archiver('tar', {
gzip: false
});
break;
case 'gzip':
archive = archiver('tar', {
gzip: true,
gzipOptions: {
level: config.level || 6
}
});
break;
default:
archive = archiver('zip', {
zlib: { level: config.level || 6 }
});
}
// 监听错误事件
archive.on('error', (err) => {
console.error(chalk.red('❌ 压缩过程中出错:'), err);
cb(err);
});
// 监听警告事件
archive.on('warning', (err) => {
console.warn(chalk.yellow('⚠️ 压缩警告:'), err);
});
// 监听完成事件
output.on('close', () => {
const stats = fs.statSync(outputPath);
const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
console.log(chalk.green(`${config.type?.toUpperCase()} 压缩完成`));
console.log(chalk.gray(` - 文件数量: ${fileCount}`));
console.log(chalk.gray(` - 压缩包大小: ${fileSizeMB} MB`));
console.log(chalk.gray(` - 保存路径: ${outputPath}`));
cb();
});
// 将压缩包管道到输出流
archive.pipe(output);
// 创建 Gulp 流处理文件
const stream = src(config.src, { allowEmpty: true })
.pipe(through2.obj(function(file, enc, callback) {
if (!file.isBuffer()) {
callback();
return;
}
fileCount++;
console.log(chalk.yellow(`添加文件: ${file.relative}`));
// 将文件添加到压缩包
archive.append(file.contents, { name: file.relative });
callback();
}));
stream.on('end', () => {
// 完成压缩
archive.finalize();
});
stream.on('error', (error) => {
console.error(chalk.red('❌ 文件处理失败:'), error);
cb(error);
});
return stream;
};
}
/**
* 创建压缩任务(根据配置自动选择压缩方式)
* @param config 压缩配置
* @returns Gulp 任务函数
*/
export function createCompressTask(config: CompressConfig): TaskFunction {
if (!config.type || config.type === 'zip') {
return createZipTask(config);
} else {
return createCustomCompressTask(config);
}
}
/**
* 批量压缩文件
* @param configs 压缩配置数组
* @returns Promise<TaskResult[]>
*/
export async function batchCompress(configs: CompressConfig[]): Promise<TaskResult[]> {
const results: TaskResult[] = [];
for (const config of configs) {
try {
await new Promise<void>((resolve, reject) => {
const task = createCompressTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
results.push({
success: true,
message: `文件压缩成功: ${path.join(config.dest, config.filename)}`,
fileCount: 1
});
} catch (error) {
results.push({
success: false,
error: error as Error,
message: `文件压缩失败: ${path.join(config.dest, config.filename)}`
});
}
}
return results;
}
/**
* 并行压缩文件
* @param configs 压缩配置数组
* @returns Promise<TaskResult[]>
*/
export async function parallelCompress(configs: CompressConfig[]): Promise<TaskResult[]> {
console.log(chalk.blue(`📦 开始并行压缩 ${configs.length} 个文件包...`));
const promises = configs.map(async (config, index) => {
try {
await new Promise<void>((resolve, reject) => {
const task = createCompressTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
return {
success: true,
message: `压缩成功 [${index + 1}]: ${path.join(config.dest, config.filename)}`,
fileCount: 1
} as TaskResult;
} catch (error) {
return {
success: false,
error: error as Error,
message: `压缩失败 [${index + 1}]: ${path.join(config.dest, config.filename)}`
} as TaskResult;
}
});
const results = await Promise.all(promises);
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
console.log(chalk.green(`✅ 并行压缩完成: ${successCount} 成功, ${failureCount} 失败`));
return results;
}
/**
* 创建多压缩任务
* @param configs 压缩配置数组
* @param parallel 是否并行执行
* @returns Gulp 任务函数
*/
export function createMultiCompressTask(configs: CompressConfig[], parallel: boolean = false): TaskFunction {
return async function multiCompress(cb) {
try {
console.log(chalk.blue(`📦 开始${parallel ? '并行' : '串行'}压缩 ${configs.length} 个文件包...`));
// 验证所有配置
for (const config of configs) {
const errors = compressHelpers.validateConfig(config);
if (errors.length > 0) {
throw new Error(`压缩配置错误 (${config.filename}): ${errors.join(', ')}`);
}
}
let results: TaskResult[];
if (parallel) {
results = await parallelCompress(configs);
} else {
results = await batchCompress(configs);
}
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
// 计算总文件大小
let totalSize = 0;
for (const config of configs) {
try {
const filePath = path.join(config.dest, config.filename);
if (fs.existsSync(filePath)) {
const stats = fs.statSync(filePath);
totalSize += stats.size;
}
} catch (error) {
// 忽略获取文件大小失败的错误
}
}
const totalSizeMB = (totalSize / (1024 * 1024)).toFixed(2);
if (failureCount > 0) {
console.log(chalk.yellow(`⚠️ 部分压缩失败:`));
results.filter(r => !r.success).forEach(result => {
console.log(chalk.red(` - ${result.message}`));
});
}
console.log(chalk.green(`✅ 多压缩任务完成: ${successCount} 成功, ${failureCount} 失败`));
console.log(chalk.gray(` - 总压缩包大小: ${totalSizeMB} MB`));
if (failureCount > 0 && successCount === 0) {
cb(new Error('所有压缩任务都失败了'));
} else {
cb();
}
} catch (error) {
console.error(chalk.red('❌ 多压缩任务执行失败:'), error);
cb(error);
}
};
}
/**
* 压缩工具函数
*/
export const compressHelpers = {
/**
* 创建压缩配置
* @param src 源文件路径
* @param filename 压缩包文件名
* @param dest 输出目录
* @param options 其他选项
*/
createConfig: (
src: string | string[],
filename: string,
dest: string,
options: Partial<CompressConfig> = {}
): CompressConfig => {
return {
src,
filename,
dest,
type: 'zip',
level: 6,
...options
};
},
/**
* 根据文件扩展名推断压缩类型
* @param filename 文件名
*/
inferType: (filename: string): CompressConfig['type'] => {
const ext = path.extname(filename).toLowerCase();
switch (ext) {
case '.zip':
return 'zip';
case '.tar':
return 'tar';
case '.gz':
case '.tgz':
return 'gzip';
default:
return 'zip';
}
},
/**
* 生成带时间戳的文件名
* @param basename 基础文件名
* @param extension 扩展名
*/
timestampedFilename: (basename: string, extension: string = 'zip'): string => {
const timestamp = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-');
return `${basename}-${timestamp}.${extension}`;
},
/**
* 获取推荐的压缩级别
* @param priority 优先级 ('speed' | 'size' | 'balanced')
*/
getCompressionLevel: (priority: 'speed' | 'size' | 'balanced' = 'balanced'): number => {
switch (priority) {
case 'speed':
return 1;
case 'size':
return 9;
case 'balanced':
default:
return 6;
}
},
/**
* 验证压缩配置
* @param config 压缩配置
*/
validateConfig: (config: CompressConfig): string[] => {
const errors: string[] = [];
if (!config.src) errors.push('缺少源文件路径');
if (!config.filename) errors.push('缺少压缩包文件名');
if (!config.dest) errors.push('缺少输出目录');
if (config.level && (config.level < 0 || config.level > 9)) {
errors.push('压缩级别必须在 0-9 之间');
}
return errors;
}
};

View File

@@ -0,0 +1,433 @@
import { TaskFunction } from 'gulp';
import { GitConfig, TaskResult } from '../types.js';
import chalk from 'chalk';
import { simpleGit, SimpleGit } from 'simple-git';
import path from 'path';
/**
* 创建 Git 操作任务
* @param config Git 配置
* @returns Gulp 任务函数
*/
export function createGitTask(config: GitConfig): TaskFunction {
return async function gitOperation(cb) {
try {
const git: SimpleGit = simpleGit({
baseDir: config.repoPath || process.cwd(),
binary: 'git',
maxConcurrentProcesses: 6,
});
console.log(chalk.blue(`🔧 执行 Git 操作: ${config.action}`));
console.log(chalk.gray(`仓库路径: ${config.repoPath || process.cwd()}`));
switch (config.action) {
case 'commit':
await handleCommit(git, config);
break;
case 'pull':
await handlePull(git, config);
break;
case 'push':
await handlePush(git, config);
break;
case 'checkout':
await handleCheckout(git, config);
break;
case 'branch':
await handleBranch(git, config);
break;
case 'merge':
await handleMerge(git, config);
break;
default:
throw new Error(`不支持的 Git 操作: ${config.action}`);
}
console.log(chalk.green(`✅ Git 操作完成: ${config.action}`));
cb();
} catch (error) {
console.error(chalk.red(`❌ Git 操作失败: ${config.action}`), error);
cb(error);
}
};
}
/**
* 处理提交操作
*/
async function handleCommit(git: SimpleGit, config: GitConfig) {
console.log(chalk.yellow('📝 准备提交代码...'));
// 检查是否有未提交的更改
const status = await git.status();
if (status.files.length === 0) {
console.log(chalk.yellow('⚠️ 没有文件需要提交'));
return;
}
// 添加文件
if (config.files) {
if (Array.isArray(config.files)) {
for (const file of config.files) {
await git.add(file);
console.log(chalk.cyan(`添加文件: ${file}`));
}
} else {
await git.add(config.files);
console.log(chalk.cyan(`添加文件: ${config.files}`));
}
} else {
await git.add('.');
console.log(chalk.cyan('添加所有更改的文件'));
}
// 提交
const message = config.message || `自动提交 - ${new Date().toLocaleString()}`;
await git.commit(message);
console.log(chalk.green(`✅ 提交完成: ${message}`));
// 显示提交信息
const log = await git.log({ maxCount: 1 });
if (log.latest) {
console.log(chalk.gray(`提交哈希: ${log.latest.hash}`));
console.log(chalk.gray(`提交时间: ${log.latest.date}`));
}
}
/**
* 处理拉取操作
*/
async function handlePull(git: SimpleGit, config: GitConfig) {
console.log(chalk.yellow('⬇️ 拉取远程代码...'));
const remote = config.remote || 'origin';
const branch = config.branch;
if (branch) {
await git.pull(remote, branch);
console.log(chalk.green(`✅ 拉取完成: ${remote}/${branch}`));
} else {
await git.pull();
console.log(chalk.green(`✅ 拉取完成`));
}
// 显示最新提交信息
const log = await git.log({ maxCount: 1 });
if (log.latest) {
console.log(chalk.gray(`最新提交: ${log.latest.message}`));
console.log(chalk.gray(`提交作者: ${log.latest.author_name}`));
}
}
/**
* 处理推送操作
*/
async function handlePush(git: SimpleGit, config: GitConfig) {
console.log(chalk.yellow('⬆️ 推送代码到远程...'));
const remote = config.remote || 'origin';
const branch = config.branch;
if (branch) {
await git.push(remote, branch);
console.log(chalk.green(`✅ 推送完成: ${remote}/${branch}`));
} else {
await git.push();
console.log(chalk.green(`✅ 推送完成`));
}
}
/**
* 处理分支切换操作
*/
async function handleCheckout(git: SimpleGit, config: GitConfig) {
if (!config.branch) {
throw new Error('切换分支需要指定分支名称');
}
console.log(chalk.yellow(`🔄 切换到分支: ${config.branch}`));
// 检查分支是否存在
const branches = await git.branch();
const branchExists = branches.all.includes(config.branch);
if (branchExists) {
await git.checkout(config.branch);
console.log(chalk.green(`✅ 切换到分支: ${config.branch}`));
} else {
// 创建并切换到新分支
await git.checkoutLocalBranch(config.branch);
console.log(chalk.green(`✅ 创建并切换到新分支: ${config.branch}`));
}
// 显示当前分支信息
const currentBranch = await git.branch();
console.log(chalk.gray(`当前分支: ${currentBranch.current}`));
}
/**
* 处理分支操作
*/
async function handleBranch(git: SimpleGit, config: GitConfig) {
if (!config.branch) {
// 列出所有分支
console.log(chalk.yellow('📋 列出所有分支...'));
const branches = await git.branch();
console.log(chalk.green('本地分支:'));
Object.entries(branches.branches).forEach(([name, branch]) => {
const marker = name === branches.current ? ' * ' : ' ';
console.log(chalk.gray(`${marker}${name}`));
});
if (Object.keys(branches.branches).some(name => name.startsWith('remotes/'))) {
console.log(chalk.green('\n远程分支:'));
Object.keys(branches.branches)
.filter(name => name.startsWith('remotes/'))
.forEach(name => {
console.log(chalk.gray(` ${name}`));
});
}
} else {
// 创建新分支
console.log(chalk.yellow(`🌿 创建新分支: ${config.branch}`));
await git.checkoutLocalBranch(config.branch);
console.log(chalk.green(`✅ 分支创建完成: ${config.branch}`));
}
}
/**
* 处理合并操作
*/
async function handleMerge(git: SimpleGit, config: GitConfig) {
if (!config.branch) {
throw new Error('合并操作需要指定要合并的分支');
}
console.log(chalk.yellow(`🔀 合并分支: ${config.branch}`));
try {
await git.merge([config.branch]);
console.log(chalk.green(`✅ 分支合并完成: ${config.branch}`));
} catch (error: any) {
if (error.message.includes('CONFLICTS')) {
console.log(chalk.red('❌ 合并冲突,需要手动解决'));
// 显示冲突文件
const status = await git.status();
if (status.conflicted.length > 0) {
console.log(chalk.yellow('冲突文件:'));
status.conflicted.forEach(file => {
console.log(chalk.red(` - ${file}`));
});
}
throw error;
} else {
throw error;
}
}
}
/**
* 批量执行 Git 操作
* @param configs Git 配置数组
* @returns Promise<TaskResult[]>
*/
export async function batchGitOperation(configs: GitConfig[]): Promise<TaskResult[]> {
const results: TaskResult[] = [];
for (const config of configs) {
try {
await new Promise<void>((resolve, reject) => {
const task = createGitTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
results.push({
success: true,
message: `Git 操作成功: ${config.action}`,
fileCount: 1
});
} catch (error) {
results.push({
success: false,
error: error as Error,
message: `Git 操作失败: ${config.action}`
});
}
}
return results;
}
/**
* 并行执行 Git 操作
* @param configs Git 配置数组
* @returns Promise<TaskResult[]>
*/
export async function parallelGitOperation(configs: GitConfig[]): Promise<TaskResult[]> {
console.log(chalk.blue(`🔧 开始并行执行 ${configs.length} 个 Git 操作...`));
const promises = configs.map(async (config, index) => {
try {
await new Promise<void>((resolve, reject) => {
const task = createGitTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
return {
success: true,
message: `Git 操作成功 [${index + 1}]: ${config.action}`,
fileCount: 1
} as TaskResult;
} catch (error) {
return {
success: false,
error: error as Error,
message: `Git 操作失败 [${index + 1}]: ${config.action}`
} as TaskResult;
}
});
const results = await Promise.all(promises);
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
console.log(chalk.green(`✅ 并行 Git 操作完成: ${successCount} 成功, ${failureCount} 失败`));
return results;
}
/**
* 创建多 Git 操作任务
* @param configs Git 配置数组
* @param parallel 是否并行执行
* @returns Gulp 任务函数
*/
export function createMultiGitTask(configs: GitConfig[], parallel: boolean = false): TaskFunction {
return async function multiGitOperation(cb) {
try {
console.log(chalk.blue(`🔧 开始${parallel ? '并行' : '串行'}执行 ${configs.length} 个 Git 操作...`));
let results: TaskResult[];
if (parallel) {
results = await parallelGitOperation(configs);
} else {
results = await batchGitOperation(configs);
}
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
if (failureCount > 0) {
console.log(chalk.yellow(`⚠️ 部分操作失败:`));
results.filter(r => !r.success).forEach(result => {
console.log(chalk.red(` - ${result.message}`));
});
}
console.log(chalk.green(`✅ 多 Git 操作完成: ${successCount} 成功, ${failureCount} 失败`));
if (failureCount > 0 && successCount === 0) {
cb(new Error('所有 Git 操作都失败了'));
} else {
cb();
}
} catch (error) {
console.error(chalk.red('❌ 多 Git 操作执行失败:'), error);
cb(error);
}
};
}
/**
* Git 工具函数
*/
export const gitHelpers = {
/**
* 检查 Git 仓库状态
* @param repoPath 仓库路径
*/
checkStatus: async (repoPath?: string) => {
const git = simpleGit(repoPath || process.cwd());
const status = await git.status();
return {
clean: status.files.length === 0,
ahead: status.ahead,
behind: status.behind,
modified: status.modified,
staged: status.staged,
deleted: status.deleted,
created: status.created,
conflicted: status.conflicted
};
},
/**
* 获取当前分支名
* @param repoPath 仓库路径
*/
getCurrentBranch: async (repoPath?: string): Promise<string> => {
const git = simpleGit(repoPath || process.cwd());
const branch = await git.branch();
return branch.current;
},
/**
* 检查是否有未提交的更改
* @param repoPath 仓库路径
*/
hasUncommittedChanges: async (repoPath?: string): Promise<boolean> => {
const git = simpleGit(repoPath || process.cwd());
const status = await git.status();
return status.files.length > 0;
},
/**
* 获取最新提交信息
* @param repoPath 仓库路径
*/
getLatestCommit: async (repoPath?: string) => {
const git = simpleGit(repoPath || process.cwd());
const log = await git.log({ maxCount: 1 });
return log.latest;
},
/**
* 验证 Git 配置
* @param config Git 配置
*/
validateConfig: (config: GitConfig): string[] => {
const errors: string[] = [];
if (!config.action) {
errors.push('缺少 Git 操作类型');
}
if (config.action === 'commit' && !config.message && !config.files) {
errors.push('提交操作需要指定提交信息或文件');
}
if (['checkout', 'merge'].includes(config.action) && !config.branch) {
errors.push(`${config.action} 操作需要指定分支名称`);
}
return errors;
}
};

View File

@@ -0,0 +1,144 @@
import { src, dest, TaskFunction } from 'gulp';
import gulpRename from 'gulp-rename';
import { RenameConfig, TaskResult } from '../types.js';
import chalk from 'chalk';
import through2 from 'through2';
/**
* 创建文件重命名任务
* @param config 重命名配置
* @returns Gulp 任务函数
*/
export function createRenameTask(config: RenameConfig): TaskFunction {
return function renameFiles(cb) {
let fileCount = 0;
console.log(chalk.blue('🔄 开始重命名文件...'));
console.log(chalk.gray(`源路径: ${Array.isArray(config.src) ? config.src.join(', ') : config.src}`));
console.log(chalk.gray(`目标路径: ${config.dest}`));
const stream = src(config.src, { allowEmpty: true })
.pipe(through2.obj(function(file, enc, callback) {
fileCount++;
console.log(chalk.yellow(`处理文件: ${file.relative}`));
this.push(file);
callback();
}))
.pipe(gulpRename(config.rename))
.pipe(through2.obj(function(file, enc, callback) {
console.log(chalk.green(`重命名为: ${file.relative}`));
this.push(file);
callback();
}))
.pipe(dest(config.dest));
stream.on('end', () => {
console.log(chalk.green(`✅ 文件重命名完成,共处理 ${fileCount} 个文件`));
cb();
});
stream.on('error', (error) => {
console.error(chalk.red('❌ 文件重命名失败:'), error);
cb(error);
});
return stream;
};
}
/**
* 批量重命名文件
* @param configs 重命名配置数组
* @returns Promise<TaskResult[]>
*/
export async function batchRename(configs: RenameConfig[]): Promise<TaskResult[]> {
const results: TaskResult[] = [];
for (const config of configs) {
try {
await new Promise<void>((resolve, reject) => {
const task = createRenameTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
results.push({
success: true,
message: `文件重命名成功: ${config.dest}`
});
} catch (error) {
results.push({
success: false,
error: error as Error,
message: `文件重命名失败: ${config.dest}`
});
}
}
return results;
}
/**
* 创建常用的重命名函数
*/
export const renameHelpers = {
/**
* 添加前缀
* @param prefix 前缀
*/
addPrefix: (prefix: string) => (path: any) => {
path.basename = prefix + path.basename;
},
/**
* 添加后缀
* @param suffix 后缀
*/
addSuffix: (suffix: string) => (path: any) => {
path.basename = path.basename + suffix;
},
/**
* 更改扩展名
* @param ext 新扩展名(包含点)
*/
changeExtension: (ext: string) => (path: any) => {
path.extname = ext;
},
/**
* 添加时间戳
*/
addTimestamp: () => (path: any) => {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
path.basename = `${path.basename}-${timestamp}`;
},
/**
* 转换为小写
*/
toLowerCase: () => (path: any) => {
path.basename = path.basename.toLowerCase();
},
/**
* 转换为大写
*/
toUpperCase: () => (path: any) => {
path.basename = path.basename.toUpperCase();
},
/**
* 替换文件名中的字符
* @param search 要替换的字符串或正则
* @param replace 替换为的字符串
*/
replaceInName: (search: string | RegExp, replace: string) => (path: any) => {
path.basename = path.basename.replace(search, replace);
}
};

View File

@@ -0,0 +1,185 @@
import { src, dest, TaskFunction } from 'gulp';
import gulpReplace from 'gulp-replace';
import { ReplaceConfig, TaskResult } from '../types.js';
import chalk from 'chalk';
import through2 from 'through2';
import { Transform } from 'stream';
/**
* 创建文件内容替换任务
* @param config 替换配置
* @returns Gulp 任务函数
*/
export function createReplaceTask(config: ReplaceConfig): TaskFunction {
return function replaceContent(cb) {
let fileCount = 0;
let replaceCount = 0;
console.log(chalk.blue('🔄 开始替换文件内容...'));
console.log(chalk.gray(`源路径: ${Array.isArray(config.src) ? config.src.join(', ') : config.src}`));
console.log(chalk.gray(`目标路径: ${config.dest}`));
console.log(chalk.gray(`替换规则数量: ${config.replacements.length}`));
let stream: any = src(config.src, { allowEmpty: true })
.pipe(through2.obj(function(file, enc, callback) {
fileCount++;
console.log(chalk.yellow(`处理文件: ${file.relative}`));
this.push(file);
callback();
}));
// 应用所有替换规则
for (const replacement of config.replacements) {
const { search, replace } = replacement;
stream = (stream as any).pipe(gulpReplace(search, (match, ...args) => {
replaceCount++;
console.log(chalk.cyan(`替换内容: ${match.substring(0, 50)}${match.length > 50 ? '...' : ''}`));
if (typeof replace === 'function') {
return replace(match, ...args);
}
return replace;
}));
}
stream = (stream as any).pipe(dest(config.dest));
stream.on('end', () => {
console.log(chalk.green(`✅ 内容替换完成,共处理 ${fileCount} 个文件,执行 ${replaceCount} 次替换`));
cb();
});
stream.on('error', (error) => {
console.error(chalk.red('❌ 内容替换失败:'), error);
cb(error);
});
return stream;
};
}
/**
* 批量替换文件内容
* @param configs 替换配置数组
* @returns Promise<TaskResult[]>
*/
export async function batchReplace(configs: ReplaceConfig[]): Promise<TaskResult[]> {
const results: TaskResult[] = [];
for (const config of configs) {
try {
await new Promise<void>((resolve, reject) => {
const task = createReplaceTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
results.push({
success: true,
message: `内容替换成功: ${config.dest}`
});
} catch (error) {
results.push({
success: false,
error: error as Error,
message: `内容替换失败: ${config.dest}`
});
}
}
return results;
}
/**
* 常用替换模式
*/
export const replacePatterns = {
/**
* 替换版本号
* @param newVersion 新版本号
*/
version: (newVersion: string) => ({
search: /"version"\s*:\s*"[^"]+"/g,
replace: `"version": "${newVersion}"`
}),
/**
* 替换 API 基础 URL
* @param newBaseUrl 新的基础 URL
*/
apiBaseUrl: (newBaseUrl: string) => ({
search: /const\s+API_BASE_URL\s*=\s*['"][^'"]+['"]/g,
replace: `const API_BASE_URL = '${newBaseUrl}'`
}),
/**
* 替换环境变量
* @param envVar 环境变量名
* @param value 新值
*/
envVariable: (envVar: string, value: string) => ({
search: new RegExp(`${envVar}\\s*=\\s*[^\\n\\r]+`, 'g'),
replace: `${envVar}=${value}`
}),
/**
* 替换 HTML 中的标题
* @param newTitle 新标题
*/
htmlTitle: (newTitle: string) => ({
search: /<title>[^<]*<\/title>/gi,
replace: `<title>${newTitle}</title>`
}),
/**
* 替换注释中的版权信息
* @param newCopyright 新版权信息
*/
copyright: (newCopyright: string) => ({
search: /\/\*\*[\s\S]*?Copyright[\s\S]*?\*\//g,
replace: `/**\n * ${newCopyright}\n */`
}),
/**
* 替换时间戳
*/
timestamp: () => ({
search: /\{\{TIMESTAMP\}\}/g,
replace: new Date().toISOString()
}),
/**
* 替换构建号
* @param buildNumber 构建号
*/
buildNumber: (buildNumber: string) => ({
search: /BUILD_NUMBER\s*=\s*['"][^'"]*['"]/g,
replace: `BUILD_NUMBER = '${buildNumber}'`
}),
/**
* 替换 CSS 中的颜色值
* @param oldColor 旧颜色值
* @param newColor 新颜色值
*/
cssColor: (oldColor: string, newColor: string) => ({
search: new RegExp(oldColor.replace('#', '\\#'), 'gi'),
replace: newColor
}),
/**
* 替换 JavaScript 中的配置对象
* @param configKey 配置键名
* @param newValue 新值
*/
jsConfig: (configKey: string, newValue: any) => ({
search: new RegExp(`${configKey}\\s*:\\s*[^,}]+`, 'g'),
replace: `${configKey}: ${JSON.stringify(newValue)}`
})
};

View File

@@ -0,0 +1,448 @@
import { TaskFunction } from 'gulp';
import { SSHConfig, TaskResult } from '../types.js';
import chalk from 'chalk';
import { Client } from 'ssh2';
import fs from 'fs';
/**
* 创建 SSH 命令执行任务
* @param config SSH 配置
* @returns Gulp 任务函数
*/
export function createSSHTask(config: SSHConfig): TaskFunction {
return function sshExecution(cb) {
const conn = new Client();
let commandCount = 0;
const commands = Array.isArray(config.commands) ? config.commands : [config.commands];
console.log(chalk.blue('🔗 开始 SSH 连接...'));
console.log(chalk.gray(`服务器: ${config.host}:${config.port || 22}`));
console.log(chalk.gray(`用户: ${config.username}`));
console.log(chalk.gray(`命令数量: ${commands.length}`));
conn.on('ready', async () => {
console.log(chalk.green('✅ SSH 连接成功'));
try {
for (const command of commands) {
await executeCommand(conn, command, config.verbose || false);
commandCount++;
}
console.log(chalk.green(`✅ SSH 命令执行完成,共执行 ${commandCount} 个命令`));
conn.end();
cb();
} catch (error) {
console.error(chalk.red('❌ SSH 命令执行失败:'), error);
conn.end();
cb(error);
}
});
conn.on('error', (error) => {
console.error(chalk.red('❌ SSH 连接失败:'), error);
cb(error);
});
conn.on('end', () => {
console.log(chalk.blue('🔌 SSH 连接已断开'));
});
// 准备连接配置
const connectConfig: any = {
host: config.host,
port: config.port || 22,
username: config.username,
};
if (config.password) {
connectConfig.password = config.password;
} else if (config.privateKey) {
try {
connectConfig.privateKey = fs.readFileSync(config.privateKey);
} catch (error) {
console.error(chalk.red('❌ 读取私钥文件失败:'), error);
cb(error);
return;
}
} else {
console.error(chalk.red('❌ 缺少密码或私钥'));
cb(new Error('缺少密码或私钥'));
return;
}
// 建立连接
conn.connect(connectConfig);
};
}
/**
* 执行单个命令
* @param conn SSH 连接
* @param command 要执行的命令
* @param verbose 是否显示详细输出
*/
function executeCommand(conn: Client, command: string, verbose: boolean): Promise<{ stdout: string; stderr: string; code: number }> {
return new Promise((resolve, reject) => {
console.log(chalk.yellow(`📝 执行命令: ${command}`));
conn.exec(command, (err, stream) => {
if (err) {
reject(err);
return;
}
let stdout = '';
let stderr = '';
let code = 0;
stream.on('close', (exitCode: number, signal: string) => {
code = exitCode;
if (exitCode === 0) {
console.log(chalk.green(`✅ 命令执行成功 (退出码: ${exitCode})`));
} else {
console.log(chalk.red(`❌ 命令执行失败 (退出码: ${exitCode})`));
}
if (signal) {
console.log(chalk.yellow(`收到信号: ${signal}`));
}
resolve({ stdout, stderr, code });
});
stream.on('data', (data: Buffer) => {
const output = data.toString();
stdout += output;
if (verbose) {
console.log(chalk.gray('[输出]'), output.trim());
}
});
stream.stderr.on('data', (data: Buffer) => {
const output = data.toString();
stderr += output;
if (verbose) {
console.log(chalk.red('[错误]'), output.trim());
}
});
stream.on('error', (error: Error) => {
reject(error);
});
});
});
}
/**
* 批量执行 SSH 命令
* @param configs SSH 配置数组
* @returns Promise<TaskResult[]>
*/
export async function batchSSHExecution(configs: SSHConfig[]): Promise<TaskResult[]> {
const results: TaskResult[] = [];
for (const config of configs) {
try {
await new Promise<void>((resolve, reject) => {
const task = createSSHTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
results.push({
success: true,
message: `SSH 命令执行成功: ${config.host}`,
fileCount: Array.isArray(config.commands) ? config.commands.length : 1
});
} catch (error) {
results.push({
success: false,
error: error as Error,
message: `SSH 命令执行失败: ${config.host}`
});
}
}
return results;
}
/**
* 并行执行 SSH 命令
* @param configs SSH 配置数组
* @returns Promise<TaskResult[]>
*/
export async function parallelSSHExecution(configs: SSHConfig[]): Promise<TaskResult[]> {
console.log(chalk.blue(`🔗 开始并行连接 ${configs.length} 个服务器...`));
const promises = configs.map(async (config, index) => {
try {
await new Promise<void>((resolve, reject) => {
const task = createSSHTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
return {
success: true,
message: `SSH 执行成功 [${index + 1}]: ${config.host}`,
fileCount: Array.isArray(config.commands) ? config.commands.length : 1
} as TaskResult;
} catch (error) {
return {
success: false,
error: error as Error,
message: `SSH 执行失败 [${index + 1}]: ${config.host}`
} as TaskResult;
}
});
const results = await Promise.all(promises);
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
console.log(chalk.green(`✅ 并行 SSH 执行完成: ${successCount} 成功, ${failureCount} 失败`));
return results;
}
/**
* 创建多 SSH 任务
* @param configs SSH 配置数组
* @param parallel 是否并行执行
* @returns Gulp 任务函数
*/
export function createMultiSSHTask(configs: SSHConfig[], parallel: boolean = false): TaskFunction {
return async function multiSSHExecution(cb) {
try {
console.log(chalk.blue(`🔗 开始${parallel ? '并行' : '串行'}执行 ${configs.length} 个 SSH 任务...`));
// 验证所有配置
for (const config of configs) {
const errors = sshHelpers.validateConfig(config);
if (errors.length > 0) {
throw new Error(`SSH 配置错误 (${config.host}): ${errors.join(', ')}`);
}
}
let results: TaskResult[];
if (parallel) {
results = await parallelSSHExecution(configs);
} else {
results = await batchSSHExecution(configs);
}
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
const totalCommands = results.reduce((sum, r) => sum + (r.fileCount || 0), 0);
if (failureCount > 0) {
console.log(chalk.yellow(`⚠️ 部分 SSH 执行失败:`));
results.filter(r => !r.success).forEach(result => {
console.log(chalk.red(` - ${result.message}`));
});
}
console.log(chalk.green(`✅ 多 SSH 任务完成: ${successCount} 服务器成功, ${failureCount} 失败, 共执行 ${totalCommands} 个命令`));
if (failureCount > 0 && successCount === 0) {
cb(new Error('所有 SSH 任务都失败了'));
} else {
cb();
}
} catch (error) {
console.error(chalk.red('❌ 多 SSH 任务执行失败:'), error);
cb(error);
}
};
}
/**
* SSH 工具函数
*/
export const sshHelpers = {
/**
* 测试 SSH 连接
* @param config SSH 配置(不包含命令)
*/
testConnection: (config: Omit<SSHConfig, 'commands'>): Promise<boolean> => {
return new Promise((resolve) => {
const conn = new Client();
conn.on('ready', () => {
console.log(chalk.green('✅ SSH 连接测试成功'));
conn.end();
resolve(true);
});
conn.on('error', (error) => {
console.error(chalk.red('❌ SSH 连接测试失败:'), error);
resolve(false);
});
const connectConfig: any = {
host: config.host,
port: config.port || 22,
username: config.username,
};
if (config.password) {
connectConfig.password = config.password;
} else if (config.privateKey) {
try {
connectConfig.privateKey = fs.readFileSync(config.privateKey);
} catch (error) {
console.error(chalk.red('❌ 读取私钥文件失败:'), error);
resolve(false);
return;
}
}
conn.connect(connectConfig);
});
},
/**
* 执行单个命令并返回结果
* @param config SSH 配置
* @param command 单个命令
*/
executeCommand: async (config: Omit<SSHConfig, 'commands'>, command: string): Promise<{ stdout: string; stderr: string; code: number }> => {
return new Promise((resolve, reject) => {
const conn = new Client();
conn.on('ready', () => {
executeCommand(conn, command, config.verbose || false)
.then(result => {
conn.end();
resolve(result);
})
.catch(error => {
conn.end();
reject(error);
});
});
conn.on('error', (error) => {
reject(error);
});
const connectConfig: any = {
host: config.host,
port: config.port || 22,
username: config.username,
};
if (config.password) {
connectConfig.password = config.password;
} else if (config.privateKey) {
try {
connectConfig.privateKey = fs.readFileSync(config.privateKey);
} catch (error) {
reject(error);
return;
}
}
conn.connect(connectConfig);
});
},
/**
* 创建常用的命令模板
*/
commands: {
/**
* 重启服务
* @param serviceName 服务名称
*/
restartService: (serviceName: string) => `sudo systemctl restart ${serviceName}`,
/**
* 检查服务状态
* @param serviceName 服务名称
*/
checkService: (serviceName: string) => `sudo systemctl status ${serviceName}`,
/**
* 部署应用
* @param appPath 应用路径
*/
deployApp: (appPath: string) => [
`cd ${appPath}`,
'git pull origin main',
'npm install',
'npm run build',
'pm2 restart all'
],
/**
* 清理日志
* @param logPath 日志路径
* @param days 保留天数
*/
cleanLogs: (logPath: string, days: number = 7) =>
`find ${logPath} -name "*.log" -mtime +${days} -delete`,
/**
* 备份数据库
* @param dbName 数据库名
* @param backupPath 备份路径
*/
backupDatabase: (dbName: string, backupPath: string) =>
`mysqldump -u root -p ${dbName} > ${backupPath}/${dbName}_$(date +%Y%m%d_%H%M%S).sql`,
/**
* 检查磁盘空间
*/
checkDiskSpace: () => 'df -h',
/**
* 检查内存使用
*/
checkMemory: () => 'free -h',
/**
* 检查 CPU 使用
*/
checkCPU: () => 'top -bn1 | grep "Cpu(s)"',
/**
* 检查进程
* @param processName 进程名称
*/
checkProcess: (processName: string) => `ps aux | grep ${processName}`
},
/**
* 验证 SSH 配置
* @param config SSH 配置
*/
validateConfig: (config: SSHConfig): string[] => {
const errors: string[] = [];
if (!config.host) errors.push('缺少服务器主机地址');
if (!config.username) errors.push('缺少用户名');
if (!config.password && !config.privateKey) errors.push('缺少密码或私钥');
if (!config.commands || (Array.isArray(config.commands) && config.commands.length === 0)) {
errors.push('缺少要执行的命令');
}
return errors;
}
};

View File

@@ -0,0 +1,418 @@
import { src, TaskFunction } from 'gulp';
import { UploadConfig, TaskResult } from '../types.js';
import chalk from 'chalk';
import through2 from 'through2';
import path from 'path';
import fs from 'fs';
// 动态导入以避免 CommonJS 模块问题
let SftpClient: any;
let ftp: any;
async function loadDependencies() {
if (!SftpClient) {
SftpClient = (await import('ssh2-sftp-client')).default;
}
if (!ftp) {
ftp = await import('basic-ftp');
}
}
/**
* 创建 SFTP 上传任务
* @param config 上传配置
* @returns Gulp 任务函数
*/
export function createSftpUploadTask(config: UploadConfig): TaskFunction {
return async function sftpUpload(cb) {
try {
await loadDependencies();
const sftp = new SftpClient();
let fileCount = 0;
console.log(chalk.blue('🚀 开始 SFTP 上传...'));
console.log(chalk.gray(`服务器: ${config.host}:${config.port || 22}`));
console.log(chalk.gray(`用户: ${config.username}`));
console.log(chalk.gray(`远程路径: ${config.remotePath}`));
// 连接 SFTP
const connectConfig: any = {
host: config.host,
port: config.port || 22,
username: config.username
};
if (config.password) {
connectConfig.password = config.password;
} else if (config.privateKey) {
connectConfig.privateKey = fs.readFileSync(config.privateKey);
}
await sftp.connect(connectConfig);
console.log(chalk.green('✅ SFTP 连接成功'));
// 确保远程目录存在
await sftp.mkdir(config.remotePath, true);
if (config.clean) {
console.log(chalk.yellow('🧹 清空远程目录...'));
await sftp.rmdir(config.remotePath, true);
await sftp.mkdir(config.remotePath, true);
}
// 上传文件
const uploadPromises: Promise<void>[] = [];
const stream = src(config.src, { allowEmpty: true })
.pipe(through2.obj(function(file, enc, callback) {
if (!file.isBuffer()) {
callback();
return;
}
fileCount++;
const relativePath = file.relative;
const remotePath = path.posix.join(config.remotePath, relativePath).replace(/\\/g, '/');
console.log(chalk.yellow(`上传文件: ${relativePath} -> ${remotePath}`));
const uploadPromise = (async () => {
try {
// 确保远程目录存在
const remoteDir = path.posix.dirname(remotePath);
await sftp.mkdir(remoteDir, true);
// 上传文件
await sftp.put(file.contents, remotePath);
console.log(chalk.green(`✅ 上传成功: ${relativePath}`));
} catch (error) {
console.error(chalk.red(`❌ 上传失败: ${relativePath}`), error);
throw error;
}
})();
if (config.parallel) {
uploadPromises.push(uploadPromise);
} else {
uploadPromise.then(() => callback()).catch(callback);
return;
}
callback();
}));
stream.on('end', async () => {
try {
if (config.parallel && uploadPromises.length > 0) {
await Promise.all(uploadPromises);
}
await sftp.end();
console.log(chalk.green(`✅ SFTP 上传完成,共上传 ${fileCount} 个文件`));
cb();
} catch (error) {
console.error(chalk.red('❌ SFTP 上传失败:'), error);
await sftp.end();
cb(error);
}
});
stream.on('error', async (error) => {
console.error(chalk.red('❌ SFTP 上传流错误:'), error);
await sftp.end();
cb(error);
});
return stream;
} catch (error) {
console.error(chalk.red('❌ SFTP 连接失败:'), error);
cb(error);
}
};
}
/**
* 创建 FTP 上传任务
* @param config 上传配置
* @returns Gulp 任务函数
*/
export function createFtpUploadTask(config: UploadConfig): TaskFunction {
return async function ftpUpload(cb) {
try {
await loadDependencies();
let fileCount = 0;
console.log(chalk.blue('🚀 开始 FTP 上传...'));
console.log(chalk.gray(`服务器: ${config.host}:${config.port || 21}`));
console.log(chalk.gray(`用户: ${config.username}`));
console.log(chalk.gray(`远程路径: ${config.remotePath}`));
const client = new ftp.Client();
try {
await client.access({
host: config.host,
port: config.port || 21,
user: config.username,
password: config.password
});
console.log(chalk.green('✅ FTP 连接成功'));
if (config.clean) {
console.log(chalk.yellow('🧹 清空远程目录...'));
try {
await client.removeDir(config.remotePath);
} catch (error) {
// 忽略删除失败的错误
}
}
// 确保远程目录存在
await client.ensureDir(config.remotePath);
const stream = src(config.src, { allowEmpty: true })
.pipe(through2.obj(async function(file, enc, callback) {
if (!file.isBuffer()) {
callback();
return;
}
fileCount++;
const relativePath = file.relative;
const remotePath = path.posix.join(config.remotePath, relativePath).replace(/\\/g, '/');
console.log(chalk.yellow(`上传文件: ${relativePath} -> ${remotePath}`));
try {
// 确保远程目录存在
const remoteDir = path.posix.dirname(remotePath);
await client.ensureDir(remoteDir);
// 上传文件
await client.uploadFrom(file.contents, remotePath);
console.log(chalk.green(`✅ 上传成功: ${relativePath}`));
} catch (error) {
console.error(chalk.red(`❌ 上传失败: ${relativePath}`), error);
callback(error);
return;
}
callback();
}));
stream.on('end', async () => {
try {
client.close();
console.log(chalk.green(`✅ FTP 上传完成,共上传 ${fileCount} 个文件`));
cb();
} catch (error) {
console.error(chalk.red('❌ FTP 上传失败:'), error);
cb(error);
}
});
stream.on('error', (error) => {
console.error(chalk.red('❌ FTP 上传流错误:'), error);
client.close();
cb(error);
});
return stream;
} catch (error) {
console.error(chalk.red('❌ FTP 连接失败:'), error);
cb(error);
}
} catch (error) {
console.error(chalk.red('❌ FTP 初始化失败:'), error);
cb(error);
}
};
}
/**
* 创建上传任务(根据配置自动选择 FTP 或 SFTP
* @param config 上传配置
* @returns Gulp 任务函数
*/
export function createUploadTask(config: UploadConfig): TaskFunction {
if (config.type === 'sftp') {
return createSftpUploadTask(config);
} else {
return createFtpUploadTask(config);
}
}
/**
* 批量上传文件
* @param configs 上传配置数组
* @returns Promise<TaskResult[]>
*/
export async function batchUpload(configs: UploadConfig[]): Promise<TaskResult[]> {
const results: TaskResult[] = [];
for (const config of configs) {
try {
await new Promise<void>((resolve, reject) => {
const task = createUploadTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
results.push({
success: true,
message: `文件上传成功: ${config.host}:${config.remotePath}`,
fileCount: 1
});
} catch (error) {
results.push({
success: false,
error: error as Error,
message: `文件上传失败: ${config.host}:${config.remotePath}`
});
}
}
return results;
}
/**
* 并行上传文件
* @param configs 上传配置数组
* @returns Promise<TaskResult[]>
*/
export async function parallelUpload(configs: UploadConfig[]): Promise<TaskResult[]> {
console.log(chalk.blue(`🚀 开始并行上传到 ${configs.length} 个服务器...`));
const promises = configs.map(async (config, index) => {
try {
await new Promise<void>((resolve, reject) => {
const task = createUploadTask(config);
task((error?: Error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
});
return {
success: true,
message: `上传成功 [${index + 1}]: ${config.host}:${config.remotePath}`,
fileCount: 1
} as TaskResult;
} catch (error) {
return {
success: false,
error: error as Error,
message: `上传失败 [${index + 1}]: ${config.host}:${config.remotePath}`
} as TaskResult;
}
});
const results = await Promise.all(promises);
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
console.log(chalk.green(`✅ 并行上传完成: ${successCount} 成功, ${failureCount} 失败`));
return results;
}
/**
* 创建多上传任务
* @param configs 上传配置数组
* @param parallel 是否并行执行
* @returns Gulp 任务函数
*/
export function createMultiUploadTask(configs: UploadConfig[], parallel: boolean = false): TaskFunction {
return async function multiUpload(cb) {
try {
console.log(chalk.blue(`🚀 开始${parallel ? '并行' : '串行'}上传到 ${configs.length} 个目标...`));
// 验证所有配置
for (const config of configs) {
const errors = uploadHelpers.validateConfig(config);
if (errors.length > 0) {
throw new Error(`上传配置错误 (${config.host}): ${errors.join(', ')}`);
}
}
let results: TaskResult[];
if (parallel) {
results = await parallelUpload(configs);
} else {
results = await batchUpload(configs);
}
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
if (failureCount > 0) {
console.log(chalk.yellow(`⚠️ 部分上传失败:`));
results.filter(r => !r.success).forEach(result => {
console.log(chalk.red(` - ${result.message}`));
});
}
console.log(chalk.green(`✅ 多目标上传完成: ${successCount} 成功, ${failureCount} 失败`));
if (failureCount > 0 && successCount === 0) {
cb(new Error('所有上传任务都失败了'));
} else {
cb();
}
} catch (error) {
console.error(chalk.red('❌ 多目标上传失败:'), error);
cb(error);
}
};
}
/**
* 上传工具函数
*/
export const uploadHelpers = {
/**
* 创建上传配置
* @param baseConfig 基础配置
* @param overrides 覆盖配置
*/
createConfig: (baseConfig: Partial<UploadConfig>, overrides: Partial<UploadConfig> = {}): UploadConfig => {
return {
type: 'sftp',
port: baseConfig.type === 'ftp' ? 21 : 22,
parallel: true,
clean: false,
...baseConfig,
...overrides
} as UploadConfig;
},
/**
* 验证上传配置
* @param config 上传配置
*/
validateConfig: (config: UploadConfig): string[] => {
const errors: string[] = [];
if (!config.host) errors.push('缺少服务器主机地址');
if (!config.username) errors.push('缺少用户名');
if (!config.password && !config.privateKey) errors.push('缺少密码或私钥');
if (!config.remotePath) errors.push('缺少远程路径');
if (!config.src) errors.push('缺少源文件路径');
return errors;
}
};