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.
495 lines
13 KiB
JavaScript
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;
|