From b5ae69e0410688062f37bd3b768cd9733d3bba13 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Wed, 16 Sep 2026 20:18:00 +1200
Subject: [PATCH 01/15] Add Basic git command handlers.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitman.js | 55 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
create mode 100644 UI/git_sidebar/gitman.js
diff --git a/UI/git_sidebar/gitman.js b/UI/git_sidebar/gitman.js
new file mode 100644
index 0000000..787982a
--- /dev/null
+++ b/UI/git_sidebar/gitman.js
@@ -0,0 +1,55 @@
+const { exec } = require("child_process");
+
+class GitManager {
+ constructor(options = {}) {
+ // Directory where Git commands will run
+ this.cwd = options.cwd || process.cwd();
+
+ // Optional: enable verbose logging
+ this.verbose = options.verbose || false;
+ }
+
+ run(cmd) {
+ return new Promise((resolve, reject) => {
+ exec(cmd, { cwd: this.cwd }, (err, stdout, stderr) => {
+ if (this.verbose) {
+ console.log("[GitManager] CMD: ", cmd);
+ console.log("[GitManager] OUT: ", stdout);
+ console.log("[GitManager] ERR: ", stderr);
+ }
+ if (err) reject(stderr.trim());
+ else resolve(stdout.trim());
+ });
+ });
+ }
+
+ async init() { // Function to handle git init
+ return this.run("git init");
+ }
+
+ async addAll() { // Function to handle git add .
+ return this.run("git add .");
+ }
+
+ async commit(message) { // Function to handle git commit -m ""
+ return this.run(`git commit -m "${message}"`);
+ }
+
+ async status() { // Function to handle git status
+ return this.run("git status --short");
+ }
+
+ async push() { // Function to handle git push
+ return this.run("git push");
+ }
+
+ async pull() { // Function to handle git pull
+ return this.run("git pull");
+ }
+
+ async branch(name) { // Function to handle git branch
+ return this.run(`git branch ${name}`);
+ }
+}
+
+module.exports = GitManager;
\ No newline at end of file
From dd5da07bb77be1a8ff97d6a03a65ce9961428f63 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Wed, 16 Sep 2026 20:23:36 +1200
Subject: [PATCH 02/15] Add aditional git commands support and git switch
command stub. Also add escape char ignoring for the git commit command.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitman.js | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/UI/git_sidebar/gitman.js b/UI/git_sidebar/gitman.js
index 787982a..bc07147 100644
--- a/UI/git_sidebar/gitman.js
+++ b/UI/git_sidebar/gitman.js
@@ -17,8 +17,12 @@ class GitManager {
console.log("[GitManager] OUT: ", stdout);
console.log("[GitManager] ERR: ", stderr);
}
- if (err) reject(stderr.trim());
- else resolve(stdout.trim());
+ if (err) {
+ const msg = stdout.trim() || stderr.trim();
+ reject(msg);
+ return;
+ }
+ resolve(stdout.trim());
});
});
}
@@ -32,7 +36,8 @@ class GitManager {
}
async commit(message) { // Function to handle git commit -m ""
- return this.run(`git commit -m "${message}"`);
+ const safe = message.replace(/"/g, '\\"'); // Escape double quotes and special chars in commit mesages.
+ return this.run(`git commit -m "${safe}"`);
}
async status() { // Function to handle git status
@@ -50,6 +55,10 @@ class GitManager {
async branch(name) { // Function to handle git branch
return this.run(`git branch ${name}`);
}
+
+ async switch(name) { // Function to handle git switch branch
+ return this.run(`git switch ${name}`);
+ }
}
module.exports = GitManager;
\ No newline at end of file
From a54a0f96cfda039252005bffc4bca8e938d2f3ad Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Wed, 16 Sep 2026 20:28:41 +1200
Subject: [PATCH 03/15] Potential fix for pull request finding 'CodeQL /
Incomplete string escaping or encoding'
Signed-off-by: G-type 162748908+gtref@users.noreply.github.com
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitman.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/UI/git_sidebar/gitman.js b/UI/git_sidebar/gitman.js
index bc07147..a746caa 100644
--- a/UI/git_sidebar/gitman.js
+++ b/UI/git_sidebar/gitman.js
@@ -36,7 +36,7 @@ class GitManager {
}
async commit(message) { // Function to handle git commit -m ""
- const safe = message.replace(/"/g, '\\"'); // Escape double quotes and special chars in commit mesages.
+ const safe = message.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); // Escape backslashes and double quotes in commit messages.
return this.run(`git commit -m "${safe}"`);
}
From 12f2a9df733859b3c4c44739c890d7b9f25d3e54 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Wed, 16 Sep 2026 20:57:11 +1200
Subject: [PATCH 04/15] Add some basic stubs.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitstub.js | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 UI/git_sidebar/gitstub.js
diff --git a/UI/git_sidebar/gitstub.js b/UI/git_sidebar/gitstub.js
new file mode 100644
index 0000000..180b2d4
--- /dev/null
+++ b/UI/git_sidebar/gitstub.js
@@ -0,0 +1,37 @@
+class StatusStub {
+ static parse(raw) {
+ if (!raw.trim()) {
+ return [];
+ }
+
+ return raw.split("\n").map(line => {
+ const code = line.slice(0, 2).trim();
+ const file = line.slice(2).trim();
+ return { code, file };
+ });
+ }
+}
+
+class BranchStub {
+ static parse(raw) {
+ const lines = raw.split("\n").filter(Bolean);
+
+ let curr = null;
+ const branches = [];
+
+ for (const line of lines) {
+ if (line.startsWith("*")) {
+ current = line.replace("*", "").trim();
+ branches.push(current);
+ } else {
+ branches.push(line.trim());
+ }
+ }
+ return { current, branches };
+ }
+}
+
+module.exports = {
+ StatusStub,
+ BranchStub
+}
\ No newline at end of file
From f25e460f7e6c2892a28983b6e2114aeba716904e Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Wed, 16 Sep 2026 21:01:02 +1200
Subject: [PATCH 05/15] Add eslint.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
package-lock.json | 874 +++++++++++++++++++++++++++++++++++++++++++++-
package.json | 3 +-
2 files changed, 874 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index ccd0fdf..7af244b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,7 +15,69 @@
},
"devDependencies": {
"electron": "^44.3.0",
- "electron-builder": "^26.15.3"
+ "electron-builder": "^26.15.3",
+ "eslint": "^10.10.0"
+ }
+ },
+ "node_modules/@cacheable/memory": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz",
+ "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/utils": "^2.5.0",
+ "@keyv/bigmap": "^1.3.1",
+ "hookified": "^1.15.1",
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz",
+ "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.4.0",
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@cacheable/memory/node_modules/keyv": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@keyv/serialize": "^1.1.1"
+ }
+ },
+ "node_modules/@cacheable/utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz",
+ "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hashery": "^1.5.1",
+ "keyv": "^5.6.0"
+ }
+ },
+ "node_modules/@cacheable/utils/node_modules/keyv": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@keyv/serialize": "^1.1.1"
}
},
"node_modules/@codingame/monaco-vscode-api": {
@@ -705,6 +767,218 @@
"node": ">=14.14"
}
},
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz",
+ "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^3.0.5",
+ "debug": "^4.3.1",
+ "minimatch": "^10.2.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/brace-expansion": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@eslint/config-array/node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.8"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz",
+ "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz",
+ "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -718,6 +992,13 @@
"node": ">=18.0.0"
}
},
+ "node_modules/@keyv/serialize": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz",
+ "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@malept/cross-spawn-promise": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz",
@@ -890,6 +1171,20 @@
"@types/ms": "*"
}
},
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/fs-extra": {
"version": "9.0.13",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
@@ -907,6 +1202,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/keyv": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
@@ -977,6 +1279,29 @@
"node": "^20.17.0 || >=22.9.0"
}
},
+ "node_modules/acorn": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
+ "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -1404,6 +1729,20 @@
"node": ">=6.0.0"
}
},
+ "node_modules/cacheable": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz",
+ "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cacheable/memory": "^2.2.0",
+ "@cacheable/utils": "^2.5.0",
+ "hookified": "^1.15.0",
+ "keyv": "^5.6.0",
+ "qified": "^0.10.1"
+ }
+ },
"node_modules/cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
@@ -1433,6 +1772,16 @@
"node": ">=8"
}
},
+ "node_modules/cacheable/node_modules/keyv": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz",
+ "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@keyv/serialize": "^1.1.1"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -1686,6 +2035,13 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/defer-to-connect": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@@ -2125,7 +2481,6 @@
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"dev": true,
"license": "MIT",
- "optional": true,
"engines": {
"node": ">=10"
},
@@ -2133,6 +2488,224 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/eslint": {
+ "version": "10.10.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.10.0.tgz",
+ "integrity": "sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "packages/*"
+ ],
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.5",
+ "@eslint/config-helpers": "^0.7.0",
+ "@eslint/core": "^1.2.1",
+ "@eslint/plugin-kit": "^0.7.3",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "11.1.5 || >11.1.6 <12",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/eslint/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
+ "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/eslint/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.8"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/espree": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^5.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/exponential-backoff": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
@@ -2147,6 +2720,20 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/fast-uri": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz",
@@ -2182,6 +2769,16 @@
}
}
},
+ "node_modules/file-entry-cache": {
+ "version": "11.1.5",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz",
+ "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^6.1.23"
+ }
+ },
"node_modules/filelist": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
@@ -2192,6 +2789,42 @@
"minimatch": "^5.0.1"
}
},
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "6.1.23",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz",
+ "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cacheable": "^2.5.0",
+ "flatted": "^3.4.2",
+ "hookified": "^1.15.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.4",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz",
+ "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/form-data": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
@@ -2328,6 +2961,19 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
@@ -2488,6 +3134,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/hashery": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz",
+ "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^1.15.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
"node_modules/hasown": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
@@ -2501,6 +3160,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/hookified": {
+ "version": "1.15.1",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz",
+ "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/hosted-git-info": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
@@ -2563,6 +3229,26 @@
"node": ">= 14"
}
},
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@@ -2582,6 +3268,16 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
@@ -2592,6 +3288,19 @@
"node": ">=8"
}
},
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -2696,6 +3405,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@@ -2747,6 +3463,36 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
@@ -2981,6 +3727,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/node-abi": {
"version": "4.35.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz",
@@ -3132,6 +3885,24 @@
"wrappy": "1"
}
},
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/p-cancelable": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
@@ -3158,6 +3929,32 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -3289,6 +4086,16 @@
"node": "^12.20.0 || >=14"
}
},
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/proc-log": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz",
@@ -3353,6 +4160,16 @@
"once": "^1.3.1"
}
},
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/pvtsutils": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz",
@@ -3373,6 +4190,26 @@
"node": ">=16.0.0"
}
},
+ "node_modules/qified": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz",
+ "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hookified": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/qified/node_modules/hookified": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz",
+ "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
@@ -3854,6 +4691,19 @@
"dev": true,
"license": "0BSD"
},
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
"node_modules/type-fest": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
@@ -3925,6 +4775,16 @@
"node": ">=14.14"
}
},
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
"node_modules/utf8-byte-length": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz",
@@ -4041,6 +4901,16 @@
"node": "^18.17.0 || >=20.5.0"
}
},
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
diff --git a/package.json b/package.json
index 6da74ee..a0bbc16 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,8 @@
},
"devDependencies": {
"electron": "^44.3.0",
- "electron-builder": "^26.15.3"
+ "electron-builder": "^26.15.3",
+ "eslint": "^10.10.0"
},
"overrides": {
"dompurify": "^3.4.13"
From 9d8528267afb1300cfbce96b5f90db9c2d3addd3 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Wed, 16 Sep 2026 21:03:44 +1200
Subject: [PATCH 06/15] Potential fix for pull request finding 'CodeQL /
Incomplete string escaping or encoding'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitstub.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/UI/git_sidebar/gitstub.js b/UI/git_sidebar/gitstub.js
index 180b2d4..ed67a4b 100644
--- a/UI/git_sidebar/gitstub.js
+++ b/UI/git_sidebar/gitstub.js
@@ -21,7 +21,7 @@ class BranchStub {
for (const line of lines) {
if (line.startsWith("*")) {
- current = line.replace("*", "").trim();
+ current = line.replace(/^\*\s*/, "").trim();
branches.push(current);
} else {
branches.push(line.trim());
From bcdb7d2e1844299a47317cc914148253358fc87e Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Wed, 16 Sep 2026 09:08:55 +0000
Subject: [PATCH 07/15] Prevent shell injection in Git commit messages
---
UI/git_sidebar/gitman.js | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/UI/git_sidebar/gitman.js b/UI/git_sidebar/gitman.js
index a746caa..ca5c424 100644
--- a/UI/git_sidebar/gitman.js
+++ b/UI/git_sidebar/gitman.js
@@ -1,4 +1,4 @@
-const { exec } = require("child_process");
+const { exec, execFile } = require("child_process");
class GitManager {
constructor(options = {}) {
@@ -36,8 +36,21 @@ class GitManager {
}
async commit(message) { // Function to handle git commit -m ""
- const safe = message.replace(/\\/g, "\\\\").replace(/"/g, '\\"'); // Escape backslashes and double quotes in commit messages.
- return this.run(`git commit -m "${safe}"`);
+ return new Promise((resolve, reject) => {
+ execFile("git", ["commit", "-m", message], { cwd: this.cwd }, (err, stdout, stderr) => {
+ if (this.verbose) {
+ console.log("[GitManager] CMD: ", "git commit -m", message);
+ console.log("[GitManager] OUT: ", stdout);
+ console.log("[GitManager] ERR: ", stderr);
+ }
+ if (err) {
+ const msg = stdout.trim() || stderr.trim();
+ reject(msg);
+ return;
+ }
+ resolve(stdout.trim());
+ });
+ });
}
async status() { // Function to handle git status
@@ -61,4 +74,4 @@ class GitManager {
}
}
-module.exports = GitManager;
\ No newline at end of file
+module.exports = GitManager;
From c71207804a4d858d66b8254dfed1ddc5b05eb486 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Wed, 16 Sep 2026 21:37:30 +1200
Subject: [PATCH 08/15] Add Statis Stup and edit package.json
Added Statis Stub to gitstub.js for easy use in the upcomming gitui.js. Also added eslint to package.json so now developers can lint code on the fly with npm run lint.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitstub.js | 15 +++++++++++++-
eslint.config.mjs | 43 +++++++++++++++++++++++++++++++++++++++
package-lock.json | 38 +++++++++++++++++++++++++++++++++-
package.json | 7 +++++--
4 files changed, 99 insertions(+), 4 deletions(-)
create mode 100644 eslint.config.mjs
diff --git a/UI/git_sidebar/gitstub.js b/UI/git_sidebar/gitstub.js
index 180b2d4..322d22a 100644
--- a/UI/git_sidebar/gitstub.js
+++ b/UI/git_sidebar/gitstub.js
@@ -31,7 +31,20 @@ class BranchStub {
}
}
+class LogStub {
+ static parse(raw) {
+ if (!raw.trim()) return [];
+
+ return raw.split("\n").filter(Boolean).map(line => {
+ const [hash, author, message, date] = line.split("|");
+ return { hash, author, message, date };
+ });
+ }
+}
+
+
module.exports = {
StatusStub,
- BranchStub
+ BranchStub,
+ LogStub
}
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..e7d58d8
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,43 @@
+import js from "@eslint/js";
+import globals from "globals";
+import { defineConfig } from "eslint/config";
+
+export default defineConfig([
+ // Backend: Node + CommonJS (GitManager, stubs, runner, Electron main/preload)
+ {
+ files: ["main/**/*.js", "preload/**/*.js", "backend/**/*.js", "git/**/*.js"],
+ extends: [js.configs.recommended],
+ languageOptions: {
+ sourceType: "commonjs",
+ globals: {
+ ...globals.node,
+ ...globals.commonjs,
+ ...globals.es2021,
+
+ // Electron globals
+ BrowserWindow: "readonly",
+ ipcMain: "readonly",
+ ipcRenderer: "readonly",
+ contextBridge: "readonly"
+ }
+ }
+ },
+
+ // Renderer: Browser + AMD (Monaco)
+ {
+ files: ["renderer/**/*.js"],
+ extends: [js.configs.recommended],
+ languageOptions: {
+ sourceType: "script", // browser JS
+ globals: {
+ ...globals.browser,
+
+ // Monaco AMD loader
+ require: "readonly",
+ define: "readonly",
+
+ monaco: "readonly"
+ }
+ }
+ }
+]);
diff --git a/package-lock.json b/package-lock.json
index 7af244b..d8d46b0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,9 +14,11 @@
"vscode-ws-jsonrpc": "^3.5.0"
},
"devDependencies": {
+ "@eslint/js": "^10.0.1",
"electron": "^44.3.0",
"electron-builder": "^26.15.3",
- "eslint": "^10.10.0"
+ "eslint": "^10.10.0",
+ "globals": "^17.12.0"
}
},
"node_modules/@cacheable/memory": {
@@ -889,6 +891,27 @@
"node": "^20.19.0 || ^22.13.0 || >=24"
}
},
+ "node_modules/@eslint/js": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
+ "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "eslint": "^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@eslint/object-schema": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
@@ -3017,6 +3040,19 @@
"node": ">=10.0"
}
},
+ "node_modules/globals": {
+ "version": "17.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz",
+ "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/globalthis": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
diff --git a/package.json b/package.json
index a0bbc16..ef9740b 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,8 @@
"scripts": {
"start": "electron .",
"dist:win": "electron-builder --win nsis",
- "test": "node --test"
+ "test": "node --test",
+ "lint": "eslint ."
},
"keywords": [
"electron",
@@ -27,9 +28,11 @@
"vscode-ws-jsonrpc": "^3.5.0"
},
"devDependencies": {
+ "@eslint/js": "^10.0.1",
"electron": "^44.3.0",
"electron-builder": "^26.15.3",
- "eslint": "^10.10.0"
+ "eslint": "^10.10.0",
+ "globals": "^17.12.0"
},
"overrides": {
"dompurify": "^3.4.13"
From 7d542e60cea7f416104d4e18fcf490bb01c10641 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Thu, 17 Sep 2026 11:22:52 +1200
Subject: [PATCH 09/15] Add initial git ui setup and update website to reflect
git integration
Added the initial UI/git_sidebar/gitui.js file and update the documentation websote to reflect these changes.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitui.js | 32 ++++++++++++++++++++++++++++++++
docs/index.html | 1 +
styles/style1.css | 22 ++++++++++++++++++++++
3 files changed, 55 insertions(+)
create mode 100644 UI/git_sidebar/gitui.js
diff --git a/UI/git_sidebar/gitui.js b/UI/git_sidebar/gitui.js
new file mode 100644
index 0000000..0410a41
--- /dev/null
+++ b/UI/git_sidebar/gitui.js
@@ -0,0 +1,32 @@
+const { } = require('electron');
+const { } = require('./gitman');
+const { } = require('./gitstub');
+
+class GitUi {
+ constructor(containerEl, gitBar) {
+ this.gitman = new GitMan();
+ this.gitstub = new GitStub();
+ this.containerEl = containerEl;
+ this.gitBar = gitBar;
+ }
+
+ init_listners() {
+ this.containerEl.addEventListener('click', (e) => {
+ if (e.target.id === 'git-init-btn') {
+ this.gitman.init().then((output) => {
+ console.log("[GitUi MSG] : ", output);
+ });
+ }
+ });
+ }
+
+ render() {
+ if (!this.containerEl) return;
+
+ this.containerEl.innerHTML = `
+
+
+
+ `;
+ }
+}
\ No newline at end of file
diff --git a/docs/index.html b/docs/index.html
index 6e39ff5..3258427 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -43,6 +43,7 @@ Features
ANTI AI: The IDE has NO AI FEATURES
C support: This IDE nativly supports C and C++ syntax highlighting.
Python and WebDev Language support.
+ Git support.
diff --git a/styles/style1.css b/styles/style1.css
index a0977cf..4e73eea 100644
--- a/styles/style1.css
+++ b/styles/style1.css
@@ -578,3 +578,25 @@ body {
outline: 2px solid var(--vscode-focus-border);
outline-offset: -2px;
}
+
+.git_bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ height: 30px;
+ padding: 0 10px;
+ background-color: var(--vscode-toolbar-bg);
+ border-top: 1px solid var(--vscode-border);
+}
+
+.init_btn {
+ background-color: transparent;
+ color: var(--vscode-text-main);
+ border: 1px solid transparent;
+ border-radius: 4px;
+ padding: 4px 10px;
+ font-size: 12px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition: background-color 0.15s ease;
+}
\ No newline at end of file
From d4961fa79015c8992f86803b5367dbc8e04461b9 Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Wed, 16 Sep 2026 23:24:22 +0000
Subject: [PATCH 10/15] Fix Git sidebar command safety and lint errors
---
UI/git_sidebar/gitman.js | 22 ++++++++++++++++++++--
UI/git_sidebar/gitstub.js | 8 ++++----
eslint.config.mjs | 11 ++++++++++-
preload.js | 3 +--
4 files changed, 35 insertions(+), 9 deletions(-)
diff --git a/UI/git_sidebar/gitman.js b/UI/git_sidebar/gitman.js
index ca5c424..e85cea7 100644
--- a/UI/git_sidebar/gitman.js
+++ b/UI/git_sidebar/gitman.js
@@ -27,6 +27,24 @@ class GitManager {
});
}
+ runGit(operation, name) {
+ return new Promise((resolve, reject) => {
+ execFile("git", [operation, name], { cwd: this.cwd }, (err, stdout, stderr) => {
+ if (this.verbose) {
+ console.log("[GitManager] CMD: ", "git", operation, name);
+ console.log("[GitManager] OUT: ", stdout);
+ console.log("[GitManager] ERR: ", stderr);
+ }
+ if (err) {
+ const msg = stdout.trim() || stderr.trim();
+ reject(msg);
+ return;
+ }
+ resolve(stdout.trim());
+ });
+ });
+ }
+
async init() { // Function to handle git init
return this.run("git init");
}
@@ -66,11 +84,11 @@ class GitManager {
}
async branch(name) { // Function to handle git branch
- return this.run(`git branch ${name}`);
+ return this.runGit("branch", name);
}
async switch(name) { // Function to handle git switch branch
- return this.run(`git switch ${name}`);
+ return this.runGit("switch", name);
}
}
diff --git a/UI/git_sidebar/gitstub.js b/UI/git_sidebar/gitstub.js
index 777e22e..1a0d3c1 100644
--- a/UI/git_sidebar/gitstub.js
+++ b/UI/git_sidebar/gitstub.js
@@ -5,7 +5,7 @@ class StatusStub {
}
return raw.split("\n").map(line => {
- const code = line.slice(0, 2).trim();
+ const code = line.slice(0, 2);
const file = line.slice(2).trim();
return { code, file };
});
@@ -14,9 +14,9 @@ class StatusStub {
class BranchStub {
static parse(raw) {
- const lines = raw.split("\n").filter(Bolean);
+ const lines = raw.split("\n").filter(Boolean);
- let curr = null;
+ let current = null;
const branches = [];
for (const line of lines) {
@@ -47,4 +47,4 @@ module.exports = {
StatusStub,
BranchStub,
LogStub
-}
\ No newline at end of file
+}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index e7d58d8..23dbef4 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -5,7 +5,16 @@ import { defineConfig } from "eslint/config";
export default defineConfig([
// Backend: Node + CommonJS (GitManager, stubs, runner, Electron main/preload)
{
- files: ["main/**/*.js", "preload/**/*.js", "backend/**/*.js", "git/**/*.js"],
+ files: [
+ "main.js",
+ "preload.js",
+ "main/**/*.js",
+ "preload/**/*.js",
+ "backend/**/*.js",
+ "git/**/*.js",
+ "UI/git_sidebar/gitman.js",
+ "UI/git_sidebar/gitstub.js"
+ ],
extends: [js.configs.recommended],
languageOptions: {
sourceType: "commonjs",
diff --git a/preload.js b/preload.js
index 41a6e5e..73ecc4c 100644
--- a/preload.js
+++ b/preload.js
@@ -29,6 +29,5 @@ contextBridge.exposeInMainWorld('api', {
onPyrightMessage: (callback) => ipcRenderer.on('pyright:message', (_event, message) => callback(message)),
onPyrightStderr: (callback) => ipcRenderer.on('pyright:stderr', (_event, message) => callback(message)),
onPyrightError: (callback) => ipcRenderer.on('pyright:error', (_event, message) => callback(message)),
- onPyrightExit: (callback) => ipcRenderer.on('pyright:exit', (_event) => callback())
+ onPyrightExit: (callback) => ipcRenderer.on('pyright:exit', () => callback())
});
-
From d11368d7143fd5d91ba287f61d7bcd2bc7dadbeb Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Thu, 17 Sep 2026 11:28:25 +1200
Subject: [PATCH 11/15] Update labler workflow.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
.github/labeler.yml | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 49c1a75..fdbcde9 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -7,3 +7,6 @@ config:
theme:
- "themes/**"
+
+UI:
+ - "UI/**/**"
From 6efddaf9d1ff21e3701dd26f9d3b5d989f76cbd9 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Thu, 17 Sep 2026 15:18:32 +1200
Subject: [PATCH 12/15] Finnish basic git ui sidebar implamentation
Fix some errors in UI/git_sidebar/gitui.js that were related to not importing the right classes and functions/variables from files
Fixed 8 eslint errors related to redefining gitui.js file to use the propper cjs types.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitui.js | 20 ++++++++++++++------
eslint.config.mjs | 5 +++--
styles/style1.css | 12 ++++++++++++
3 files changed, 29 insertions(+), 8 deletions(-)
diff --git a/UI/git_sidebar/gitui.js b/UI/git_sidebar/gitui.js
index 0410a41..8414a6d 100644
--- a/UI/git_sidebar/gitui.js
+++ b/UI/git_sidebar/gitui.js
@@ -1,11 +1,10 @@
-const { } = require('electron');
-const { } = require('./gitman');
-const { } = require('./gitstub');
+const { GitManager } = require('./gitman');
+const { StatusStub } = require('./gitstub');
class GitUi {
constructor(containerEl, gitBar) {
- this.gitman = new GitMan();
- this.gitstub = new GitStub();
+ this.gitman = new GitManager();
+ this.statusStub = new StatusStub();
this.containerEl = containerEl;
this.gitBar = gitBar;
}
@@ -18,6 +17,14 @@ class GitUi {
});
}
});
+
+ this.containerEl.addEventListener('click', (e) => {
+ if (e.target.id === 'git-add-btn') {
+ this.gitman.init().then((output) => {
+ console.log("[GitUi MSG] : ", output);
+ });
+ }
+ });
}
render() {
@@ -25,7 +32,8 @@ class GitUi {
this.containerEl.innerHTML = `
-
+
+
`;
}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 23dbef4..4d6d1cb 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -12,8 +12,9 @@ export default defineConfig([
"preload/**/*.js",
"backend/**/*.js",
"git/**/*.js",
- "UI/git_sidebar/gitman.js",
- "UI/git_sidebar/gitstub.js"
+ "UI/git_sidebar/**.js",
+ "UI/codebase_indexer/**.js",
+ "UI/tab_manager/**.js"
],
extends: [js.configs.recommended],
languageOptions: {
diff --git a/styles/style1.css b/styles/style1.css
index 4e73eea..2ccc7ce 100644
--- a/styles/style1.css
+++ b/styles/style1.css
@@ -599,4 +599,16 @@ body {
font-family: var(--font-ui);
cursor: pointer;
transition: background-color 0.15s ease;
+}
+
+.add_btn {
+ background-color: transparent;
+ color: var(--vscode-text-main);
+ border: 1px solid transparent;
+ border-radius: 4px;
+ padding: 4px 10px;
+ font-size: 12px;
+ font-family: var(--font-ui);
+ cursor: pointer;
+ transition: background-color 0.15s ease;
}
\ No newline at end of file
From 3f9d9a172d4ba8fa6070a8639c4ebef7c28baf30 Mon Sep 17 00:00:00 2001
From: G-type <162748908+gtref@users.noreply.github.com>
Date: Thu, 17 Sep 2026 15:44:00 +1200
Subject: [PATCH 13/15] Add basic patch file gen to git sidepanel
Add patchgen.js from another project to generate diff and patch files based on git history.
Signed-off-by: G-type <162748908+gtref@users.noreply.github.com>
---
UI/git_sidebar/gitui.js | 11 +++++++++
UI/tab_manager/tabman.js | 3 +++
eslint.config.mjs | 3 ++-
patchgen.js | 53 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 69 insertions(+), 1 deletion(-)
create mode 100644 patchgen.js
diff --git a/UI/git_sidebar/gitui.js b/UI/git_sidebar/gitui.js
index 8414a6d..cf601ec 100644
--- a/UI/git_sidebar/gitui.js
+++ b/UI/git_sidebar/gitui.js
@@ -1,11 +1,13 @@
const { GitManager } = require('./gitman');
const { StatusStub } = require('./gitstub');
+const { generateUnifiedDiff } = require('../../patchgen')
class GitUi {
constructor(containerEl, gitBar) {
this.gitman = new GitManager();
this.statusStub = new StatusStub();
this.containerEl = containerEl;
+ this.patch = new generateUnifiedDiff();
this.gitBar = gitBar;
}
@@ -25,6 +27,14 @@ class GitUi {
});
}
});
+
+ this.containerEl.addEventListener('click', (e) => {
+ if (e.target.id === 'git-patch-btn') {
+ this.patch.generateUnifiedDiff().then((output) => {
+ console.log("[GitUi MSG] : ", output);
+ });
+ }
+ });
}
render() {
@@ -34,6 +44,7 @@ class GitUi {
+
`;
}
diff --git a/UI/tab_manager/tabman.js b/UI/tab_manager/tabman.js
index 933ef08..27f429d 100644
--- a/UI/tab_manager/tabman.js
+++ b/UI/tab_manager/tabman.js
@@ -139,3 +139,6 @@ function escapeHtml(value) {
'"': '"'
}[char]));
}
+
+// TODO: Needs fixing so module exports work.
+// module.exports = TabManager;
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 4d6d1cb..13159d0 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -14,7 +14,8 @@ export default defineConfig([
"git/**/*.js",
"UI/git_sidebar/**.js",
"UI/codebase_indexer/**.js",
- "UI/tab_manager/**.js"
+ "UI/tab_manager/**.js",
+ "patchgen.js"
],
extends: [js.configs.recommended],
languageOptions: {
diff --git a/patchgen.js b/patchgen.js
new file mode 100644
index 0000000..952a714
--- /dev/null
+++ b/patchgen.js
@@ -0,0 +1,53 @@
+import fs from "fs";
+import path from "path";
+
+export function generateUnifiedDiff(oldFile, newFile) {
+ const oldText = fs.readFileSync(oldFile, "utf8").split("\n");
+ const newText = fs.readFileSync(newFile, "utf8").split("\n");
+
+ let patch = "";
+ patch += `--- ${path.basename(oldFile)}\n`;
+ patch += `+++ ${path.basename(newFile)}\n`;
+
+ let i = 0;
+ let j = 0;
+
+ while (i < oldText.length || j < newText.length) {
+ const oldLine = oldText[i];
+ const newLine = newText[j];
+
+ if (oldLine === newLine) {
+ i++;
+ j++;
+ continue;
+ }
+
+ // Both lines exist but differ
+ if (oldLine !== undefined && newLine !== undefined) {
+ patch += `@@ -${i + 1} +${j + 1} @@\n`;
+ patch += `-${oldLine}\n`;
+ patch += `+${newLine}\n`;
+ i++;
+ j++;
+ continue;
+ }
+
+ // Old line removed
+ if (oldLine !== undefined) {
+ patch += `@@ -${i + 1} +${j} @@\n`;
+ patch += `-${oldLine}\n`;
+ i++;
+ continue;
+ }
+
+ // New line added
+ if (newLine !== undefined) {
+ patch += `@@ -${i} +${j + 1} @@\n`;
+ patch += `+${newLine}\n`;
+ j++;
+ continue;
+ }
+ }
+
+ return patch;
+}
From 1df8890f771a0a87f3966e60970e0454d1922f44 Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Fri, 18 Sep 2026 01:29:34 +0000
Subject: [PATCH 14/15] Connect Git sidebar controls and fix patch generation
Wire file selection and staging, correct unified diff ranges, and add regression tests.
---
UI/git_sidebar/gitui.js | 29 ++++++++++-----
UI/git_sidebar/gitui.test.js | 71 ++++++++++++++++++++++++++++++++++++
index.html | 3 ++
patchgen.js | 12 +++---
patchgen.test.js | 36 ++++++++++++++++++
preload.js | 14 ++++++-
styles/style1.css | 24 +++++++++++-
7 files changed, 172 insertions(+), 17 deletions(-)
create mode 100644 UI/git_sidebar/gitui.test.js
create mode 100644 patchgen.test.js
diff --git a/UI/git_sidebar/gitui.js b/UI/git_sidebar/gitui.js
index cf601ec..156eb31 100644
--- a/UI/git_sidebar/gitui.js
+++ b/UI/git_sidebar/gitui.js
@@ -1,13 +1,11 @@
-const { GitManager } = require('./gitman');
-const { StatusStub } = require('./gitstub');
-const { generateUnifiedDiff } = require('../../patchgen')
+const GitManager = require('./gitman');
+const { generateUnifiedDiff } = require('../../patchgen');
class GitUi {
constructor(containerEl, gitBar) {
this.gitman = new GitManager();
- this.statusStub = new StatusStub();
this.containerEl = containerEl;
- this.patch = new generateUnifiedDiff();
+ this.patch = generateUnifiedDiff;
this.gitBar = gitBar;
}
@@ -22,7 +20,7 @@ class GitUi {
this.containerEl.addEventListener('click', (e) => {
if (e.target.id === 'git-add-btn') {
- this.gitman.init().then((output) => {
+ this.gitman.addAll().then((output) => {
console.log("[GitUi MSG] : ", output);
});
}
@@ -30,9 +28,14 @@ class GitUi {
this.containerEl.addEventListener('click', (e) => {
if (e.target.id === 'git-patch-btn') {
- this.patch.generateUnifiedDiff().then((output) => {
- console.log("[GitUi MSG] : ", output);
- });
+ const oldFile = this.containerEl.querySelector('#git-old-file').files[0];
+ const newFile = this.containerEl.querySelector('#git-new-file').files[0];
+ if (!oldFile || !newFile) return;
+
+ const oldFilePath = this.gitBar.getPathForFile(oldFile);
+ const newFilePath = this.gitBar.getPathForFile(newFile);
+ const output = this.patch(oldFilePath, newFilePath);
+ console.log("[GitUi MSG] : ", output);
}
});
}
@@ -44,8 +47,14 @@ class GitUi {
+
+
+
+
`;
}
-}
\ No newline at end of file
+}
+
+module.exports = { GitUi };
diff --git a/UI/git_sidebar/gitui.test.js b/UI/git_sidebar/gitui.test.js
new file mode 100644
index 0000000..6976e9c
--- /dev/null
+++ b/UI/git_sidebar/gitui.test.js
@@ -0,0 +1,71 @@
+const assert = require('node:assert/strict');
+const test = require('node:test');
+const { GitUi } = require('./gitui');
+const { generateUnifiedDiff } = require('../../patchgen');
+
+test('exports GitUi and stores the diff generator function', () => {
+ const gitUi = new GitUi({}, {});
+
+ assert.equal(gitUi.patch, generateUnifiedDiff);
+});
+
+test('renders the Git controls into its mount element', () => {
+ const container = { innerHTML: '' };
+ const gitUi = new GitUi(container, {});
+
+ gitUi.render();
+
+ assert.match(container.innerHTML, /id="git-add-btn"/);
+ assert.match(container.innerHTML, /id="git-old-file"/);
+ assert.match(container.innerHTML, /id="git-new-file"/);
+ assert.match(container.innerHTML, /id="git-patch-btn"/);
+});
+
+test('the Add control stages all files', () => {
+ const listeners = [];
+ const container = {
+ addEventListener: (_type, listener) => listeners.push(listener)
+ };
+ const gitUi = new GitUi(container, {});
+ let addAllCalls = 0;
+ gitUi.gitman = {
+ init: () => Promise.resolve(''),
+ addAll: () => {
+ addAllCalls++;
+ return Promise.resolve('');
+ }
+ };
+
+ gitUi.init_listners();
+ listeners.forEach((listener) => listener({ target: { id: 'git-add-btn' } }));
+
+ assert.equal(addAllCalls, 1);
+});
+
+test('generates a patch synchronously from the selected file paths', () => {
+ const listeners = [];
+ const oldFile = { name: 'old.txt' };
+ const newFile = { name: 'new.txt' };
+ const inputs = {
+ '#git-old-file': { files: [oldFile] },
+ '#git-new-file': { files: [newFile] }
+ };
+ const container = {
+ addEventListener: (_type, listener) => listeners.push(listener),
+ querySelector: (selector) => inputs[selector]
+ };
+ const gitBar = {
+ getPathForFile: (file) => `/selected/${file.name}`
+ };
+ const gitUi = new GitUi(container, gitBar);
+ let selectedPaths;
+ gitUi.patch = (oldFilePath, newFilePath) => {
+ selectedPaths = [oldFilePath, newFilePath];
+ return 'patch text';
+ };
+
+ gitUi.init_listners();
+ listeners.forEach((listener) => listener({ target: { id: 'git-patch-btn' } }));
+
+ assert.deepEqual(selectedPaths, ['/selected/old.txt', '/selected/new.txt']);
+});
diff --git a/index.html b/index.html
index 31e542e..a3e6a89 100644
--- a/index.html
+++ b/index.html
@@ -82,6 +82,9 @@
+
diff --git a/patchgen.js b/patchgen.js
index 952a714..1d715bd 100644
--- a/patchgen.js
+++ b/patchgen.js
@@ -1,7 +1,7 @@
-import fs from "fs";
-import path from "path";
+const fs = require("fs");
+const path = require("path");
-export function generateUnifiedDiff(oldFile, newFile) {
+function generateUnifiedDiff(oldFile, newFile) {
const oldText = fs.readFileSync(oldFile, "utf8").split("\n");
const newText = fs.readFileSync(newFile, "utf8").split("\n");
@@ -34,7 +34,7 @@ export function generateUnifiedDiff(oldFile, newFile) {
// Old line removed
if (oldLine !== undefined) {
- patch += `@@ -${i + 1} +${j} @@\n`;
+ patch += `@@ -${i + 1},1 +${j},0 @@\n`;
patch += `-${oldLine}\n`;
i++;
continue;
@@ -42,7 +42,7 @@ export function generateUnifiedDiff(oldFile, newFile) {
// New line added
if (newLine !== undefined) {
- patch += `@@ -${i} +${j + 1} @@\n`;
+ patch += `@@ -${i},0 +${j + 1},1 @@\n`;
patch += `+${newLine}\n`;
j++;
continue;
@@ -51,3 +51,5 @@ export function generateUnifiedDiff(oldFile, newFile) {
return patch;
}
+
+module.exports = { generateUnifiedDiff };
diff --git a/patchgen.test.js b/patchgen.test.js
new file mode 100644
index 0000000..c177c0b
--- /dev/null
+++ b/patchgen.test.js
@@ -0,0 +1,36 @@
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const os = require('node:os');
+const path = require('node:path');
+const test = require('node:test');
+const { generateUnifiedDiff } = require('./patchgen');
+
+function withFiles(oldContent, newContent, assertion) {
+ const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'satiscode-patchgen-'));
+ const oldFile = path.join(directory, 'old.txt');
+ const newFile = path.join(directory, 'new.txt');
+
+ try {
+ fs.writeFileSync(oldFile, oldContent);
+ fs.writeFileSync(newFile, newContent);
+ assertion(generateUnifiedDiff(oldFile, newFile));
+ } finally {
+ fs.rmSync(directory, { recursive: true, force: true });
+ }
+}
+
+test('exports generateUnifiedDiff through CommonJS', () => {
+ assert.equal(typeof generateUnifiedDiff, 'function');
+});
+
+test('uses an explicit zero-length new range for a deletion', () => {
+ withFiles('first\nremoved', 'first', (patch) => {
+ assert.match(patch, /@@ -2,1 \+1,0 @@\n-removed\n/);
+ });
+});
+
+test('uses an explicit zero-length old range for an addition', () => {
+ withFiles('first', 'first\nadded', (patch) => {
+ assert.match(patch, /@@ -1,0 \+2,1 @@\n\+added\n/);
+ });
+});
diff --git a/preload.js b/preload.js
index 73ecc4c..a12ee75 100644
--- a/preload.js
+++ b/preload.js
@@ -1,5 +1,17 @@
-const { contextBridge, ipcRenderer } = require('electron');
+const { contextBridge, ipcRenderer, webUtils } = require('electron');
const { indexer } = require('./UI/codebase_indexer/cb_index');
+const { GitUi } = require('./UI/git_sidebar/gitui');
+
+globalThis.addEventListener('DOMContentLoaded', () => {
+ const container = globalThis.document.getElementById('git-sidebar-mount');
+ const gitBar = {
+ getPathForFile: (file) => webUtils.getPathForFile(file)
+ };
+
+ const gitUi = new GitUi(container, gitBar);
+ gitUi.render();
+ gitUi.init_listners();
+});
contextBridge.exposeInMainWorld('api', {
openFile: () => ipcRenderer.invoke('dialog:openFile'),
diff --git a/styles/style1.css b/styles/style1.css
index 2ccc7ce..2414d4f 100644
--- a/styles/style1.css
+++ b/styles/style1.css
@@ -195,6 +195,28 @@ body {
padding-left: 0;
}
+#git-sidebar {
+ width: 220px;
+ padding: 10px;
+ background-color: var(--vscode-sidebar-bg);
+ border-right: 1px solid var(--vscode-border);
+ overflow-y: auto;
+}
+
+#git-sidebar .git_bar {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 8px;
+ height: auto;
+ padding: 0;
+}
+
+#git-sidebar input,
+#git-sidebar button {
+ width: 100%;
+}
+
.tree-entry {
display: flex;
align-items: center;
@@ -611,4 +633,4 @@ body {
font-family: var(--font-ui);
cursor: pointer;
transition: background-color 0.15s ease;
-}
\ No newline at end of file
+}
From fcec02a7b5ca3ba03612f89170cbb47742a3beda Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Fri, 18 Sep 2026 01:39:33 +0000
Subject: [PATCH 15/15] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Integ?=
=?UTF-8?q?rate=20Git=20Sidebar=20with=20Main-Process=20Workspace=20Operat?=
=?UTF-8?q?ions?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
UI/git_sidebar/gitman.js | 99 +++++--------------
UI/git_sidebar/gitman.test.js | 16 +++
UI/git_sidebar/gitstub.js | 6 +-
UI/git_sidebar/gitstub.test.js | 10 ++
UI/git_sidebar/gitui.js | 173 +++++++++++++++++++++++----------
UI/git_sidebar/gitui.test.js | 118 +++++++++++-----------
eslint.config.mjs | 21 +++-
index.html | 7 +-
main.js | 5 +-
main/git-service.js | 37 +++++++
main/git-service.test.js | 18 ++++
preload.js | 20 ++--
styles/style1.css | 67 ++++++-------
13 files changed, 364 insertions(+), 233 deletions(-)
create mode 100644 UI/git_sidebar/gitman.test.js
create mode 100644 UI/git_sidebar/gitstub.test.js
create mode 100644 main/git-service.js
create mode 100644 main/git-service.test.js
diff --git a/UI/git_sidebar/gitman.js b/UI/git_sidebar/gitman.js
index e85cea7..dcfabc7 100644
--- a/UI/git_sidebar/gitman.js
+++ b/UI/git_sidebar/gitman.js
@@ -1,94 +1,47 @@
-const { exec, execFile } = require("child_process");
-
class GitManager {
constructor(options = {}) {
- // Directory where Git commands will run
- this.cwd = options.cwd || process.cwd();
-
- // Optional: enable verbose logging
- this.verbose = options.verbose || false;
- }
-
- run(cmd) {
- return new Promise((resolve, reject) => {
- exec(cmd, { cwd: this.cwd }, (err, stdout, stderr) => {
- if (this.verbose) {
- console.log("[GitManager] CMD: ", cmd);
- console.log("[GitManager] OUT: ", stdout);
- console.log("[GitManager] ERR: ", stderr);
- }
- if (err) {
- const msg = stdout.trim() || stderr.trim();
- reject(msg);
- return;
- }
- resolve(stdout.trim());
- });
- });
- }
-
- runGit(operation, name) {
- return new Promise((resolve, reject) => {
- execFile("git", [operation, name], { cwd: this.cwd }, (err, stdout, stderr) => {
- if (this.verbose) {
- console.log("[GitManager] CMD: ", "git", operation, name);
- console.log("[GitManager] OUT: ", stdout);
- console.log("[GitManager] ERR: ", stderr);
- }
- if (err) {
- const msg = stdout.trim() || stderr.trim();
- reject(msg);
- return;
- }
- resolve(stdout.trim());
- });
- });
+ this.api = options.api;
+ this.workspace = options.workspace || null;
}
- async init() { // Function to handle git init
- return this.run("git init");
+ setWorkspace(workspace) {
+ this.workspace = workspace || null;
}
- async addAll() { // Function to handle git add .
- return this.run("git add .");
+ async run(operation, ...args) {
+ if (!this.workspace) throw new Error('Open a folder to use Git.');
+ if (!this.api || typeof this.api.run !== 'function') throw new Error('Git API is unavailable.');
+ const result = await this.api.run(operation, this.workspace, args);
+ if (!result.ok) {
+ const error = new Error(result.stderr || result.stdout || 'Git command failed.');
+ error.code = result.code;
+ throw error;
+ }
+ return result.stdout;
}
- async commit(message) { // Function to handle git commit -m ""
- return new Promise((resolve, reject) => {
- execFile("git", ["commit", "-m", message], { cwd: this.cwd }, (err, stdout, stderr) => {
- if (this.verbose) {
- console.log("[GitManager] CMD: ", "git commit -m", message);
- console.log("[GitManager] OUT: ", stdout);
- console.log("[GitManager] ERR: ", stderr);
- }
- if (err) {
- const msg = stdout.trim() || stderr.trim();
- reject(msg);
- return;
- }
- resolve(stdout.trim());
- });
- });
+ async init() {
+ return this.run('init');
}
- async status() { // Function to handle git status
- return this.run("git status --short");
+ async addAll() {
+ return this.run('addAll');
}
- async push() { // Function to handle git push
- return this.run("git push");
+ async commit(message) {
+ return this.run('commit', message);
}
- async pull() { // Function to handle git pull
- return this.run("git pull");
+ async status() {
+ return this.run('status');
}
- async branch(name) { // Function to handle git branch
- return this.runGit("branch", name);
+ async push() {
+ return this.run('push');
}
- async switch(name) { // Function to handle git switch branch
- return this.runGit("switch", name);
+ async pull() {
+ return this.run('pull');
}
}
diff --git a/UI/git_sidebar/gitman.test.js b/UI/git_sidebar/gitman.test.js
new file mode 100644
index 0000000..8c82933
--- /dev/null
+++ b/UI/git_sidebar/gitman.test.js
@@ -0,0 +1,16 @@
+const assert = require('node:assert/strict');
+const test = require('node:test');
+const GitManager = require('./gitman');
+
+test('passes the selected workspace and arguments to the Git bridge', async () => {
+ let request;
+ const manager = new GitManager({ api: { run: async (...args) => { request = args; return { ok: true, stdout: 'done' }; } } });
+ manager.setWorkspace('/selected/workspace');
+ assert.equal(await manager.commit('message with spaces'), 'done');
+ assert.deepEqual(request, ['commit', '/selected/workspace', ['message with spaces']]);
+});
+
+test('rejects structured bridge errors', async () => {
+ const manager = new GitManager({ workspace: '/workspace', api: { run: async () => ({ ok: false, stderr: 'failed', code: 1 }) } });
+ await assert.rejects(manager.status(), /failed/);
+});
diff --git a/UI/git_sidebar/gitstub.js b/UI/git_sidebar/gitstub.js
index 1a0d3c1..2db84e7 100644
--- a/UI/git_sidebar/gitstub.js
+++ b/UI/git_sidebar/gitstub.js
@@ -1,10 +1,10 @@
class StatusStub {
static parse(raw) {
- if (!raw.trim()) {
+ if (!raw || !raw.trim()) {
return [];
}
- return raw.split("\n").map(line => {
+ return raw.split(/\r?\n/).filter(Boolean).map(line => {
const code = line.slice(0, 2);
const file = line.slice(2).trim();
return { code, file };
@@ -47,4 +47,4 @@ module.exports = {
StatusStub,
BranchStub,
LogStub
-}
+};
diff --git a/UI/git_sidebar/gitstub.test.js b/UI/git_sidebar/gitstub.test.js
new file mode 100644
index 0000000..17f1999
--- /dev/null
+++ b/UI/git_sidebar/gitstub.test.js
@@ -0,0 +1,10 @@
+const assert = require('node:assert/strict');
+const test = require('node:test');
+const { StatusStub } = require('./gitstub');
+
+test('parses modified, added, deleted, renamed, and untracked files', () => {
+ assert.deepEqual(StatusStub.parse(' M modified.js\nA added.js\n D deleted.js\nR old.js -> new.js\n?? untracked.js'), [
+ { code: ' M', file: 'modified.js' }, { code: 'A ', file: 'added.js' }, { code: ' D', file: 'deleted.js' },
+ { code: 'R ', file: 'old.js -> new.js' }, { code: '??', file: 'untracked.js' }
+ ]);
+});
diff --git a/UI/git_sidebar/gitui.js b/UI/git_sidebar/gitui.js
index 156eb31..5b0bc14 100644
--- a/UI/git_sidebar/gitui.js
+++ b/UI/git_sidebar/gitui.js
@@ -1,60 +1,135 @@
const GitManager = require('./gitman');
-const { generateUnifiedDiff } = require('../../patchgen');
+const { StatusStub } = require('./gitstub');
class GitUi {
- constructor(containerEl, gitBar) {
- this.gitman = new GitManager();
- this.containerEl = containerEl;
- this.patch = generateUnifiedDiff;
- this.gitBar = gitBar;
+ constructor(containerEl, options = {}) {
+ this.containerEl = containerEl;
+ this.gitman = options.gitman || new GitManager({ api: options.gitApi });
+ this.workspace = null;
+ this.state = { mode: 'empty', entries: [], message: 'Open a folder to use Git.', messageType: 'info' };
+ this.listening = false;
+ }
+
+ async setWorkspace(workspace) {
+ this.workspace = workspace || null;
+ this.gitman.setWorkspace(this.workspace);
+ if (!this.workspace) {
+ this.state = { mode: 'empty', entries: [], message: 'Open a folder to use Git.', messageType: 'info' };
+ this.render();
+ return;
+ }
+ await this.refresh();
+ }
+
+ initListeners() {
+ if (!this.containerEl || this.listening) return;
+ this.listening = true;
+ this.containerEl.addEventListener('click', (event) => this.handleClick(event));
+ this.containerEl.addEventListener('input', (event) => {
+ if (event.target.id === 'git-commit-message') this.renderActionState();
+ });
+ }
+
+ init_listners() { this.initListeners(); }
+
+ async handleClick(event) {
+ const action = event.target.dataset?.gitAction;
+ if (!action) return;
+ if (action === 'refresh') return this.refresh();
+ const operations = { init: 'init', stage: 'addAll', commit: 'commit', pull: 'pull', push: 'push' };
+ const method = operations[action];
+ if (!method) return;
+ const message = action === 'commit' ? this.containerEl.querySelector('#git-commit-message')?.value.trim() : undefined;
+ if (action === 'commit' && !message) return;
+ await this.runAction(method, message);
+ }
+
+ async runAction(method, argument) {
+ this.setMessage(`${this.actionLabel(method)}…`, 'loading');
+ try {
+ const output = await this.gitman[method](argument);
+ await this.refresh(`${this.actionLabel(method)} completed${output ? `: ${output}` : '.'}`);
+ } catch (error) {
+ this.setMessage(error.message || String(error), 'error');
}
+ }
- init_listners() {
- this.containerEl.addEventListener('click', (e) => {
- if (e.target.id === 'git-init-btn') {
- this.gitman.init().then((output) => {
- console.log("[GitUi MSG] : ", output);
- });
- }
- });
-
- this.containerEl.addEventListener('click', (e) => {
- if (e.target.id === 'git-add-btn') {
- this.gitman.addAll().then((output) => {
- console.log("[GitUi MSG] : ", output);
- });
- }
- });
-
- this.containerEl.addEventListener('click', (e) => {
- if (e.target.id === 'git-patch-btn') {
- const oldFile = this.containerEl.querySelector('#git-old-file').files[0];
- const newFile = this.containerEl.querySelector('#git-new-file').files[0];
- if (!oldFile || !newFile) return;
-
- const oldFilePath = this.gitBar.getPathForFile(oldFile);
- const newFilePath = this.gitBar.getPathForFile(newFile);
- const output = this.patch(oldFilePath, newFilePath);
- console.log("[GitUi MSG] : ", output);
- }
- });
+ actionLabel(method) {
+ return { init: 'Initializing repository', addAll: 'Staging files', commit: 'Committing changes', pull: 'Pulling changes', push: 'Pushing changes' }[method];
+ }
+
+ async refresh(successMessage = '') {
+ if (!this.workspace) return;
+ this.state = { ...this.state, mode: 'loading', message: 'Loading repository status…', messageType: 'loading' };
+ this.render();
+ try {
+ const raw = await this.gitman.status();
+ this.state = { mode: 'repository', entries: StatusStub.parse(raw), message: successMessage || 'Repository status is up to date.', messageType: successMessage ? 'success' : 'info' };
+ } catch (error) {
+ if (/not a git repository/i.test(error.message || '')) {
+ this.state = { mode: 'non-repository', entries: [], message: 'This folder is not a Git repository.', messageType: 'info' };
+ } else {
+ this.state = { mode: 'error', entries: [], message: error.message || String(error), messageType: 'error' };
+ }
}
+ this.render();
+ }
+
+ setMessage(message, messageType) {
+ this.state.message = message;
+ this.state.messageType = messageType;
+ this.render();
+ }
+
+ create(tagName, properties = {}, text = '') {
+ const element = this.containerEl.ownerDocument.createElement(tagName);
+ Object.assign(element, properties);
+ if (text) element.textContent = text;
+ return element;
+ }
- render() {
- if (!this.containerEl) return;
-
- this.containerEl.innerHTML = `
-
-
-
-
-
-
-
-
-
- `;
+ button(label, action, disabled = false) {
+ const button = this.create('button', { type: 'button', disabled }, label);
+ button.dataset.gitAction = action;
+ return button;
+ }
+
+ render() {
+ if (!this.containerEl || !this.containerEl.ownerDocument) return;
+ const panel = this.create('section', { className: 'git-panel' });
+ panel.append(this.create('h2', { className: 'git-title' }, 'Source Control'));
+ const status = this.create('p', { className: `git-message git-message--${this.state.messageType}`, role: this.state.messageType === 'error' ? 'alert' : 'status' }, this.state.message);
+ panel.append(status);
+
+ if (this.state.mode === 'non-repository') {
+ panel.append(this.button('Init repository', 'init'));
+ } else if (this.state.mode === 'repository' || this.state.mode === 'loading' || this.state.mode === 'error') {
+ const busy = this.state.mode === 'loading' || this.state.messageType === 'loading';
+ const toolbar = this.create('div', { className: 'git-actions' });
+ toolbar.append(this.button('Refresh', 'refresh', busy), this.button('Stage all', 'stage', busy || !this.state.entries.length), this.button('Pull', 'pull', busy), this.button('Push', 'push', busy));
+ panel.append(toolbar);
+ const input = this.create('input', { id: 'git-commit-message', className: 'git-commit-input', type: 'text', placeholder: 'Commit message', disabled: busy || !this.state.entries.length });
+ input.setAttribute('aria-label', 'Commit message');
+ panel.append(input, this.button('Commit', 'commit', true));
+ const list = this.create('ul', { className: 'git-status-list' });
+ list.setAttribute('aria-label', 'Changed files');
+ if (!this.state.entries.length) list.append(this.create('li', { className: 'git-clean' }, 'No changes'));
+ for (const entry of this.state.entries) {
+ const item = this.create('li', { className: 'git-status-entry' });
+ item.append(this.create('span', { className: 'git-status-code' }, entry.code), this.create('span', { className: 'git-status-file', title: entry.file }, entry.file));
+ list.append(item);
+ }
+ panel.append(list);
}
+ this.containerEl.replaceChildren(panel);
+ this.renderActionState();
+ }
+
+ renderActionState() {
+ const input = this.containerEl?.querySelector?.('#git-commit-message');
+ const commit = this.containerEl?.querySelector?.('[data-git-action="commit"]');
+ if (input && commit) commit.disabled = input.disabled || !input.value.trim();
+ }
}
module.exports = { GitUi };
diff --git a/UI/git_sidebar/gitui.test.js b/UI/git_sidebar/gitui.test.js
index 6976e9c..dab0f42 100644
--- a/UI/git_sidebar/gitui.test.js
+++ b/UI/git_sidebar/gitui.test.js
@@ -1,71 +1,71 @@
const assert = require('node:assert/strict');
const test = require('node:test');
const { GitUi } = require('./gitui');
-const { generateUnifiedDiff } = require('../../patchgen');
-test('exports GitUi and stores the diff generator function', () => {
- const gitUi = new GitUi({}, {});
+class FakeElement {
+ constructor(tagName, document) { this.tagName = tagName; this.ownerDocument = document; this.children = []; this.dataset = {}; this.disabled = false; this.value = ''; }
+ append(...children) { this.children.push(...children); }
+ replaceChildren(...children) { this.children = children; }
+ setAttribute(name, value) { this[name] = value; }
+ addEventListener() {}
+ querySelector(selector) {
+ const matches = (element) => selector.startsWith('#') ? element.id === selector.slice(1) : selector === '[data-git-action="commit"]' && element.dataset.gitAction === 'commit';
+ const visit = (element) => matches(element) ? element : element.children.map(visit).find(Boolean);
+ return visit(this);
+ }
+ get textContent() { return this._text || this.children.map((child) => child.textContent).join(' '); }
+ set textContent(value) { this._text = value; }
+}
- assert.equal(gitUi.patch, generateUnifiedDiff);
-});
-
-test('renders the Git controls into its mount element', () => {
- const container = { innerHTML: '' };
- const gitUi = new GitUi(container, {});
+function createContainer() {
+ const document = { createElement: (tagName) => new FakeElement(tagName, document) };
+ return new FakeElement('div', document);
+}
- gitUi.render();
+function deferred() {
+ let resolve;
+ const promise = new Promise((done) => { resolve = done; });
+ return { promise, resolve };
+}
- assert.match(container.innerHTML, /id="git-add-btn"/);
- assert.match(container.innerHTML, /id="git-old-file"/);
- assert.match(container.innerHTML, /id="git-new-file"/);
- assert.match(container.innerHTML, /id="git-patch-btn"/);
+test('renders empty, loading, repository, success, non-repository, and error states', async () => {
+ const container = createContainer();
+ const pending = deferred();
+ const gitman = { setWorkspace() {}, status: () => pending.promise };
+ const ui = new GitUi(container, { gitman });
+ ui.render();
+ assert.match(container.textContent, /Open a folder/);
+ const refresh = ui.setWorkspace('/workspace');
+ assert.match(container.textContent, /Loading repository status/);
+ pending.resolve(' M changed.js\n?? new.js');
+ await refresh;
+ assert.match(container.textContent, /changed\.js/);
+ ui.setMessage('Staging files completed.', 'success');
+ assert.match(container.textContent, /completed/);
+ gitman.status = async () => { throw new Error('fatal: not a git repository'); };
+ await ui.refresh();
+ assert.match(container.textContent, /Init repository/);
+ gitman.status = async () => { throw new Error('permission denied'); };
+ await ui.refresh();
+ assert.match(container.textContent, /permission denied/);
});
-test('the Add control stages all files', () => {
- const listeners = [];
- const container = {
- addEventListener: (_type, listener) => listeners.push(listener)
- };
- const gitUi = new GitUi(container, {});
- let addAllCalls = 0;
- gitUi.gitman = {
- init: () => Promise.resolve(''),
- addAll: () => {
- addAllCalls++;
- return Promise.resolve('');
- }
- };
-
- gitUi.init_listners();
- listeners.forEach((listener) => listener({ target: { id: 'git-add-btn' } }));
-
- assert.equal(addAllCalls, 1);
+test('actions invoke the expected operation and refresh status', async () => {
+ let stageCalls = 0;
+ let statusCalls = 0;
+ const gitman = { setWorkspace() {}, status: async () => { statusCalls++; return ' M file.js'; }, addAll: async () => { stageCalls++; return ''; } };
+ const ui = new GitUi(createContainer(), { gitman });
+ await ui.setWorkspace('/workspace');
+ await ui.runAction('addAll');
+ assert.equal(stageCalls, 1);
+ assert.equal(statusCalls, 2);
+ assert.equal(ui.state.messageType, 'success');
});
-test('generates a patch synchronously from the selected file paths', () => {
- const listeners = [];
- const oldFile = { name: 'old.txt' };
- const newFile = { name: 'new.txt' };
- const inputs = {
- '#git-old-file': { files: [oldFile] },
- '#git-new-file': { files: [newFile] }
- };
- const container = {
- addEventListener: (_type, listener) => listeners.push(listener),
- querySelector: (selector) => inputs[selector]
- };
- const gitBar = {
- getPathForFile: (file) => `/selected/${file.name}`
- };
- const gitUi = new GitUi(container, gitBar);
- let selectedPaths;
- gitUi.patch = (oldFilePath, newFilePath) => {
- selectedPaths = [oldFilePath, newFilePath];
- return 'patch text';
- };
-
- gitUi.init_listners();
- listeners.forEach((listener) => listener({ target: { id: 'git-patch-btn' } }));
-
- assert.deepEqual(selectedPaths, ['/selected/old.txt', '/selected/new.txt']);
+test('clearing the workspace clears repository state', async () => {
+ const ui = new GitUi(createContainer(), { gitman: { setWorkspace() {}, status: async () => '' } });
+ await ui.setWorkspace('/workspace');
+ await ui.setWorkspace(null);
+ assert.equal(ui.state.mode, 'empty');
+ assert.deepEqual(ui.state.entries, []);
});
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 13159d0..7c88e86 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -13,8 +13,7 @@ export default defineConfig([
"backend/**/*.js",
"git/**/*.js",
"UI/git_sidebar/**.js",
- "UI/codebase_indexer/**.js",
- "UI/tab_manager/**.js",
+ "UI/codebase_indexer/cb_index.js",
"patchgen.js"
],
extends: [js.configs.recommended],
@@ -34,6 +33,24 @@ export default defineConfig([
}
},
+ {
+ files: ["UI/codebase_indexer/editor_integration.js"],
+ extends: [js.configs.recommended],
+ languageOptions: {
+ sourceType: "commonjs",
+ globals: { ...globals.node, ...globals.browser }
+ }
+ },
+
+ {
+ files: ["UI/tab_manager/**/*.js"],
+ extends: [js.configs.recommended],
+ languageOptions: {
+ sourceType: "module",
+ globals: { ...globals.browser }
+ }
+ },
+
// Renderer: Browser + AMD (Monaco)
{
files: ["renderer/**/*.js"],
diff --git a/index.html b/index.html
index a3e6a89..9cdf270 100644
--- a/index.html
+++ b/index.html
@@ -456,8 +456,13 @@
tabManager.onNewTabRequest = () => createNewTab();
async function refreshFileTree(directoryPath) {
- if (!directoryPath) return;
+ if (!directoryPath) {
+ currentDirectory = null;
+ await window.api.setGitWorkspace(null);
+ return;
+ }
currentDirectory = directoryPath;
+ await window.api.setGitWorkspace(directoryPath);
await renderDirectory(directoryPath, document.getElementById('tree-root'));
}
diff --git a/main.js b/main.js
index 2ff1eca..75ec086 100644
--- a/main.js
+++ b/main.js
@@ -3,6 +3,7 @@ const { app, BrowserWindow, ipcMain, dialog } = require('electron');
const path = require('path');
const fs = require('fs');
const { spawn } = require('child_process');
+const { runGitRequest } = require('./main/git-service');
let clangdProcess = null;
let clangdBuffer = Buffer.alloc(0);
@@ -321,6 +322,8 @@ ipcMain.handle('directory:list', async (_event, directoryPath) => {
.sort((left, right) => Number(right.isDirectory) - Number(left.isDirectory) || left.name.localeCompare(right.name));
});
+ipcMain.handle('git:run', (_event, request) => runGitRequest(request));
+
ipcMain.handle('clangd:start', (event, rootPath) => startClangd(event, rootPath));
ipcMain.on('clangd:message', (_event, message) => sendToClangd(message));
ipcMain.on('clangd:stop', stopClangd);
@@ -342,4 +345,4 @@ app.on('before-quit', (event) => {
app.on('will-quit', () => {
stopClangd();
stopPyright();
-});
\ No newline at end of file
+});
diff --git a/main/git-service.js b/main/git-service.js
new file mode 100644
index 0000000..fa7b527
--- /dev/null
+++ b/main/git-service.js
@@ -0,0 +1,37 @@
+const path = require('node:path');
+const { execFile } = require('node:child_process');
+
+const OPERATIONS = Object.freeze({
+ init: ['init'], status: ['status', '--short'], addAll: ['add', '--all'], pull: ['pull'], push: ['push']
+});
+
+function validateGitRequest(request) {
+ if (!request || typeof request !== 'object') throw new Error('Invalid Git request.');
+ const { operation, workspace, args = [] } = request;
+ if (!path.isAbsolute(workspace || '')) throw new Error('A valid workspace path is required.');
+ if (!Array.isArray(args) || args.some((argument) => typeof argument !== 'string')) throw new Error('Invalid Git arguments.');
+ if (operation === 'commit') {
+ if (args.length !== 1 || !args[0].trim() || args[0].length > 1000) throw new Error('A commit message is required.');
+ return { workspace: path.resolve(workspace), gitArgs: ['commit', '-m', args[0]] };
+ }
+ if (!Object.hasOwn(OPERATIONS, operation) || args.length) throw new Error('Unsupported Git operation.');
+ return { workspace: path.resolve(workspace), gitArgs: OPERATIONS[operation] };
+}
+
+function runGitRequest(request, executor = execFile) {
+ let command;
+ try { command = validateGitRequest(request); } catch (error) {
+ return Promise.resolve({ ok: false, stdout: '', stderr: error.message, code: 'INVALID_REQUEST' });
+ }
+ return new Promise((resolve) => {
+ executor('git', command.gitArgs, { cwd: command.workspace }, (error, stdout = '', stderr = '') => {
+ if (error) {
+ resolve({ ok: false, stdout: stdout.trim(), stderr: stderr.trim() || error.message, code: typeof error.code === 'number' ? error.code : 'GIT_ERROR' });
+ return;
+ }
+ resolve({ ok: true, stdout: stdout.trim(), stderr: stderr.trim(), code: 0 });
+ });
+ });
+}
+
+module.exports = { OPERATIONS, validateGitRequest, runGitRequest };
diff --git a/main/git-service.test.js b/main/git-service.test.js
new file mode 100644
index 0000000..7337759
--- /dev/null
+++ b/main/git-service.test.js
@@ -0,0 +1,18 @@
+const assert = require('node:assert/strict');
+const test = require('node:test');
+const { runGitRequest, validateGitRequest } = require('./git-service');
+
+test('accepts allowlisted operations and rejects unsupported operations', async () => {
+ assert.deepEqual(validateGitRequest({ operation: 'status', workspace: '/workspace' }).gitArgs, ['status', '--short']);
+ const result = await runGitRequest({ operation: 'shell', workspace: '/workspace' });
+ assert.equal(result.ok, false);
+ assert.equal(result.code, 'INVALID_REQUEST');
+});
+
+test('uses the selected workspace as cwd and keeps commit arguments separate', async () => {
+ let invocation;
+ const executor = (...args) => { invocation = args.slice(0, 3); args[3](null, 'committed\n', ''); };
+ const result = await runGitRequest({ operation: 'commit', workspace: '/selected/project', args: ['fix; echo unsafe'] }, executor);
+ assert.equal(result.ok, true);
+ assert.deepEqual(invocation, ['git', ['commit', '-m', 'fix; echo unsafe'], { cwd: '/selected/project' }]);
+});
diff --git a/preload.js b/preload.js
index a12ee75..f3ac7e2 100644
--- a/preload.js
+++ b/preload.js
@@ -1,16 +1,15 @@
-const { contextBridge, ipcRenderer, webUtils } = require('electron');
+const { contextBridge, ipcRenderer } = require('electron');
const { indexer } = require('./UI/codebase_indexer/cb_index');
const { GitUi } = require('./UI/git_sidebar/gitui');
+let gitUi = null;
+const gitApi = { run: (operation, workspace, args = []) => ipcRenderer.invoke('git:run', { operation, workspace, args }) };
+
globalThis.addEventListener('DOMContentLoaded', () => {
const container = globalThis.document.getElementById('git-sidebar-mount');
- const gitBar = {
- getPathForFile: (file) => webUtils.getPathForFile(file)
- };
-
- const gitUi = new GitUi(container, gitBar);
+ gitUi = new GitUi(container, { gitApi });
gitUi.render();
- gitUi.init_listners();
+ gitUi.initListeners();
});
contextBridge.exposeInMainWorld('api', {
@@ -20,6 +19,13 @@ contextBridge.exposeInMainWorld('api', {
saveFile: (data) => ipcRenderer.invoke('file:save', data),
listDirectory: (directoryPath) => ipcRenderer.invoke('directory:list', directoryPath),
openFolder: () => ipcRenderer.invoke('dialog:openFolder'),
+ setGitWorkspace: (workspace) => gitUi?.setWorkspace(workspace),
+ gitStatus: (workspace) => gitApi.run('status', workspace),
+ gitInit: (workspace) => gitApi.run('init', workspace),
+ gitAddAll: (workspace) => gitApi.run('addAll', workspace),
+ gitCommit: (workspace, message) => gitApi.run('commit', workspace, [message]),
+ gitPull: (workspace) => gitApi.run('pull', workspace),
+ gitPush: (workspace) => gitApi.run('push', workspace),
updateCodebaseIndex: (documentId, text) => indexer.updateActiveDocument(documentId, text),
queryCodebaseGhostText: (prefix) => indexer.queryGhostText(prefix),
diff --git a/styles/style1.css b/styles/style1.css
index 2414d4f..0e1b697 100644
--- a/styles/style1.css
+++ b/styles/style1.css
@@ -196,22 +196,14 @@ body {
}
#git-sidebar {
- width: 220px;
+ width: min(260px, 28vw);
+ min-width: 190px;
padding: 10px;
background-color: var(--vscode-sidebar-bg);
border-right: 1px solid var(--vscode-border);
overflow-y: auto;
}
-#git-sidebar .git_bar {
- display: flex;
- flex-direction: column;
- align-items: stretch;
- gap: 8px;
- height: auto;
- padding: 0;
-}
-
#git-sidebar input,
#git-sidebar button {
width: 100%;
@@ -601,36 +593,35 @@ body {
outline-offset: -2px;
}
-.git_bar {
+.git-panel {
display: flex;
- align-items: center;
- gap: 6px;
- height: 30px;
- padding: 0 10px;
- background-color: var(--vscode-toolbar-bg);
- border-top: 1px solid var(--vscode-border);
-}
-
-.init_btn {
- background-color: transparent;
+ flex-direction: column;
+ gap: 8px;
+ min-width: 0;
+}
+
+.git-title { margin: 0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; }
+.git-message { margin: 0; padding: 7px; border-radius: 3px; color: var(--vscode-text-muted); overflow-wrap: anywhere; }
+.git-message--success { color: #89d185; }
+.git-message--error { color: #f48771; }
+.git-message--loading { color: var(--vscode-text-main); }
+.git-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; }
+.git-panel button, .git-commit-input { box-sizing: border-box; min-height: 30px; border: 1px solid var(--vscode-border); border-radius: 4px; padding: 5px 8px; font: 12px var(--font-ui); }
+.git-panel button { background-color: var(--vscode-toolbar-bg); color: var(--vscode-text-main); cursor: pointer; }
+.git-panel button:hover:not(:disabled) { background-color: var(--vscode-hover-item); }
+.git-panel button:disabled { cursor: default; opacity: 0.5; }
+.git-panel button:focus-visible, .git-commit-input:focus-visible { outline: 2px solid var(--vscode-focus-border); outline-offset: 1px; }
+.git-commit-input {
+ background: var(--vscode-editor-bg);
color: var(--vscode-text-main);
- border: 1px solid transparent;
- border-radius: 4px;
- padding: 4px 10px;
- font-size: 12px;
- font-family: var(--font-ui);
- cursor: pointer;
- transition: background-color 0.15s ease;
}
+.git-status-list { margin: 4px 0 0; padding: 0; list-style: none; overflow-y: auto; }
+.git-status-entry { display: flex; gap: 8px; padding: 5px 2px; min-width: 0; }
+.git-status-code { flex: 0 0 22px; color: #cca700; font-family: monospace; }
+.git-status-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.git-clean { color: var(--vscode-text-muted); padding: 6px 2px; }
-.add_btn {
- background-color: transparent;
- color: var(--vscode-text-main);
- border: 1px solid transparent;
- border-radius: 4px;
- padding: 4px 10px;
- font-size: 12px;
- font-family: var(--font-ui);
- cursor: pointer;
- transition: background-color 0.15s ease;
+@media (max-width: 760px) {
+ #git-sidebar { width: 190px; min-width: 160px; padding: 7px; }
+ .git-actions { grid-template-columns: 1fr; }
}