From e79fd3f281cd469fa04664371b11e11f1d0ae177 Mon Sep 17 00:00:00 2001 From: logikonline Date: Sun, 18 Jan 2026 14:48:43 -0500 Subject: [PATCH] feat: add .NET host process integration Implements native host process support with .NET backend: - Add .NET host project with HTTP endpoints (health check, hello world) - Configure multi-platform host executables in addon.json - Add host communication methods in main process - Create host demo tab in UI - Add .gitignore for .NET build artifacts The host process runs on a dynamic port and communicates via HTTP with the main addon process. --- .gitignore | 17 ++++++ addon.json | 15 ++++- host/MyFirstAddon.Host.csproj | 11 ++++ host/Program.cs | 35 ++++++++++++ main/index.js | 45 +++++++++++++++ views/demo.html | 104 ++++++++++++++++++++++++++++++++++ 6 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 host/MyFirstAddon.Host.csproj create mode 100644 host/Program.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fae7b80 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# .NET build outputs +host/bin/ +host/obj/ +host/win-x64/ +host/linux-x64/ +host/osx-x64/ +host/osx-arm64/ + +# IDE +.vs/ +.vscode/ +*.user +*.suo + +# OS +.DS_Store +Thumbs.db diff --git a/addon.json b/addon.json index ea4b002..49d4808 100644 --- a/addon.json +++ b/addon.json @@ -29,8 +29,19 @@ "_comment_renderer": "Optional: Path to renderer process module for UI components", "renderer": "renderer/index.js", - "_comment_host": "Optional: Native host process configuration (for .NET, Go, Rust, etc.)", - "_comment_host_note": "Remove this section if your addon is pure JavaScript", + "_comment_host": "Native host process configuration (.NET backend)", + "host": { + "directory": "host", + "executable": "MyFirstAddon.Host", + "platforms": { + "win-x64": "host/win-x64/MyFirstAddon.Host.exe", + "linux-x64": "host/linux-x64/MyFirstAddon.Host", + "osx-x64": "host/osx-x64/MyFirstAddon.Host", + "osx-arm64": "host/osx-arm64/MyFirstAddon.Host" + }, + "healthCheck": "/health", + "startupTimeout": 10000 + }, "_comment_permissions": "Required permissions - user sees these before installing", "permissions": [ diff --git a/host/MyFirstAddon.Host.csproj b/host/MyFirstAddon.Host.csproj new file mode 100644 index 0000000..b6d996a --- /dev/null +++ b/host/MyFirstAddon.Host.csproj @@ -0,0 +1,11 @@ + + + + net10.0 + enable + enable + MyFirstAddon.Host + MyFirstAddon.Host + + + diff --git a/host/Program.cs b/host/Program.cs new file mode 100644 index 0000000..7577d24 --- /dev/null +++ b/host/Program.cs @@ -0,0 +1,35 @@ +var builder = WebApplication.CreateBuilder(args); + +// Configure port from environment variable (set by GitCaddy AddonManager) +var port = Environment.GetEnvironmentVariable("ADDON_HOST_PORT") ?? "47933"; +builder.WebHost.UseUrls($"http://127.0.0.1:{port}"); + +var app = builder.Build(); + +// Root endpoint +app.MapGet("/", () => new { + status = "ok", + service = "MyFirstAddon.Host", + version = "1.0.0" +}); + +// Health check endpoint (required by GitCaddy) +app.MapGet("/health", () => new { + status = "healthy", + timestamp = DateTime.UtcNow +}); + +// Hello World endpoint - demonstrates a simple addon host function +app.MapGet("/hello", () => new { + message = "Hello World from MyFirstAddon.Host!", + timestamp = DateTime.UtcNow +}); + +// Hello endpoint with name parameter +app.MapGet("/hello/{name}", (string name) => new { + message = $"Hello, {name}!", + timestamp = DateTime.UtcNow +}); + +Console.WriteLine($"MyFirstAddon.Host starting on http://127.0.0.1:{port}"); +app.Run(); diff --git a/main/index.js b/main/index.js index 11db8a0..8ff557e 100644 --- a/main/index.js +++ b/main/index.js @@ -201,6 +201,11 @@ class MyFirstAddon { case 'performAction': return this.performAction(args[0]); + // ---- Host Methods ---- + // These call the .NET host process + case 'callHelloWorld': + return this.callHelloWorld(args[0]); + // ---- Capability Methods ---- // These are called by GitCaddy for registered capabilities case 'generateCommitMessage': @@ -460,6 +465,46 @@ class MyFirstAddon { }; } + // ============================================================ + // HOST METHODS + // These methods communicate with the .NET host process + // ============================================================ + + /** + * Call the Hello World endpoint on the .NET host. + * + * Demonstrates how to communicate with a native host process. + * + * @param {string} [name] - Optional name to greet + * @returns {Promise<{message: string, timestamp: string}>} + */ + async callHelloWorld(name) { + if (!this.context?.hostPort) { + this.context?.log.warn('Host port not available - host may not be running'); + return { error: 'Host not available', message: null }; + } + + try { + const endpoint = name + ? `http://127.0.0.1:${this.context.hostPort}/hello/${encodeURIComponent(name)}` + : `http://127.0.0.1:${this.context.hostPort}/hello`; + + this.context?.log.info(`Calling host endpoint: ${endpoint}`); + + const response = await fetch(endpoint); + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + const result = await response.json(); + this.context?.log.info('Host response:', result); + return result; + } catch (error) { + this.context?.log.error('Failed to call host:', error); + return { error: error.message, message: null }; + } + } + // ============================================================ // EVENT HANDLERS // ============================================================ diff --git a/views/demo.html b/views/demo.html index 6e8186e..d651dfa 100644 --- a/views/demo.html +++ b/views/demo.html @@ -310,6 +310,7 @@ + @@ -420,6 +421,46 @@ + +
+
+

.NET Host Communication

+

+ This addon includes a .NET native host process that provides HTTP endpoints. + GitCaddy automatically starts the host when the addon is activated and + provides the port number via context.hostPort. +

+ +

Hello World

+

Call the /hello endpoint on the .NET host.

+
+ + +
+ +

Host Response

+
+ Click a button above to test host communication. +
+
+ +
+

Host Status

+
+ Host Port: + Loading... +
+
+ Host Available: + Unknown +
+
+
+
@@ -558,6 +599,9 @@ async function initializeView() { log('info', 'Demo view initialized'); + // Update host status display + updateHostStatus(); + // Load addon data try { const data = await invokeAddon('getDemoData'); @@ -701,6 +745,66 @@ } } + // ============================================================ + // HOST DEMO FUNCTIONS + // ============================================================ + + async function callHelloWorld() { + const resultEl = document.getElementById('host-result'); + resultEl.innerHTML = ' Calling host...'; + log('info', 'Calling host /hello endpoint'); + + try { + const result = await invokeAddon('callHelloWorld'); + resultEl.textContent = JSON.stringify(result, null, 2); + if (result.error) { + log('error', `Host error: ${result.error}`); + document.getElementById('host-available').textContent = 'No (Error)'; + } else { + log('info', `Host response: ${result.message}`); + document.getElementById('host-available').textContent = 'Yes'; + } + } catch (err) { + resultEl.textContent = 'Error: ' + err.message; + log('error', `Host call failed: ${err.message}`); + document.getElementById('host-available').textContent = 'No (Exception)'; + } + } + + async function callHelloWorldWithName() { + const name = prompt('Enter your name:', 'World'); + if (!name) return; + + const resultEl = document.getElementById('host-result'); + resultEl.innerHTML = ' Calling host...'; + log('info', `Calling host /hello/${name} endpoint`); + + try { + const result = await invokeAddon('callHelloWorld', name); + resultEl.textContent = JSON.stringify(result, null, 2); + if (result.error) { + log('error', `Host error: ${result.error}`); + } else { + log('info', `Host response: ${result.message}`); + } + } catch (err) { + resultEl.textContent = 'Error: ' + err.message; + log('error', `Host call failed: ${err.message}`); + } + } + + function updateHostStatus() { + // hostPort is received in addon:init-context + const portEl = document.getElementById('host-port'); + if (hostPort) { + portEl.textContent = hostPort; + document.getElementById('host-available').textContent = 'Checking...'; + } else { + portEl.textContent = 'Not available'; + document.getElementById('host-available').textContent = 'No (No port)'; + } + } + // ============================================================ // LOGGING // ============================================================