【新增】部署类型七牛云oss、七牛云cdn、百度cdn、腾讯waf、腾讯edgeone、阿里云waf

【新增】解析类型godaddy
【新增】自定义CA授权管理
【调整】优化部署流程,减少代码冗余,提升类型添加效率
This commit is contained in:
chudong
2025-05-23 16:58:34 +08:00
parent 71de397e11
commit e5634d4992
263 changed files with 18348 additions and 14253 deletions

View File

@@ -1,78 +0,0 @@
# vite-plugin-git-sync
一个 Vite 插件,用于将构建后的文件同步到指定的 Git 仓库。
## 功能特点
- 自动检查并克隆目标 Git 仓库
- 支持清理同步目录
- 支持自定义文件处理函数
- 交互式 Git 提交流程
## 安装
```bash
npm install vite-plugin-git-sync --save-dev
```
## 使用方法
`vite.config.ts` 中配置插件:
```typescript
import { defineConfig } from "vite";
import gitSync from "vite-plugin-git-sync";
export default defineConfig({
plugins: [
gitSync({
gitUrl: "https://github.com/username/repo.git",
syncPath: "./sync-dir",
cleanSyncDir: true,
fileProcessor: async (content, filePath) => {
// 自定义文件处理逻辑
return content;
},
}),
],
});
```
## 配置选项
| 选项 | 类型 | 必填 | 默认值 | 描述 |
| ------------- | -------- | ---- | ------ | ------------------------------------ |
| gitUrl | string | 是 | - | Git 仓库地址 |
| syncPath | string | 是 | - | 同步目标目录(相对于项目根目录) |
| cleanSyncDir | boolean | 否 | false | 是否在同步前清理目标目录 |
| fileProcessor | function | 否 | - | 自定义文件处理函数,可以修改文件内容 |
## 文件处理函数
`fileProcessor` 函数接收两个参数:
- `content`: 文件内容(字符串)
- `filePath`: 文件路径
返回处理后的文件内容(字符串或 Promise<string>)。
## 示例
```typescript
// 简单的文件处理示例
const fileProcessor = async (content: string, filePath: string) => {
if (filePath.endsWith(".js")) {
// 为 JS 文件添加版权信息
return `/* Copyright ${new Date().getFullYear()} */\n${content}`;
}
return content;
};
// 在 vite.config.ts 中使用
gitSync({
gitUrl: "https://github.com/username/repo.git",
syncPath: "./sync-dir",
cleanSyncDir: true,
fileProcessor,
});
```

View File

@@ -1,38 +0,0 @@
{
"name": "@baota/plugin-project-sync-git",
"version": "1.0.0",
"description": "A Vite plugin to sync build files to a git repository",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc -w"
},
"keywords": [
"vite",
"plugin",
"git",
"sync"
],
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.js"
}
},
"author": "",
"license": "MIT",
"dependencies": {
"inquirer": "^8.2.5",
"simple-git": "^3.22.0"
},
"devDependencies": {
"@types/inquirer": "^8.2.5",
"@types/node": "^20.8.0",
"typescript": "^5.2.2",
"vite": "^4.4.9"
},
"peerDependencies": {
"vite": "^4.0.0"
}
}

View File

@@ -1,511 +0,0 @@
import { Plugin } from "vite";
import { simpleGit, SimpleGit } from "simple-git";
import * as fs from "fs";
import * as path from "path";
import inquirer from "inquirer";
import { promisify } from "util";
import { createReadStream, createWriteStream } from "fs";
import { pipeline } from "stream/promises";
import { Transform } from "stream";
// 将 Node.js 的回调式 API 转换为 Promise 形式
const mkdir = promisify(fs.mkdir);
const readdir = promisify(fs.readdir);
const stat = promisify(fs.stat);
const rm = promisify(fs.rm);
const exists = promisify(fs.exists);
/**
* 插件配置选项接口
*/
export interface GitSyncOptions {
/** Git 仓库地址,支持同步到多个仓库 */
gitUrl: string[];
/** 同步目标目录(相对于项目根目录)的前缀,每个仓库会在此前缀下创建对应的目录 */
syncPath: string;
/** 是否在同步前清理目标目录(可选) */
cleanSyncDir?: boolean;
/** 自定义文件处理函数,可以修改文件内容(可选) */
fileProcessor?: (
content: string,
filePath: string,
) => string | Promise<string>;
/** 是否跳过提交确认(可选),直接进行提交 */
skipConfirmation?: boolean;
}
// 文件统计信息接口
interface FileStats {
size: number;
path: string;
type: "file" | "directory";
children?: FileStats[];
}
// 格式化文件大小
function formatFileSize(bytes: number): string {
const units = ["B", "KB", "MB", "GB", "TB"];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
// 获取目录结构
async function getDirectoryStructure(dir: string): Promise<FileStats> {
const stats = await stat(dir);
const result: FileStats = {
size: stats.size,
path: path.basename(dir),
type: stats.isDirectory() ? "directory" : "file",
};
if (stats.isDirectory()) {
const files = await readdir(dir);
result.children = await Promise.all(
files.map(async (file) => {
const fullPath = path.join(dir, file);
return getDirectoryStructure(fullPath);
}),
);
result.size = result.children.reduce(
(total, child) => total + child.size,
0,
);
}
return result;
}
// 打印目录结构
function printDirectoryStructure(stats: FileStats, level = 0): void {
const indent = " ".repeat(level);
const prefix = stats.type === "directory" ? "📁" : "📄";
console.log(
`${indent}${prefix} ${stats.path} (${formatFileSize(stats.size)})`,
);
if (stats.children) {
stats.children.forEach((child) =>
printDirectoryStructure(child, level + 1),
);
}
}
// 创建进度显示流
class ProgressStream extends Transform {
private totalBytes = 0;
private processedBytes = 0;
private lastUpdate = 0;
private readonly updateInterval = 1000; // 更新间隔(毫秒)
constructor(private filePath: string) {
super();
}
_transform(
chunk: Buffer,
encoding: string,
callback: (error?: Error | null, data?: Buffer) => void,
) {
this.processedBytes += chunk.length;
this.totalBytes += chunk.length;
const now = Date.now();
if (now - this.lastUpdate >= this.updateInterval) {
const progress = ((this.processedBytes / this.totalBytes) * 100).toFixed(
2,
);
process.stdout.write(
`\r处理文件 ${this.filePath}: ${progress}% (${formatFileSize(this.processedBytes)} / ${formatFileSize(this.totalBytes)})`,
);
this.lastUpdate = now;
}
callback(null, chunk);
}
_flush(callback: (error?: Error | null) => void) {
process.stdout.write("\n");
callback();
}
}
/**
* 获取当前项目的最新Git提交信息
*
* @returns 最新的Git提交信息
*/
async function getLatestCommitMessage(): Promise<string> {
try {
// 初始化当前项目的Git
const currentProjectGit = simpleGit(process.cwd());
// 检查是否是Git仓库
const isRepo = await currentProjectGit.checkIsRepo();
if (!isRepo) {
return "Update build files"; // 默认提交信息
}
// 获取最新的提交记录
const log = await currentProjectGit.log({ maxCount: 1 });
if (log.latest) {
return `Sync: ${log.latest.message}`;
}
return "Update build files";
} catch (error) {
console.warn("获取最新提交信息失败:", error);
return "Update build files";
}
}
/**
* 处理单个Git仓库的同步
*
* @param repoUrl Git仓库URL
* @param syncBasePath 基础同步路径
* @param distDir 构建输出目录
* @param commitMessage 提交信息
* @param cleanSyncDir 是否清理同步目录
* @param fileProcessor 文件处理函数
* @returns 同步结果
*/
async function syncToRepo(
repoUrl: string,
syncBasePath: string,
distDir: string,
commitMessage: string,
cleanSyncDir: boolean,
fileProcessor?: (
content: string,
filePath: string,
) => string | Promise<string>,
): Promise<boolean> {
// 从仓库URL提取仓库名称作为目录名
const repoName = path
.basename(repoUrl, ".git")
.replace(/[^a-zA-Z0-9_-]/g, "_");
const repoSyncPath = path.join(syncBasePath, repoName);
const absoluteSyncPath = path.resolve(process.cwd(), repoSyncPath);
console.log(`\n开始同步到仓库: ${repoUrl}`);
console.log(`同步目标目录: ${repoSyncPath}`);
let git: SimpleGit;
// 检查同步目录是否存在
const syncDirExists = await exists(absoluteSyncPath);
if (!syncDirExists) {
// 目录不存在,克隆仓库
console.log(`目录 ${repoSyncPath} 不存在,正在克隆仓库...`);
git = simpleGit();
try {
await git.clone(repoUrl, absoluteSyncPath);
console.log(`仓库克隆成功: ${repoUrl}`);
} catch (error) {
console.error(`克隆仓库失败: ${repoUrl}`, error);
return false;
}
}
// 初始化Git
git = simpleGit(absoluteSyncPath);
// 检查是否是有效的Git仓库
try {
const isRepo = await git.checkIsRepo();
if (!isRepo) {
console.error(`目录 ${repoSyncPath} 不是有效的Git仓库`);
return false;
}
} catch (error) {
console.error(`检查Git仓库失败: ${repoSyncPath}`, error);
return false;
}
// 如果需要清理同步目录
if (cleanSyncDir) {
console.log(`清理同步目录: ${repoSyncPath}`);
const files = await readdir(absoluteSyncPath);
for (const file of files) {
// 保留 .git 目录,删除其他所有文件
if (file !== ".git") {
await rm(path.join(absoluteSyncPath, file), {
recursive: true,
force: true,
});
}
}
}
// 复制文件到同步目录
try {
await copyFiles(distDir, absoluteSyncPath, fileProcessor);
} catch (error) {
console.error(`复制文件失败: ${repoSyncPath}`, error);
return false;
}
try {
// 拉取远程仓库以避免冲突
console.log(`拉取远程仓库: ${repoUrl}`);
await git.pull();
// 添加所有文件
console.log(`添加文件到Git: ${repoSyncPath}`);
await git.add(".");
// 检查是否有更改
const status = await git.status();
if (status.files.length === 0) {
console.log(`没有需要提交的更改: ${repoSyncPath}`);
return true;
}
// 提交更改
console.log(`提交更改: ${repoSyncPath}`);
await git.commit(commitMessage);
// 推送到远程仓库
console.log(`推送到远程仓库: ${repoUrl}`);
await git.push();
console.log(`同步成功: ${repoUrl}`);
return true;
} catch (error) {
console.error(`Git操作失败: ${repoUrl}`, error);
return false;
}
}
/**
* Vite 插件:将构建后的文件同步到指定的多个 Git 仓库
*
* 该插件作为当前项目Git之外的同步工具主要用于将当前项目构建后的内容
* 同步到其他Git仓库并提交。
*
* @param options 插件配置选项
* @returns Vite 插件对象
*/
export function pluginProjectSyncGit(options: GitSyncOptions): Plugin {
// 解构配置选项,设置默认值
const {
gitUrl,
syncPath,
cleanSyncDir = false,
fileProcessor = undefined,
skipConfirmation = false,
} = options;
// 存储 vite 配置中的构建输出目录
let viteBuildOutDir: string;
return {
name: "vite-plugin-git-sync",
// 仅在构建模式下应用插件
apply: "build",
// 在配置解析后执行,获取 vite 配置中的构建输出目录
configResolved(config) {
// 获取 vite 配置中的构建输出目录
viteBuildOutDir = config.build.outDir || "dist";
},
// 在构建完成后执行
async closeBundle() {
// 使用 vite 配置中的构建输出目录
console.log(`\n=== 项目构建同步工具 ===`);
console.log(`使用构建输出目录: ${viteBuildOutDir}`);
console.log(`同步目标仓库数量: ${gitUrl.length}`);
// 复制文件到同步目录
const distDir = path.resolve(process.cwd(), viteBuildOutDir);
// 检查构建输出目录是否存在
try {
await stat(distDir);
} catch {
console.error(`构建输出目录 ${distDir} 不存在,请确保构建成功`);
return;
}
// 获取默认提交信息(当前项目的最新提交信息)
const defaultCommitMessage = await getLatestCommitMessage();
// 确认是否要提交
let shouldCommit = skipConfirmation;
let commitMessage = defaultCommitMessage;
if (!skipConfirmation) {
const confirmResult = await inquirer.prompt([
{
type: "confirm",
name: "shouldCommit",
message: "是否要同步并提交更改到Git仓库",
default: true,
},
]);
shouldCommit = confirmResult.shouldCommit;
}
if (shouldCommit) {
// 获取提交信息
const messageResult = await inquirer.prompt([
{
type: "input",
name: "commitMessage",
message: "请输入提交信息(留空使用最新提交信息):",
default: defaultCommitMessage,
},
]);
commitMessage = messageResult.commitMessage || defaultCommitMessage;
console.log(`使用提交信息: "${commitMessage}"`);
// 创建基础同步目录
const absoluteSyncPath = path.resolve(process.cwd(), syncPath);
await mkdir(absoluteSyncPath, { recursive: true });
// 同步到每个仓库
const results = await Promise.all(
gitUrl.map((url) =>
syncToRepo(
url,
syncPath,
distDir,
commitMessage,
cleanSyncDir,
fileProcessor,
),
),
);
// 统计结果
const successCount = results.filter(Boolean).length;
console.log(`\n=== 同步完成 ===`);
console.log(`成功: ${successCount}/${gitUrl.length}`);
if (successCount === gitUrl.length) {
console.log("所有仓库同步成功!");
} else {
console.warn(`部分仓库同步失败,请检查上面的错误信息`);
}
} else {
console.log("用户取消了同步操作");
}
},
};
}
// 优化的文件复制函数
async function copyFiles(
sourceDir: string,
targetDir: string,
fileProcessor?: (
content: string,
filePath: string,
) => string | Promise<string>,
) {
console.log(`\n开始复制文件从 ${sourceDir}${targetDir}...`);
const startTime = Date.now();
// 获取源目录结构
const sourceStructure = await getDirectoryStructure(sourceDir);
console.log("\n源目录结构:");
printDirectoryStructure(sourceStructure);
// 获取源目录中的所有文件
const files = await readdir(sourceDir);
let totalFiles = 0;
let processedFiles = 0;
for (const file of files) {
const sourcePath = path.join(sourceDir, file);
const targetPath = path.join(targetDir, file);
const stats = await stat(sourcePath);
if (stats.isDirectory()) {
// 如果是目录,递归复制
await mkdir(targetPath, { recursive: true });
await copyFiles(sourcePath, targetPath, fileProcessor);
} else {
totalFiles++;
try {
// 如果是文件,根据是否提供 fileProcessor 决定处理方式
if (fileProcessor) {
// 使用流式处理文件内容
const readStream = createReadStream(sourcePath, {
encoding: "utf-8",
});
const writeStream = createWriteStream(targetPath, {
encoding: "utf-8",
});
// 创建进度显示流
const progressStream = new ProgressStream(sourcePath);
// 创建一个转换流来处理文件内容
const transformStream = new Transform({
transform: async (
chunk: Buffer,
encoding: BufferEncoding,
callback: (error?: Error | null, data?: string) => void,
) => {
try {
const processedContent = await fileProcessor(
chunk.toString(),
sourcePath,
);
callback(null, processedContent);
} catch (error) {
callback(
error instanceof Error ? error : new Error(String(error)),
);
}
},
});
// 使用 pipeline 来处理流
await pipeline(
readStream,
progressStream,
transformStream,
writeStream,
);
} else {
// 直接复制文件
const readStream = createReadStream(sourcePath);
const writeStream = createWriteStream(targetPath);
const progressStream = new ProgressStream(sourcePath);
await pipeline(readStream, progressStream, writeStream);
}
processedFiles++;
const progress = ((processedFiles / totalFiles) * 100).toFixed(2);
console.log(`\n总进度: ${progress}% (${processedFiles}/${totalFiles})`);
} catch (error) {
console.error(`\n复制文件失败: ${sourcePath} -> ${targetPath}`, error);
throw error;
}
}
}
const endTime = Date.now();
const duration = (endTime - startTime) / 1000;
console.log(`\n文件复制完成耗时: ${duration.toFixed(2)}`);
// 获取目标目录结构
const targetStructure = await getDirectoryStructure(targetDir);
console.log("\n目标目录结构:");
printDirectoryStructure(targetStructure);
}
export default pluginProjectSyncGit;

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": ["ES2020"],
"declaration": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}