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.
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -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
|
||||
15
addon.json
15
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": [
|
||||
|
||||
11
host/MyFirstAddon.Host.csproj
Normal file
11
host/MyFirstAddon.Host.csproj
Normal file
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AssemblyName>MyFirstAddon.Host</AssemblyName>
|
||||
<RootNamespace>MyFirstAddon.Host</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
35
host/Program.cs
Normal file
35
host/Program.cs
Normal file
@@ -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();
|
||||
@@ -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
|
||||
// ============================================================
|
||||
|
||||
104
views/demo.html
104
views/demo.html
@@ -310,6 +310,7 @@
|
||||
<button class="tab active" data-tab="overview">Overview</button>
|
||||
<button class="tab" data-tab="actions">Actions</button>
|
||||
<button class="tab" data-tab="ipc">IPC Demo</button>
|
||||
<button class="tab" data-tab="host">Host Demo</button>
|
||||
<button class="tab" data-tab="logs">Logs</button>
|
||||
</div>
|
||||
|
||||
@@ -420,6 +421,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Host Demo Tab -->
|
||||
<div id="host" class="tab-content">
|
||||
<div class="section">
|
||||
<h2>.NET Host Communication</h2>
|
||||
<p>
|
||||
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 <code>context.hostPort</code>.
|
||||
</p>
|
||||
|
||||
<h3>Hello World</h3>
|
||||
<p>Call the /hello endpoint on the .NET host.</p>
|
||||
<div class="button-group">
|
||||
<button class="btn" onclick="callHelloWorld()">
|
||||
Call Hello World
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="callHelloWorldWithName()">
|
||||
Call with Name
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3>Host Response</h3>
|
||||
<div class="code-block" id="host-result">
|
||||
Click a button above to test host communication.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2>Host Status</h2>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Host Port:</span>
|
||||
<span class="info-value" id="host-port">Loading...</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Host Available:</span>
|
||||
<span class="info-value" id="host-available">Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logs Tab -->
|
||||
<div id="logs" class="tab-content">
|
||||
<div class="section">
|
||||
@@ -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 = '<span class="loading"></span> 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 = '<span class="loading"></span> 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
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user