Skip to content

Commit 64b736d

Browse files
committed
feat(cli/sync): 完成判断冲突=>合并冲突=>推送配置 MVP 闭环
1 parent 75234ef commit 64b736d

File tree

6 files changed

+275
-202
lines changed

6 files changed

+275
-202
lines changed

packages/cli/src/libs/sync.ts

Lines changed: 0 additions & 202 deletions
This file was deleted.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import path from "path";
2+
import os from 'os'
3+
4+
5+
/**
6+
* GCM 配置目录
7+
*/
8+
const GCM_CONFIG_DIR = path.join(os.homedir(), ".gcm")
9+
/**
10+
* 临时工作目录名称
11+
*/
12+
export const TEMP_FOLDER_NAME = "sync_temp_workspace";
13+
/**
14+
* 临时工作目录,用于处理配置同步工作
15+
*/
16+
export const TEMP_SYNC_DIR = path.join(GCM_CONFIG_DIR, TEMP_FOLDER_NAME);
17+
/**
18+
* 配置仓库名称
19+
*/
20+
const GIT_REPO_NAME = "config";
21+
/**
22+
* 配置仓库中用于存放 GCM 配置的目录名称
23+
*/
24+
const SUB_FOLDER = "gcm";
25+
/**
26+
* GCM 配置文件名称
27+
*/
28+
const GCM_CONFIG_FILE_NAME = "config.json";
29+
/**
30+
* 仓库临时存放目录路径
31+
*/
32+
export const REPO_FOLDER_PATH = path.join(TEMP_SYNC_DIR, GIT_REPO_NAME);
33+
/**
34+
* 仓库临时存放目录的 GCM 配置存放目录路径
35+
*/
36+
export const CONFIG_FOLDER_PATH = path.join(REPO_FOLDER_PATH, SUB_FOLDER);
37+
/**
38+
* 仓库临时存放目录的 GCM 配置文件存放路径
39+
*/
40+
export const GCM_CONFIG_FILE_PATH = path.join(CONFIG_FOLDER_PATH, GCM_CONFIG_FILE_NAME);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import fs from "fs";
2+
import { getAllUserConfigs } from "@lexmin0412/gcm-api";
3+
import { GCM_CONFIG_FILE_PATH } from "./const";
4+
import { cloneConfigRepo, pushConfig, writeConfigIntoLocalRepo } from "./steps";
5+
import { isEqual } from "./utils";
6+
import { createServerAndOpenPage } from "./server";
7+
8+
// 获取本地用户配置
9+
const localUserConfigs = getAllUserConfigs();
10+
11+
/**
12+
* 同步配置主函数
13+
*/
14+
const sync = async () => {
15+
// clone 用户配置仓库
16+
await cloneConfigRepo();
17+
// 判断本地配置是否与远程配置一致
18+
const remoteUserConfigs = JSON.parse(fs.readFileSync(GCM_CONFIG_FILE_PATH, "utf8"));
19+
if (!remoteUserConfigs?.length) {
20+
// 远程配置不存在,直接写入本地配置
21+
writeConfigIntoLocalRepo(localUserConfigs);
22+
pushConfig();
23+
} else if (isEqual(localUserConfigs, remoteUserConfigs)) {
24+
console.log("本地配置与远程配置一致,无需同步");
25+
} else {
26+
console.log("本地配置与远程配置存在冲突,即将打开浏览器,请前往处理");
27+
// 打开冲突处理页面
28+
const mergedConfig = await createServerAndOpenPage({
29+
localConfig: localUserConfigs,
30+
remoteConfig: remoteUserConfigs,
31+
})
32+
// 写入本地配置
33+
writeConfigIntoLocalRepo(mergedConfig);
34+
// 推送配置
35+
pushConfig();
36+
process.exit(0);
37+
}
38+
}
39+
40+
sync()
41+
42+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import http from "http";
2+
import fs from "fs";
3+
import path from "path";
4+
import puppeteer from "puppeteer";
5+
import { UserConfig } from "../../types";
6+
7+
export const createServerAndOpenPage = async (options: {
8+
localConfig: UserConfig[],
9+
remoteConfig: UserConfig[],
10+
}) => {
11+
return new Promise<UserConfig[]>((resolve, reject) => {
12+
// 创建HTTP服务器
13+
const server = http.createServer((req, res) => {
14+
// 处理根路径请求
15+
const purePath = req.url?.slice(0, req.url.indexOf("?"));
16+
if (!purePath || purePath === "/") {
17+
const html = fs.readFileSync(
18+
path.join(__dirname, "../../server/index.html")
19+
);
20+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
21+
return res.end(html);
22+
}
23+
24+
// 处理其他静态文件
25+
const filePath = path.join(__dirname, req.url as string);
26+
if (fs.existsSync(filePath)) {
27+
const content = fs.readFileSync(filePath);
28+
res.end(content);
29+
} else {
30+
res.writeHead(404);
31+
res.end("Not found");
32+
}
33+
});
34+
35+
const port = 56789;
36+
const baseURL = `http://localhost:${port}`;
37+
const url = `${baseURL}?ws=ws://localhost:${port}`;
38+
39+
/**
40+
* 打开冲突处理页面
41+
*/
42+
const openConflictWebPage = async (cb: (type: 'success', data: any) => void) => {
43+
const browser = await puppeteer.launch({
44+
headless: false,
45+
});
46+
const page = await browser.newPage();
47+
await page.goto(url, {
48+
49+
});
50+
// 给页面注入全局变量
51+
await page.addScriptTag({
52+
content: `
53+
window.GCM = {
54+
localConfig: ${JSON.stringify(options.localConfig)},
55+
remoteConfig: ${JSON.stringify(options.remoteConfig)},
56+
};
57+
document.querySelector('#localConfig').value = JSON.stringify(window.GCM.localConfig, null, 2);
58+
document.querySelector('#remoteConfig').value = JSON.stringify(window.GCM.remoteConfig, null, 2);
59+
`,
60+
});
61+
// 监听页面中的点击事件
62+
await page.exposeFunction("onClickEvent", (event) => {
63+
console.log("点击事件发生:", event);
64+
if (event.target === 'submit_btn') {
65+
66+
// TODO 校验配置
67+
68+
cb('success', JSON.parse(event.data))
69+
browser.close();
70+
}
71+
});
72+
73+
// 在页面中注入 JavaScript 代码来监听点击事件
74+
await page.evaluate(() => {
75+
76+
document.addEventListener("click", (event) => {
77+
console.log('合并输入框', document.querySelector('#mergedConfig'))
78+
console.log('合并输入框的值', document.querySelector('#mergedConfig')?.innerHTML)
79+
// 调用 Node.js 中暴露的函数
80+
window.onClickEvent({
81+
target: event.target.id,
82+
data: document.querySelector('#mergedConfig')?.value
83+
});
84+
});
85+
});
86+
};
87+
88+
// 启动服务
89+
server.listen(port, async () => {
90+
console.log(`Server running at ${baseURL}`);
91+
// 获取本地配置内容
92+
openConflictWebPage((type, data) => {
93+
if (type == 'success') {
94+
// TODO 提交配置
95+
console.log('提交配置', data)
96+
resolve(data);
97+
}
98+
});
99+
});
100+
101+
})
102+
};

0 commit comments

Comments
 (0)