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.
576 lines
16 KiB
JavaScript
576 lines
16 KiB
JavaScript
/**
|
|
* My First Addon - Main Process Module
|
|
*
|
|
* This file runs in Electron's main process and handles:
|
|
* - Addon lifecycle (initialize, activate, deactivate, dispose)
|
|
* - Settings management
|
|
* - IPC communication with renderer process
|
|
* - Method invocations from GitCaddy
|
|
* - Event handling
|
|
*
|
|
* The main process has full Node.js access and can:
|
|
* - Read/write files
|
|
* - Make network requests
|
|
* - Spawn child processes
|
|
* - Access system APIs
|
|
*/
|
|
|
|
// Import shared types (optional - for code organization)
|
|
const { DEFAULT_SETTINGS } = require('../shared/types');
|
|
|
|
/**
|
|
* Main Addon Class
|
|
*
|
|
* GitCaddy will instantiate this class and call its lifecycle methods.
|
|
* The class must be the default export.
|
|
*/
|
|
class MyFirstAddon {
|
|
constructor() {
|
|
// Context provided by GitCaddy (set in initialize)
|
|
this.context = null;
|
|
|
|
// Local settings cache
|
|
this.settings = { ...DEFAULT_SETTINGS };
|
|
|
|
// Track disposables for cleanup
|
|
this.disposables = [];
|
|
|
|
// Track if addon is active
|
|
this.isActive = false;
|
|
}
|
|
|
|
// ============================================================
|
|
// LIFECYCLE METHODS
|
|
// These are called by GitCaddy at specific points
|
|
// ============================================================
|
|
|
|
/**
|
|
* Initialize the addon.
|
|
*
|
|
* Called once when the addon is first loaded.
|
|
* Use this to set up initial state and register event listeners.
|
|
*
|
|
* @param {IAddonMainContext} context - Context provided by GitCaddy
|
|
*/
|
|
async initialize(context) {
|
|
this.context = context;
|
|
|
|
// Log that we're initializing
|
|
context.log.info('MyFirstAddon initializing...');
|
|
|
|
// Load settings from storage
|
|
await this.loadSettings();
|
|
|
|
// Log loaded settings (be careful not to log sensitive data!)
|
|
context.log.debug('Settings loaded:', {
|
|
enableFeature: this.settings.enableFeature,
|
|
mode: this.settings.mode,
|
|
maxItems: this.settings.maxItems,
|
|
// Don't log apiKey!
|
|
});
|
|
|
|
// Register for settings changes
|
|
const settingsDisposable = context.settings.onDidChange((key, value) => {
|
|
this.onSettingChanged(key, value);
|
|
});
|
|
this.disposables.push(settingsDisposable);
|
|
|
|
// Register for repository events
|
|
// Note: Use the specific event methods, not a generic 'on' method
|
|
const repoDisposable = context.events.onRepositorySelected((repo) => {
|
|
this.onRepositoryChanged(repo);
|
|
});
|
|
this.disposables.push(repoDisposable);
|
|
|
|
// Register for commit events
|
|
const commitDisposable = context.events.onCommitCreated((commit) => {
|
|
this.onCommitCreated(commit);
|
|
});
|
|
this.disposables.push(commitDisposable);
|
|
|
|
context.log.info('MyFirstAddon initialized successfully');
|
|
}
|
|
|
|
/**
|
|
* Activate the addon.
|
|
*
|
|
* Called when the addon is enabled/activated.
|
|
* Start any background processes or services here.
|
|
*/
|
|
async activate() {
|
|
this.context?.log.info('MyFirstAddon activating...');
|
|
this.isActive = true;
|
|
|
|
// Example: Start a periodic task
|
|
// this.startPeriodicTask();
|
|
|
|
this.context?.log.info('MyFirstAddon activated');
|
|
return true; // Must return true to indicate successful activation
|
|
}
|
|
|
|
/**
|
|
* Deactivate the addon.
|
|
*
|
|
* Called when the addon is disabled.
|
|
* Stop background processes but don't fully clean up.
|
|
*/
|
|
async deactivate() {
|
|
this.context?.log.info('MyFirstAddon deactivating...');
|
|
this.isActive = false;
|
|
|
|
// Example: Stop periodic tasks
|
|
// this.stopPeriodicTask();
|
|
|
|
this.context?.log.info('MyFirstAddon deactivated');
|
|
}
|
|
|
|
/**
|
|
* Dispose of the addon.
|
|
*
|
|
* Called when the addon is being unloaded.
|
|
* Clean up all resources, event listeners, etc.
|
|
*/
|
|
dispose() {
|
|
this.context?.log.info('MyFirstAddon disposing...');
|
|
|
|
// Dispose all registered disposables
|
|
for (const disposable of this.disposables) {
|
|
disposable.dispose();
|
|
}
|
|
this.disposables = [];
|
|
|
|
// Clear references
|
|
this.context = null;
|
|
this.isActive = false;
|
|
|
|
console.log('MyFirstAddon disposed');
|
|
}
|
|
|
|
// ============================================================
|
|
// INVOKE METHOD
|
|
// GitCaddy calls this to invoke addon functionality
|
|
// ============================================================
|
|
|
|
/**
|
|
* Handle method invocations from GitCaddy.
|
|
*
|
|
* This is the main entry point for GitCaddy to call your addon's functionality.
|
|
* Methods can be invoked from:
|
|
* - Toolbar buttons (onClick.action = "invoke-method")
|
|
* - Menu items
|
|
* - Context menus
|
|
* - Other addons
|
|
* - The renderer process
|
|
*
|
|
* @param {string} method - Method name to invoke
|
|
* @param {unknown[]} args - Arguments passed to the method
|
|
* @returns {Promise<unknown>} - Result of the method
|
|
*/
|
|
async invoke(method, args) {
|
|
this.context?.log.debug(`Invoke called: ${method}`, args);
|
|
|
|
switch (method) {
|
|
// ---- Settings Methods ----
|
|
case 'getSettings':
|
|
return this.getSettings();
|
|
|
|
case 'updateSettings':
|
|
return this.updateSettings(args[0]);
|
|
|
|
// ---- Badge Methods ----
|
|
case 'getBadgeContent':
|
|
return this.getBadgeContent();
|
|
|
|
// ---- Action Methods ----
|
|
case 'quickAction':
|
|
return this.quickAction(args[0], args[1]);
|
|
|
|
case 'menuAction':
|
|
return this.menuAction();
|
|
|
|
case 'processFiles':
|
|
return this.processFiles(args[0]);
|
|
|
|
case 'analyzeCommit':
|
|
return this.analyzeCommit(args[0]);
|
|
|
|
// ---- Demo Methods ----
|
|
case 'getDemoData':
|
|
return this.getDemoData();
|
|
|
|
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':
|
|
return this.generateCommitMessage(args[0]);
|
|
|
|
default:
|
|
throw new Error(`Unknown method: ${method}`);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// LICENSE METHODS
|
|
// Optional: Implement if your addon has licensing
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get the license status for this addon.
|
|
*
|
|
* GitCaddy calls this method to check if the addon is licensed.
|
|
* If not implemented, the addon is assumed to be licensed (free addon).
|
|
*
|
|
* @returns {{ valid: boolean, message?: string }}
|
|
*/
|
|
getLicenseStatus() {
|
|
// For free/open-source addons, always return valid
|
|
return { valid: true };
|
|
|
|
// For commercial addons, you would check license here:
|
|
// return {
|
|
// valid: this.isLicenseValid,
|
|
// message: this.isLicenseValid ? undefined : 'License expired'
|
|
// };
|
|
}
|
|
|
|
// ============================================================
|
|
// SETTINGS METHODS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Load settings from GitCaddy's settings store.
|
|
*/
|
|
async loadSettings() {
|
|
if (!this.context) return;
|
|
|
|
const stored = this.context.settings.getAll();
|
|
this.settings = {
|
|
enableFeature: stored.enableFeature ?? DEFAULT_SETTINGS.enableFeature,
|
|
apiKey: stored.apiKey ?? DEFAULT_SETTINGS.apiKey,
|
|
mode: stored.mode ?? DEFAULT_SETTINGS.mode,
|
|
maxItems: stored.maxItems ?? DEFAULT_SETTINGS.maxItems,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Handle setting changes.
|
|
*/
|
|
onSettingChanged(key, value) {
|
|
this.context?.log.debug(`Setting changed: ${key} =`, value);
|
|
|
|
if (key in this.settings) {
|
|
this.settings[key] = value;
|
|
}
|
|
|
|
// React to specific setting changes
|
|
if (key === 'mode') {
|
|
this.context?.log.info(`Mode changed to: ${value}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current settings (for UI).
|
|
*/
|
|
getSettings() {
|
|
return {
|
|
...this.settings,
|
|
// Don't expose the actual API key, just whether it's set
|
|
hasApiKey: !!this.settings.apiKey,
|
|
apiKey: undefined,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update settings.
|
|
*/
|
|
async updateSettings(updates) {
|
|
if (!this.context) return;
|
|
|
|
for (const [key, value] of Object.entries(updates)) {
|
|
if (value !== undefined) {
|
|
await this.context.settings.set(key, value);
|
|
}
|
|
}
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
// ============================================================
|
|
// BADGE METHODS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get badge content for toolbar button.
|
|
*
|
|
* Called by GitCaddy to update the badge on your toolbar button.
|
|
* Return null/undefined to hide the badge.
|
|
*
|
|
* @returns {string|number|null} Badge content
|
|
*/
|
|
getBadgeContent() {
|
|
// Example: Return a count or status indicator
|
|
if (!this.settings.enableFeature) {
|
|
return null; // Hide badge when feature is disabled
|
|
}
|
|
|
|
// Return a number, string, or null
|
|
return '3'; // Example: "3 items"
|
|
}
|
|
|
|
// ============================================================
|
|
// ACTION METHODS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Quick action triggered by toolbar button.
|
|
*/
|
|
async quickAction(arg1, arg2) {
|
|
this.context?.log.info('Quick action triggered!', { arg1, arg2 });
|
|
|
|
// Show a notification
|
|
this.context?.ipc.send('show-notification', {
|
|
title: 'Quick Action',
|
|
body: `Action executed with args: ${arg1}, ${arg2}`,
|
|
});
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
/**
|
|
* Menu action triggered from Repository menu.
|
|
*/
|
|
async menuAction() {
|
|
this.context?.log.info('Menu action triggered!');
|
|
|
|
// Example: Get the current repository
|
|
const repo = this.context?.appState?.getCurrentRepository();
|
|
|
|
if (repo) {
|
|
this.context?.log.info('Current repository:', repo.path);
|
|
}
|
|
|
|
return { success: true };
|
|
}
|
|
|
|
/**
|
|
* Process selected files from context menu.
|
|
*/
|
|
async processFiles(files) {
|
|
this.context?.log.info('Processing files:', files);
|
|
|
|
// Example file processing
|
|
const results = [];
|
|
for (const file of files || []) {
|
|
results.push({
|
|
path: file.path,
|
|
status: file.status,
|
|
processed: true,
|
|
});
|
|
}
|
|
|
|
return { files: results };
|
|
}
|
|
|
|
/**
|
|
* Analyze a commit from context menu.
|
|
*/
|
|
async analyzeCommit(commit) {
|
|
this.context?.log.info('Analyzing commit:', commit?.sha);
|
|
|
|
return {
|
|
sha: commit?.sha,
|
|
analysis: 'This is a demo analysis result',
|
|
};
|
|
}
|
|
|
|
// ============================================================
|
|
// DEMO METHODS (for the demo view)
|
|
// ============================================================
|
|
|
|
/**
|
|
* Get demo data for the view.
|
|
*/
|
|
getDemoData() {
|
|
return {
|
|
addonId: this.context?.addonId,
|
|
addonPath: this.context?.addonPath,
|
|
isActive: this.isActive,
|
|
settings: this.getSettings(),
|
|
features: [
|
|
'Toolbar buttons',
|
|
'Menu items',
|
|
'Context menus',
|
|
'Custom views',
|
|
'Settings storage',
|
|
'IPC communication',
|
|
'Event handling',
|
|
],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Perform a demo action.
|
|
*/
|
|
async performAction(actionType) {
|
|
this.context?.log.info(`Performing action: ${actionType}`);
|
|
|
|
switch (actionType) {
|
|
case 'notify':
|
|
this.context?.ipc.send('show-notification', {
|
|
title: 'Hello from Addon!',
|
|
body: 'This notification was triggered by the addon.',
|
|
});
|
|
return { message: 'Notification sent!' };
|
|
|
|
case 'log':
|
|
this.context?.log.info('This is an info log from the demo');
|
|
this.context?.log.warn('This is a warning log from the demo');
|
|
this.context?.log.debug('This is a debug log from the demo');
|
|
return { message: 'Check the logs!' };
|
|
|
|
case 'settings':
|
|
return this.getSettings();
|
|
|
|
default:
|
|
return { message: `Unknown action: ${actionType}` };
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// CAPABILITY METHODS
|
|
// ============================================================
|
|
|
|
/**
|
|
* Generate commit message (if you registered 'commit-message-generation' capability).
|
|
*
|
|
* GitCaddy will call this when the user requests a commit message.
|
|
*/
|
|
async generateCommitMessage(context) {
|
|
this.context?.log.info('Generating commit message...');
|
|
|
|
// Example: Simple commit message based on files
|
|
const fileCount = context.files?.length || 0;
|
|
const summary = `chore: update ${fileCount} file(s)`;
|
|
|
|
return {
|
|
summary,
|
|
description: 'This is a demo commit message from My First Addon.',
|
|
};
|
|
}
|
|
|
|
// ============================================================
|
|
// 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
|
|
// ============================================================
|
|
|
|
/**
|
|
* Handle repository changes.
|
|
*/
|
|
onRepositoryChanged(repo) {
|
|
this.context?.log.debug('Repository changed:', repo?.path);
|
|
|
|
// Example: Refresh badge when repo changes
|
|
// The badge will be refreshed automatically by GitCaddy
|
|
}
|
|
|
|
/**
|
|
* Handle new commits.
|
|
*/
|
|
onCommitCreated(commit) {
|
|
this.context?.log.debug('New commit created:', commit?.sha);
|
|
}
|
|
}
|
|
|
|
// ============================================================
|
|
// CONTEXT INTERFACE (for reference)
|
|
// ============================================================
|
|
|
|
/**
|
|
* @typedef {Object} IAddonMainContext
|
|
* @property {string} addonId - Unique addon identifier
|
|
* @property {string} addonPath - Path to addon directory
|
|
* @property {Object} manifest - Parsed addon.json
|
|
* @property {IAddonLogger} log - Logger instance
|
|
* @property {IAddonSettings} settings - Settings storage
|
|
* @property {IAddonEvents} events - Event emitter
|
|
* @property {IAddonIPC} ipc - IPC communication
|
|
* @property {number} [hostPort] - Port of native host (if configured)
|
|
* @property {IAppStateProxy} [appState] - Access to app state
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} IAddonLogger
|
|
* @property {function} debug - Log debug message
|
|
* @property {function} info - Log info message
|
|
* @property {function} warn - Log warning message
|
|
* @property {function} error - Log error message
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} IAddonSettings
|
|
* @property {function} get - Get a setting value
|
|
* @property {function} set - Set a setting value
|
|
* @property {function} getAll - Get all settings
|
|
* @property {function} onDidChange - Register for setting changes
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} IAddonEvents
|
|
* @property {function} onAppReady - Called when app is ready
|
|
* @property {function} onAppWillQuit - Called before app quits
|
|
* @property {function} onRepositorySelected - Called when repository changes
|
|
* @property {function} onFilesChanged - Called when files change
|
|
* @property {function} onCommitCreated - Called when a commit is created
|
|
* @property {function} emit - Emit a custom event
|
|
*/
|
|
|
|
// Export the addon class as default
|
|
module.exports = MyFirstAddon;
|
|
module.exports.default = MyFirstAddon;
|