Skip to content

Commit 5caff70

Browse files
committed
feat(cli): 完成 sync 的大体流程
1 parent 6bb9608 commit 5caff70

File tree

4 files changed

+608
-90
lines changed

4 files changed

+608
-90
lines changed

packages/cli/.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PUPPETEER_DOWNLOAD_BASE_URL=https://cdn.npmmirror.com/binaries/chrome-for-testing

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"inquirer": "8.2.3",
5454
"latest-version": "5.1.0",
5555
"picocolors": "^1.0.0",
56+
"puppeteer": "^24.2.1",
5657
"semver": "^7.3.7"
5758
},
5859
"types": "./lib/index.d.ts",

packages/cli/src/libs/sync.ts

Lines changed: 211 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,215 @@
1-
import http from 'http';
2-
import fs from 'fs';
3-
import path from 'path';
4-
import { exec } from 'child_process';
5-
6-
// 创建HTTP服务器
7-
const server = http.createServer((req, res) => {
8-
// 处理根路径请求
9-
const purePath = req.url?.slice(0, req.url.indexOf('?'))
10-
if (!purePath || purePath === '/') {
11-
const html = fs.readFileSync(path.join(__dirname, '../server/index.html'));
12-
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8', });
13-
return res.end(html);
14-
}
1+
import http from "http";
2+
import fs from "fs";
3+
import os from "os";
4+
import path from "path";
5+
import { getAllUserConfigs } from "@lexmin0412/gcm-api";
6+
import { execSync } from "child_process";
7+
import inquirer from "inquirer";
8+
import { UserConfig } from "../types";
9+
import puppeteer from "puppeteer";
10+
11+
// 获取本地用户配置
12+
const localUserConfigs = getAllUserConfigs();
13+
console.log("localUserConfigs", localUserConfigs);
14+
15+
const TEMP_FOLDER_NAME = "sync_temp_workspace";
16+
// 创建临时工作目录,准备同步配置
17+
const tempDir = path.join(os.homedir(), ".gcm", TEMP_FOLDER_NAME);
18+
// 如果已存在,则递归清除目录
19+
if (fs.existsSync(tempDir)) {
20+
fs.rmSync(tempDir, { recursive: true });
21+
}
22+
// 创建临时工作目录
23+
fs.mkdirSync(tempDir);
24+
// 下载远程用户配置
25+
const cloneConfigRepo = async () => {
26+
console.log("开始下载远程配置");
27+
// TODO 更换为用户的配置仓库
28+
execSync("git clone [email protected]:lexmin0412/config.git", {
29+
cwd: tempDir,
30+
});
31+
console.log("下载远程配置成功");
32+
};
1533

16-
// 处理其他静态文件
17-
const filePath = path.join(__dirname, req.url as string);
18-
if (fs.existsSync(filePath)) {
19-
const content = fs.readFileSync(filePath);
20-
res.end(content);
21-
} else {
22-
res.writeHead(404);
23-
res.end('Not found');
34+
const GIT_REPO_NAME = "config";
35+
const SUB_FOLDER = "gcm";
36+
const GCM_CONFIG_FILE_NAME = "config.json";
37+
const repoFolder = path.join(tempDir, GIT_REPO_NAME);
38+
const configFolder = path.join(repoFolder, SUB_FOLDER);
39+
const configPath = path.join(configFolder, GCM_CONFIG_FILE_NAME);
40+
41+
const writeConfig = async () => {
42+
console.log("开始写入本地配置");
43+
// 判断 configFolder 是否存在,否则新建目录
44+
if (!fs.existsSync(configFolder)) {
45+
fs.mkdirSync(configFolder);
46+
}
47+
// 判断 configPath 是否存在,否则新建文件
48+
if (!fs.existsSync(configPath)) {
49+
fs.writeFileSync(configPath, "{}");
2450
}
25-
});
51+
fs.writeFileSync(configPath, JSON.stringify(localUserConfigs, null, 2));
52+
console.log("写入本地配置成功");
53+
};
54+
55+
const pushConfig = async () => {
56+
console.log("开始推送配置到远程");
57+
// 初始化正确的 git 配置
58+
const { name, email } = await inquirer.prompt([
59+
{
60+
type: "input",
61+
name: "name",
62+
message: "请输入你的 git 用户名",
63+
},
64+
{
65+
type: "input",
66+
name: "email",
67+
message: "请输入你的 git 邮箱",
68+
},
69+
]);
70+
execSync(`git config --global user.name ${name}`, {
71+
cwd: repoFolder,
72+
});
73+
execSync(`git config --global user.email ${email}`, {
74+
cwd: repoFolder,
75+
});
76+
execSync("git add .", {
77+
cwd: repoFolder,
78+
});
79+
execSync('git commit -m "update gcm config"', {
80+
cwd: repoFolder,
81+
});
82+
execSync("git push origin main", {
83+
cwd: repoFolder,
84+
});
85+
console.log("推送配置到远程成功");
86+
};
87+
88+
cloneConfigRepo();
89+
90+
const createServerAndOpenPage = async () => {
91+
// 创建HTTP服务器
92+
const server = http.createServer((req, res) => {
93+
// 处理根路径请求
94+
const purePath = req.url?.slice(0, req.url.indexOf("?"));
95+
if (!purePath || purePath === "/") {
96+
const html = fs.readFileSync(
97+
path.join(__dirname, "../server/index.html")
98+
);
99+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
100+
return res.end(html);
101+
}
102+
103+
// 处理其他静态文件
104+
const filePath = path.join(__dirname, req.url as string);
105+
if (fs.existsSync(filePath)) {
106+
const content = fs.readFileSync(filePath);
107+
res.end(content);
108+
} else {
109+
res.writeHead(404);
110+
res.end("Not found");
111+
}
112+
});
26113

27-
const port = 56789;
28-
const baseURL = `http://localhost:${port}`
29-
const url = `${baseURL}?ws=ws://localhost:${port}`
114+
const port = 56789;
115+
const baseURL = `http://localhost:${port}`;
116+
const url = `${baseURL}?ws=ws://localhost:${port}`;
30117

31-
// 启动服务
32-
server.listen(port, () => {
33-
console.log(`Server running at ${baseURL}`);
34-
exec(`open "${url}"`);
35-
});
118+
/**
119+
* 打开冲突处理页面
120+
*/
121+
const openConflictWebPage = async () => {
122+
const browser = await puppeteer.launch({
123+
headless: false,
124+
});
125+
const page = await browser.newPage();
126+
await page.goto(url);
127+
// 监听页面中的点击事件
128+
await page.exposeFunction("onClickEvent", (event) => {
129+
console.log("点击事件发生:", event);
130+
});
131+
132+
// 在页面中注入 JavaScript 代码来监听点击事件
133+
await page.evaluate(() => {
134+
document.addEventListener("click", (event) => {
135+
// 调用 Node.js 中暴露的函数
136+
window.onClickEvent({
137+
target: event.target.tagName,
138+
x: event.clientX,
139+
y: event.clientY,
140+
});
141+
});
142+
});
143+
144+
// 监听页面中的输入事件
145+
await page.exposeFunction("onInputEvent", (event) => {
146+
console.log("输入事件发生:", event);
147+
});
148+
149+
// 在页面中注入 JavaScript 代码来监听输入事件
150+
await page.evaluate(() => {
151+
document.addEventListener("input", (event) => {
152+
// 调用 Node.js 中暴露的函数
153+
window.onInputEvent({
154+
target: event.target.tagName,
155+
value: event.target.value,
156+
});
157+
});
158+
});
159+
160+
// TODO 监听到点击提交按钮后,验证输入的内容是否正确,是则提交到远程
161+
162+
// 处理完毕后关闭浏览器
163+
await new Promise((resolve) => setTimeout(resolve, 60000));
164+
await browser.close();
165+
};
166+
167+
// 启动服务
168+
server.listen(port, async () => {
169+
console.log(`Server running at ${baseURL}`);
170+
// 获取本地配置内容
171+
openConflictWebPage();
172+
});
173+
};
174+
175+
// 判断本地配置是否与远程配置一致
176+
const remoteUserConfigs = JSON.parse(fs.readFileSync(configPath, "utf8"));
177+
// 逐个元素对比
178+
const isEqual = (
179+
localUserConfigs: UserConfig[],
180+
remoteUserConfigs: UserConfig[]
181+
) => {
182+
let result = true;
183+
if (localUserConfigs.length !== remoteUserConfigs.length) {
184+
result = false;
185+
return result;
186+
}
187+
for (let i = 0; i < localUserConfigs.length; i++) {
188+
const localUserConfig = localUserConfigs[i];
189+
const remoteUserConfig = remoteUserConfigs[i];
190+
if (localUserConfig.alias !== remoteUserConfig.alias) {
191+
result = false;
192+
}
193+
if (localUserConfig.name !== remoteUserConfig.name) {
194+
result = false;
195+
}
196+
if (localUserConfig.email !== remoteUserConfig.email) {
197+
result = false;
198+
}
199+
if (localUserConfig.origin !== remoteUserConfig.origin) {
200+
result = false;
201+
}
202+
}
203+
return result;
204+
};
205+
if (!remoteUserConfigs?.length) {
206+
// 远程配置不存在,直接写入本地配置
207+
writeConfig();
208+
pushConfig();
209+
} else if (isEqual(localUserConfigs, remoteUserConfigs)) {
210+
console.log("本地配置与远程配置一致,无需同步");
211+
} else {
212+
console.log("本地配置与远程配置存在冲突,即将打开浏览器,请前往处理");
213+
// 打开冲突处理页面
214+
createServerAndOpenPage();
215+
}

0 commit comments

Comments
 (0)