gitxelectron 1.1.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/out/main/index.js
CHANGED
|
@@ -58,15 +58,23 @@ function cleanupSession(sessionId) {
|
|
|
58
58
|
activeSessions.delete(sessionId);
|
|
59
59
|
}
|
|
60
60
|
function getToken(sessionId) {
|
|
61
|
-
|
|
61
|
+
let accountId = activeSessions.get(sessionId);
|
|
62
|
+
if (!accountId && accounts.length > 0) {
|
|
63
|
+
accountId = accounts[0].id;
|
|
64
|
+
activeSessions.set(sessionId, accountId);
|
|
65
|
+
}
|
|
62
66
|
if (!accountId) return null;
|
|
63
67
|
const acc = accounts.find((a) => a.id === accountId);
|
|
64
|
-
return acc ? acc.token : null;
|
|
68
|
+
return acc ? acc.token : accounts.length > 0 ? accounts[0].token : null;
|
|
65
69
|
}
|
|
66
70
|
function getActiveAccount(sessionId) {
|
|
67
|
-
|
|
71
|
+
let accountId = activeSessions.get(sessionId);
|
|
72
|
+
if (!accountId && accounts.length > 0) {
|
|
73
|
+
accountId = accounts[0].id;
|
|
74
|
+
activeSessions.set(sessionId, accountId);
|
|
75
|
+
}
|
|
68
76
|
if (!accountId) return null;
|
|
69
|
-
return accounts.find((a) => a.id === accountId) || null;
|
|
77
|
+
return accounts.find((a) => a.id === accountId) || (accounts.length > 0 ? accounts[0] : null);
|
|
70
78
|
}
|
|
71
79
|
async function getAccounts(sessionId) {
|
|
72
80
|
return {
|
|
@@ -578,6 +586,22 @@ function initGit(sessionId, baseDir) {
|
|
|
578
586
|
});
|
|
579
587
|
gitSessions.set(sessionId, { git, repoPath: baseDir });
|
|
580
588
|
}
|
|
589
|
+
function createSafeGit(baseDir) {
|
|
590
|
+
const options = {
|
|
591
|
+
binary: "git",
|
|
592
|
+
maxConcurrentProcesses: 6,
|
|
593
|
+
unsafe: { allowUnsafeEditor: true, allowUnsafeAskPass: true, allowUnsafeCredentialHelper: true }
|
|
594
|
+
};
|
|
595
|
+
if (baseDir) {
|
|
596
|
+
options.baseDir = baseDir;
|
|
597
|
+
}
|
|
598
|
+
return simpleGit.simpleGit(options).env({
|
|
599
|
+
...process.env,
|
|
600
|
+
GCM_INTERACTIVE: "false",
|
|
601
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
602
|
+
GIT_ASKPASS: "echo"
|
|
603
|
+
});
|
|
604
|
+
}
|
|
581
605
|
function cleanupGitSession(sessionId) {
|
|
582
606
|
gitSessions.delete(sessionId);
|
|
583
607
|
}
|
|
@@ -709,10 +733,20 @@ async function commitChanges(sessionId, message) {
|
|
|
709
733
|
function injectAuthToken(sessionId, remoteUrl) {
|
|
710
734
|
const token = getToken(sessionId);
|
|
711
735
|
if (!token) return remoteUrl;
|
|
712
|
-
|
|
713
|
-
let cleanUrl = remoteUrl.
|
|
714
|
-
if (cleanUrl.startsWith("
|
|
715
|
-
|
|
736
|
+
try {
|
|
737
|
+
let cleanUrl = remoteUrl.trim();
|
|
738
|
+
if (cleanUrl.startsWith("git@github.com:")) {
|
|
739
|
+
cleanUrl = cleanUrl.replace("git@github.com:", "https://github.com/");
|
|
740
|
+
}
|
|
741
|
+
if (cleanUrl.includes("github.com")) {
|
|
742
|
+
const parsed = new URL(cleanUrl);
|
|
743
|
+
parsed.username = "x-access-token";
|
|
744
|
+
parsed.password = token;
|
|
745
|
+
return parsed.toString();
|
|
746
|
+
}
|
|
747
|
+
} catch (e) {
|
|
748
|
+
if (remoteUrl.includes("github.com")) {
|
|
749
|
+
return remoteUrl.replace(/https:\/\/(?:[^@]+@)?github\.com\//, `https://x-access-token:${token}@github.com/`);
|
|
716
750
|
}
|
|
717
751
|
}
|
|
718
752
|
return remoteUrl;
|
|
@@ -1089,20 +1123,95 @@ async function executeRawCommand(sessionId, args) {
|
|
|
1089
1123
|
return await git.raw(args);
|
|
1090
1124
|
}
|
|
1091
1125
|
async function cloneRepository(sessionId, remoteUrl, destPath) {
|
|
1092
|
-
|
|
1126
|
+
let normalizedUrl = remoteUrl.trim();
|
|
1127
|
+
if (!normalizedUrl.startsWith("http://") && !normalizedUrl.startsWith("https://") && !normalizedUrl.startsWith("git@")) {
|
|
1128
|
+
normalizedUrl = "https://github.com/" + normalizedUrl;
|
|
1129
|
+
}
|
|
1130
|
+
if (normalizedUrl.startsWith("git@github.com:")) {
|
|
1131
|
+
normalizedUrl = normalizedUrl.replace("git@github.com:", "https://github.com/");
|
|
1132
|
+
}
|
|
1133
|
+
if (!normalizedUrl.endsWith(".git") && normalizedUrl.startsWith("http")) {
|
|
1134
|
+
normalizedUrl += ".git";
|
|
1135
|
+
}
|
|
1136
|
+
const authUrl = injectAuthToken(sessionId, normalizedUrl);
|
|
1093
1137
|
await fs__namespace.mkdir(path__namespace.dirname(destPath), { recursive: true });
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
1098
|
-
GIT_ASKPASS: "echo"
|
|
1099
|
-
});
|
|
1100
|
-
await git.clone(authUrl, destPath);
|
|
1138
|
+
let destExists = false;
|
|
1139
|
+
let destIsEmpty = false;
|
|
1140
|
+
let destHasGit = false;
|
|
1101
1141
|
try {
|
|
1102
|
-
|
|
1103
|
-
|
|
1142
|
+
const stats = await fs__namespace.stat(destPath);
|
|
1143
|
+
if (stats.isDirectory()) {
|
|
1144
|
+
destExists = true;
|
|
1145
|
+
const files = await fs__namespace.readdir(destPath);
|
|
1146
|
+
const realFiles = files.filter(
|
|
1147
|
+
(f) => ![".ds_store", "desktop.ini", "thumbs.db"].includes(f.toLowerCase())
|
|
1148
|
+
);
|
|
1149
|
+
destIsEmpty = realFiles.length === 0;
|
|
1150
|
+
destHasGit = files.includes(".git");
|
|
1151
|
+
}
|
|
1152
|
+
} catch {
|
|
1153
|
+
destExists = false;
|
|
1154
|
+
}
|
|
1155
|
+
try {
|
|
1156
|
+
if (destExists && destHasGit) {
|
|
1157
|
+
const existingGit = createSafeGit(destPath);
|
|
1158
|
+
try {
|
|
1159
|
+
await existingGit.raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1160
|
+
const remotes = await existingGit.getRemotes();
|
|
1161
|
+
if (remotes.some((r) => r.name === "origin")) {
|
|
1162
|
+
await existingGit.raw(["remote", "set-url", "origin", authUrl]);
|
|
1163
|
+
} else {
|
|
1164
|
+
await existingGit.raw(["remote", "add", "origin", authUrl]);
|
|
1165
|
+
}
|
|
1166
|
+
await existingGit.raw(["-c", "credential.helper=", "fetch", "origin"]);
|
|
1167
|
+
const st = await existingGit.status();
|
|
1168
|
+
const branch = st.current || "main";
|
|
1169
|
+
try {
|
|
1170
|
+
await existingGit.raw(["-c", "credential.helper=", "pull", "origin", branch]);
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
}
|
|
1173
|
+
return destPath;
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
console.warn("Re-using existing repo failed, falling through to clone attempt:", err);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
if (destExists && destIsEmpty) {
|
|
1179
|
+
const emptyDirGit = createSafeGit(destPath);
|
|
1180
|
+
await emptyDirGit.raw(["-c", "credential.helper=", "clone", authUrl, "."]);
|
|
1181
|
+
try {
|
|
1182
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1183
|
+
} catch (e) {
|
|
1184
|
+
}
|
|
1185
|
+
return destPath;
|
|
1186
|
+
}
|
|
1187
|
+
if (destExists && !destIsEmpty && !destHasGit) {
|
|
1188
|
+
throw new Error(
|
|
1189
|
+
`The folder '${path__namespace.basename(destPath)}' already contains files. Please select an empty folder or a different location to clone.`
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
const baseDir = path__namespace.dirname(destPath);
|
|
1193
|
+
const targetDirName = path__namespace.basename(destPath);
|
|
1194
|
+
const parentGit = createSafeGit(baseDir);
|
|
1195
|
+
await parentGit.raw(["-c", "credential.helper=", "clone", authUrl, targetDirName]);
|
|
1196
|
+
try {
|
|
1197
|
+
await createSafeGit().raw(["config", "--global", "--add", "safe.directory", destPath]);
|
|
1198
|
+
} catch (e) {
|
|
1199
|
+
}
|
|
1200
|
+
return destPath;
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
const token = getToken(sessionId);
|
|
1203
|
+
let cleanMsg = err?.message || String(err);
|
|
1204
|
+
if (token) {
|
|
1205
|
+
cleanMsg = cleanMsg.split(token).join("***");
|
|
1206
|
+
}
|
|
1207
|
+
cleanMsg = cleanMsg.replace(/https:\/\/[^@]+@github\.com/g, "https://github.com");
|
|
1208
|
+
if (cleanMsg.includes("terminal prompts disabled") || cleanMsg.includes("Authentication failed") || cleanMsg.includes("could not read Username")) {
|
|
1209
|
+
throw new Error(
|
|
1210
|
+
`Authentication failed for GitHub repository. Please verify your account is logged in and has access to this repository.`
|
|
1211
|
+
);
|
|
1212
|
+
}
|
|
1213
|
+
throw new Error(cleanMsg);
|
|
1104
1214
|
}
|
|
1105
|
-
return destPath;
|
|
1106
1215
|
}
|
|
1107
1216
|
const SYSTEM_PROMPT = `You are an autonomous Git Healer Agent for the "GitElectron" app.
|
|
1108
1217
|
The user has encountered a Git error during a push, pull, or merge operation and you are executing as a background process to silently fix it.
|
|
@@ -1209,6 +1318,32 @@ ${errorMsg}
|
|
|
1209
1318
|
`AI Analysis failed after trying all provided keys. Last error: ${lastError?.message}`
|
|
1210
1319
|
);
|
|
1211
1320
|
}
|
|
1321
|
+
const getConfigPath = () => path__namespace.join(electron.app.getPath("userData"), "gitx-config.json");
|
|
1322
|
+
const saveLastWorkspace = async (workspacePath) => {
|
|
1323
|
+
try {
|
|
1324
|
+
const configPath = getConfigPath();
|
|
1325
|
+
let config = {};
|
|
1326
|
+
try {
|
|
1327
|
+
const data = await fs__namespace.readFile(configPath, "utf8");
|
|
1328
|
+
config = JSON.parse(data);
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
}
|
|
1331
|
+
config.lastWorkspace = workspacePath;
|
|
1332
|
+
await fs__namespace.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
1333
|
+
} catch (e) {
|
|
1334
|
+
console.error("Failed to save workspace", e);
|
|
1335
|
+
}
|
|
1336
|
+
};
|
|
1337
|
+
const getLastWorkspace = async () => {
|
|
1338
|
+
try {
|
|
1339
|
+
const configPath = getConfigPath();
|
|
1340
|
+
const data = await fs__namespace.readFile(configPath, "utf8");
|
|
1341
|
+
const config = JSON.parse(data);
|
|
1342
|
+
return config.lastWorkspace || null;
|
|
1343
|
+
} catch (e) {
|
|
1344
|
+
return null;
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1212
1347
|
let currentWatcher = null;
|
|
1213
1348
|
let syncTimeout = null;
|
|
1214
1349
|
let isAutoSyncEnabled = false;
|
|
@@ -1235,7 +1370,9 @@ function toggleAutoSync(sessionId, repoPath, enabled, onLog) {
|
|
|
1235
1370
|
// ignore dotfiles (.git, .DS_Store)
|
|
1236
1371
|
/node_modules/,
|
|
1237
1372
|
/dist/,
|
|
1238
|
-
/build
|
|
1373
|
+
/build/,
|
|
1374
|
+
/~\$/
|
|
1375
|
+
// ignore Office temp lock files (~$README.md, etc.)
|
|
1239
1376
|
],
|
|
1240
1377
|
persistent: true,
|
|
1241
1378
|
ignoreInitial: true
|
|
@@ -1259,7 +1396,12 @@ async function triggerSyncSequence(sessionId, _repoPath, onLog) {
|
|
|
1259
1396
|
changedFiles.clear();
|
|
1260
1397
|
try {
|
|
1261
1398
|
onLog(`Staging ${filesToSync.length} file(s)...`);
|
|
1262
|
-
|
|
1399
|
+
const existingFiles = filesToSync.filter((f) => !f.startsWith("~$") && !f.includes("/~$"));
|
|
1400
|
+
if (existingFiles.length === 0) {
|
|
1401
|
+
onLog("No valid files to sync (temp files filtered).");
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
await stageFiles(sessionId, existingFiles);
|
|
1263
1405
|
const status = await getStatus(sessionId);
|
|
1264
1406
|
const branch = status.current || "main";
|
|
1265
1407
|
const commitMsg = `Auto-sync: update ${filesToSync.length > 1 ? filesToSync.length + " files" : filesToSync[0]}`;
|
|
@@ -1383,6 +1525,7 @@ electron.app.whenReady().then(async () => {
|
|
|
1383
1525
|
if (isRepo) {
|
|
1384
1526
|
initGit(sessionId, path2);
|
|
1385
1527
|
}
|
|
1528
|
+
await saveLastWorkspace(path2);
|
|
1386
1529
|
return { path: path2, isRepo };
|
|
1387
1530
|
});
|
|
1388
1531
|
electron.ipcMain.handle("set-repo-path", async (event, path2) => {
|
|
@@ -1391,6 +1534,7 @@ electron.app.whenReady().then(async () => {
|
|
|
1391
1534
|
if (isRepo) {
|
|
1392
1535
|
initGit(sessionId, path2);
|
|
1393
1536
|
}
|
|
1537
|
+
await saveLastWorkspace(path2);
|
|
1394
1538
|
return { path: path2, isRepo };
|
|
1395
1539
|
});
|
|
1396
1540
|
electron.ipcMain.handle("init-repo", async (event, path2) => {
|
|
@@ -1678,7 +1822,10 @@ electron.app.whenReady().then(async () => {
|
|
|
1678
1822
|
}, 500);
|
|
1679
1823
|
});
|
|
1680
1824
|
});
|
|
1681
|
-
|
|
1825
|
+
if (!initialRepo) {
|
|
1826
|
+
initialRepo = await getLastWorkspace();
|
|
1827
|
+
}
|
|
1828
|
+
createWindow(initialRepo || void 0);
|
|
1682
1829
|
electron.app.on("activate", function() {
|
|
1683
1830
|
if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
|
|
1684
1831
|
});
|