Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions plugins/github-desktop/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: github-desktop
version: 0.1.0
type: python
main: src/plugin.py

capabilities:
- config provider
73 changes: 73 additions & 0 deletions plugins/github-desktop/src/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import sys
import os
import json
import tempfile

def deep_merge(base, update):
if not isinstance(base, dict) or not isinstance(update, dict):
return update
for key, val in update.items():
if key in base and isinstance(base[key], dict) and isinstance(val, dict):
base[key] = deep_merge(base[key], val)
else:
base[key] = val
return base

def main():
raw_input = sys.stdin.read().strip()
if not raw_input:
print(json.dumps({"error": "Empty stdin context payload received"}), file=sys.stderr)
sys.exit(1)

try:
args = json.loads(raw_input)
except json.JSONDecodeError:
print(json.dumps({"error": "Invalid JSON format payload structure"}), file=sys.stderr)
sys.exit(1)

request_id = args.get("requestId", "")

if args.get("check_installed", False):
appdata = os.environ.get("APPDATA", "")
config_path = os.path.join(appdata, "GitHub Desktop", "config.json") if appdata else ""
installed = bool(config_path and os.path.exists(config_path))
print(json.dumps({"requestId": request_id, "installed": installed}))
sys.exit(0)

settings = args.get("settings", {})
dry_run = args.get("dryRun", False)

appdata = os.environ.get("APPDATA", "")
if not appdata:
print(json.dumps({"requestId": request_id, "error": "APPDATA environment variable missing"}), file=sys.stderr)
sys.exit(1)

config_dir = os.path.join(appdata, "GitHub Desktop")
config_path = os.path.join(config_dir, "config.json")

current_config = {}
if os.path.exists(config_path):
try:
with open(config_path, "r", encoding="utf-8") as f:
current_config = json.load(f)
except Exception:
current_config = {}

updated_config = deep_merge(current_config, settings)

if not dry_run:
if not os.path.exists(config_dir):
os.makedirs(config_dir, exist_ok=True)
try:
fd, temp_path = tempfile.mkstemp(dir=config_dir, prefix="config_", suffix=".json")
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(updated_config, f, indent=2)
os.replace(temp_path, config_path)
except Exception as e:
print(json.dumps({"requestId": request_id, "error": f"Atomic write operation exception: {str(e)}"}), file=sys.stderr)
sys.exit(1)

print(json.dumps({"requestId": request_id}))

if __name__ == "__main__":
main()
64 changes: 64 additions & 0 deletions plugins/github-desktop/test/test_github_desktop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import unittest
import json
import sys
import os
from unittest.mock import patch, mock_open

# Compliance constraint: Leveraging sys.path.append instead of sys.path.insert(0)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))

import plugin

class TestGitHubDesktopPlugin(unittest.TestCase):

@patch("sys.stdin")
@patch("sys.stderr")
def test_empty_stdin_throws_json_error(self, mock_stderr, mock_stdin):
mock_stdin.read.return_value = " "
with self.assertRaises(SystemExit) as cm:
plugin.main()
self.assertEqual(cm.exception.code, 1)

@patch("sys.stdin")
@patch("os.environ", {"APPDATA": "C:\\MockAppData"})
@patch("os.path.exists")
def test_check_installed_protocol_parity(self, mock_exists, mock_stdin):
mock_stdin.read.return_value = json.dumps({"requestId": "test-req-123", "check_installed": True})
mock_exists.return_value = True

with patch("sys.stdout") as mock_stdout:
with self.assertRaises(SystemExit) as cm:
plugin.main()
self.assertEqual(cm.exception.code, 0)
output = json.loads(mock_stdout.write.call_args)
self.assertEqual(output["requestId"], "test-req-123")
self.assertTrue(output["installed"])

@patch("sys.stdin")
@patch("os.environ", {"APPDATA": "C:\\MockAppData"})
@patch("os.path.exists")
@patch("builtins.open", new_callable=mock_open, read_data='{"theme": "dark"}')
@patch("tempfile.mkstemp")
@patch("os.fdopen", new_callable=mock_open)
@patch("os.replace")
def test_settings_deep_merge_atomic_write(self, mock_replace, mock_fdopen, mock_mkstemp, mock_file, mock_exists, mock_stdin):
mock_stdin.read.return_value = json.dumps({
"requestId": "test-req-456",
"settings": {"defaultBranchName": "main", "confirmRemovedFiles": True},
"dryRun": False
})
mock_exists.return_value = True
mock_mkstemp.return_value = (10, "C:\\MockAppData\\GitHub Desktop\\config_tmp.json")

with patch("sys.stdout") as mock_stdout:
with self.assertRaises(SystemExit) as cm:
plugin.main()
self.assertEqual(cm.exception.code, 0)
output = json.loads(mock_stdout.write.call_args)
self.assertEqual(output["requestId"], "test-req-456")
self.assertNotIn("success", output)
self.assertNotIn("data", output)

if __name__ == "__main__":
unittest.main()

73 changes: 73 additions & 0 deletions src/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import sys
import os
import json
import tempfile

def deep_merge(base, update):
if not isinstance(base, dict) or not isinstance(update, dict):
return update
for key, val in update.items():
if key in base and isinstance(base[key], dict) and isinstance(val, dict):
base[key] = deep_merge(base[key], val)
else:
base[key] = val
return base

def main():
raw_input = sys.stdin.read().strip()
if not raw_input:
print(json.dumps({"error": "Empty stdin context payload received"}), file=sys.stderr)
sys.exit(1)

try:
args = json.loads(raw_input)
except json.JSONDecodeError:
print(json.dumps({"error": "Invalid JSON format payload structure"}), file=sys.stderr)
sys.exit(1)

request_id = args.get("requestId", "")

if args.get("check_installed", False):
appdata = os.environ.get("APPDATA", "")
config_path = os.path.join(appdata, "GitHub Desktop", "config.json") if appdata else ""
installed = bool(config_path and os.path.exists(config_path))
print(json.dumps({"requestId": request_id, "installed": installed}))
sys.exit(0)

settings = args.get("settings", {})
dry_run = args.get("dryRun", False)

appdata = os.environ.get("APPDATA", "")
if not appdata:
print(json.dumps({"requestId": request_id, "error": "APPDATA environment variable missing"}), file=sys.stderr)
sys.exit(1)

config_dir = os.path.join(appdata, "GitHub Desktop")
config_path = os.path.join(config_dir, "config.json")

current_config = {}
if os.path.exists(config_path):
try:
with open(config_path, "r", encoding="utf-8") as f:
current_config = json.load(f)
except Exception:
current_config = {}

updated_config = deep_merge(current_config, settings)

if not dry_run:
if not os.path.exists(config_dir):
os.makedirs(config_dir, exist_ok=True)
try:
fd, temp_path = tempfile.mkstemp(dir=config_dir, prefix="config_", suffix=".json")
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(updated_config, f, indent=2)
os.replace(temp_path, config_path)
except Exception as e:
print(json.dumps({"requestId": request_id, "error": f"Atomic write operation exception: {str(e)}"}), file=sys.stderr)
sys.exit(1)

print(json.dumps({"requestId": request_id}))

if __name__ == "__main__":
main()
64 changes: 64 additions & 0 deletions test/test_github_desktop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import unittest
import json
import sys
import os
from unittest.mock import patch, mock_open

# Compliance constraint: Leveraging sys.path.append instead of sys.path.insert(0)
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))

import plugin

class TestGitHubDesktopPlugin(unittest.TestCase):

@patch("sys.stdin")
@patch("sys.stderr")
def test_empty_stdin_throws_json_error(self, mock_stderr, mock_stdin):
mock_stdin.read.return_value = " "
with self.assertRaises(SystemExit) as cm:
plugin.main()
self.assertEqual(cm.exception.code, 1)

@patch("sys.stdin")
@patch("os.environ", {"APPDATA": "C:\\MockAppData"})
@patch("os.path.exists")
def test_check_installed_protocol_parity(self, mock_exists, mock_stdin):
mock_stdin.read.return_value = json.dumps({"requestId": "test-req-123", "check_installed": True})
mock_exists.return_value = True

with patch("sys.stdout") as mock_stdout:
with self.assertRaises(SystemExit) as cm:
plugin.main()
self.assertEqual(cm.exception.code, 0)
output = json.loads(mock_stdout.write.call_args[0][0])
self.assertEqual(output["requestId"], "test-req-123")
self.assertTrue(output["installed"])

@patch("sys.stdin")
@patch("os.environ", {"APPDATA": "C:\\MockAppData"})
@patch("os.path.exists")
@patch("builtins.open", new_callable=mock_open, read_data='{"theme": "dark"}')
@patch("tempfile.mkstemp")
@patch("os.fdopen", new_callable=mock_open)
@patch("os.replace")
def test_settings_deep_merge_atomic_write(self, mock_replace, mock_fdopen, mock_mkstemp, mock_file, mock_exists, mock_stdin):
mock_stdin.read.return_value = json.dumps({
"requestId": "test-req-456",
"settings": {"defaultBranchName": "main", "confirmRemovedFiles": True},
"dryRun": False
})
mock_exists.return_value = True
mock_mkstemp.return_value = (10, "C:\\MockAppData\\GitHub Desktop\\config_tmp.json")

with patch("sys.stdout") as mock_stdout:
with self.assertRaises(SystemExit) as cm:
plugin.main()
self.assertEqual(cm.exception.code, 0)
output = json.loads(mock_stdout.write.call_args[0][0])
self.assertEqual(output["requestId"], "test-req-456")
self.assertNotIn("success", output)
self.assertNotIn("data", output)

if __name__ == "__main__":
unittest.main()