Files
myfirst-addon/main/index.js
logikonline 2b9527cc5f feat(settings): add initial addon implementation with demo views
Creates complete addon structure with manifest, main/renderer modules, shared types, and demo views. Includes comprehensive README documenting addon API, lifecycle methods, permissions, capabilities, and contribution points. Implements settings panel and demo repository view to showcase addon features.
2026-01-18 13:08:11 -05:00

495 lines
13 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
const repoDisposable = context.events.on('repository-changed', (repo) => {
this.onRepositoryChanged(repo);
});
this.disposables.push(repoDisposable);
// Register for commit events
const commitDisposable = context.events.on('commit-created', (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');
}
/**
* 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]);
// ---- Capability Methods ----
// These are called by GitCaddy for registered capabilities
case 'generateCommitMessage':
return this.generateCommitMessage(args[0]);
default:
throw new Error(`Unknown method: ${method}`);
}
}
// ============================================================
// 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.',
};
}
// ============================================================
// 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
*/
// Export the addon class as default
module.exports = MyFirstAddon;
module.exports.default = MyFirstAddon;