Development Guide
Overview
Section titled “Overview”This guide provides comprehensive information for developers working on the Warcraft II Notifications Plugin. It covers setup, development workflows, testing strategies, and contribution guidelines.
Table of Contents
Section titled “Table of Contents”- Getting Started
- Development Environment
- Project Structure
- Development Workflow
- Testing
- Code Quality
- Debugging
- Contributing
- Release Process
Development Environment
Section titled “Development Environment”IDE Setup
Section titled “IDE Setup”Visual Studio Code
Section titled “Visual Studio Code”Recommended extensions:
- ESLint: For linting
- Prettier: For code formatting
- TypeScript: For type checking
- Bun for Visual Studio Code: For Bun support
Settings (.vscode/settings.json):
{ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "typescript.tsdk": "node_modules/typescript/lib"}Environment Variables
Section titled “Environment Variables”Create a .env file for local development:
# Debug modeDEBUG_OPENCODE=1
# Custom data directory (optional)SOUNDS_DATA_DIR=/path/to/custom/sounds
# Custom base URL (optional, legacy)SOUNDS_BASE_URL=https://custom-cdn.com/soundsProject Structure
Section titled “Project Structure”opencode-warcraft-notifications/├── .github/ # GitHub workflows and automation│ ├── scripts/ # Workflow helper scripts│ └── workflows/ # CI/CD workflows├── data/ # Bundled sound files│ ├── alliance/ # Alliance faction sounds│ └── horde/ # Horde faction sounds├── docs/ # Documentation│ ├── github-workflows/ # Workflow documentation│ └── schemas/ # JSON schemas├── src/ # Source code│ ├── sound-data/ # Sound metadata│ │ ├── alliance.ts # Alliance sound entries│ │ ├── horde.ts # Horde sound entries│ │ ├── index.ts # Sound data exports│ │ └── types.ts # Type definitions│ ├── bundled-sounds.ts # Bundled sound management│ ├── notification.ts # Main plugin logic│ ├── plugin-config.ts # Configuration management│ ├── sounds.ts # Sound selection and paths│ └── test-utils.ts # Testing utilities├── typings/ # TypeScript type definitions├── index.ts # Plugin entry point├── package.json # Package configuration├── tsconfig.json # TypeScript configuration├── tsconfig.test.json # Test TypeScript configuration├── eslint.config.cjs # ESLint configuration└── .prettierrc # Prettier configurationKey Directories
Section titled “Key Directories”Contains all source code:
- Core modules:
notification.ts,plugin-config.ts,sounds.ts,bundled-sounds.ts,schema-validator.ts - Sound data:
sound-data/directory with faction-specific sound entries - Tests:
*.test.tsfiles for unit and integration tests - Utilities:
test-utils.tsfor testing helpers
Contains bundled WAV files:
- alliance/: 50+ Alliance unit sounds
- horde/: 50+ Horde unit sounds
Documentation files:
- API documentation: API reference
- Architecture: System design and component diagrams
- Workflows: CI/CD pipeline documentation
- Schemas: JSON schema definitions
Pages and Documentation Site
Section titled “Pages and Documentation Site”Documentation Structure
Section titled “Documentation Structure”The project includes a comprehensive documentation site built with Astro and Starlight, located in the pages/ directory. The documentation is automatically deployed to GitHub Pages on every push to the main branch.
Building the Documentation Site
Section titled “Building the Documentation Site”The documentation site requires several generation steps before the Astro build:
# Full build process (from pages directory)cd pagesbun run build
# Development mode with hot reloadbun run devBuild Pipeline
Section titled “Build Pipeline”The build process follows this sequence:
-
Generate Favicon (
bun run generate-favicon)- Dynamically generates
pages/public/favicon.svg - Uses the blocky text utility with “W” character
- Matches the Warcraft-themed aesthetic of the logos
- Generated file is gitignored and created on each build
- Dynamically generates
-
Transform Documentation (
bun run transform)- Copies files from
docs/topages/src/content/docs/ - Converts markdown to Astro-compatible format
- Copies files from
-
Build Astro Site (
astro build)- Builds the static site to
pages/dist/
- Builds the static site to
-
Fix Links (
bun run fix-links)- Updates internal links for GitHub Pages deployment
Regenerating the Favicon
Section titled “Regenerating the Favicon”The favicon is automatically generated during the build process, but you can regenerate it manually:
cd pagesbun run generate-faviconThe favicon generation script (pages/generate-favicon.mjs):
- Creates a blocky “W” character using the same style as the project logos
- Outputs to
pages/public/favicon.svg - Uses dark theme with 8px block size for optimal rendering
- The generated file is gitignored and must be regenerated for each build
Configuration:
The favicon can be customized by modifying pages/generate-favicon.mjs:
const faviconSVG = blockyTextToSVG('W', { theme: 'dark', // 'light' or 'dark' blockSize: 8, // Size of each pixel block charSpacing: 0, // Spacing between characters optimize: true, // Enable path optimization});Documentation Development Workflow
Section titled “Documentation Development Workflow”- Edit source documentation in the
docs/directory - Run development server:
Terminal window cd pagesbun run dev - View changes at
http://localhost:4321 - Build for production:
Terminal window bun run build
Documentation Scripts
Section titled “Documentation Scripts”All documentation scripts are run from the pages/ directory:
{ "generate-favicon": "bun generate-favicon.mjs", "transform": "node transform-docs.js", "fix-links": "node fix-links.js", "test-links": "node test-links.js", "dev": "bun run generate-favicon && bun run transform && astro dev", "build": "bun run generate-favicon && bun run transform && astro build && bun run fix-links", "preview": "astro preview", "verify": "bun run test-links"}Configuration Validation
Section titled “Configuration Validation”The plugin implements runtime validation of plugin.json configuration using Zod schemas. Configuration is validated automatically when the plugin loads, ensuring that any configuration errors are caught early with clear, actionable error messages.
Validation Architecture
Section titled “Validation Architecture”Schema Definition
Section titled “Schema Definition”The validation schema is defined in src/schema-validator.ts and enforces:
- faction: Must be one of
'alliance','horde', or'both'(optional) - soundsDir: Must be a string (optional)
- No extra keys: Unknown configuration keys are rejected
Validation Behavior
Section titled “Validation Behavior”Valid Configuration
Section titled “Valid Configuration”// Valid: Only alliance sounds{ faction: 'alliance' }
// Valid: Custom sounds directory{ soundsDir: '/custom/path/to/sounds' }
// Valid: Both settings{ faction: 'horde', soundsDir: '~/.cache/sounds' }
// Valid: Empty (uses defaults){}Invalid Configuration
Section titled “Invalid Configuration”// Invalid: Unknown faction{ faction: 'night-elf' }// Error: faction: Invalid enum value. Must be one of: 'alliance', 'horde', 'both'
// Invalid: Wrong type for soundsDir{ soundsDir: 123 }// Error: soundsDir: Expected string, received undefined
// Invalid: Unrecognized keys{ faction: 'alliance', unknownKey: 'value' }// Error: Unrecognized configuration key(s): unknownKey. Only 'soundsDir' and 'faction' are allowed.Error Messages
Section titled “Error Messages”Validation errors provide specific, actionable feedback:
[Warcraft Notifications] Configuration validation failed: - faction: Invalid enum value. Must be one of: 'alliance', 'horde', 'both' - soundsDir: Expected string, received undefined Configuration file: /path/to/.opencode/plugin.jsonTesting Validation
Section titled “Testing Validation”When writing tests that involve configuration:
import { validatePluginConfig } from './schema-validator';
describe('My Feature', () => { it('should handle valid config', () => { const result = validatePluginConfig({ faction: 'alliance' }); expect(result.valid).toBe(true); });
it('should reject invalid config', () => { const result = validatePluginConfig({ faction: 'invalid' }); expect(result.valid).toBe(false); expect(result.errors).toBeDefined(); });});Debugging Validation Issues
Section titled “Debugging Validation Issues”If you encounter validation errors during development:
- Check the error message: It will specify exactly which field is invalid and why
- Verify the schema: Look at
docs/schemas/plugin.json.schemafor the expected structure - Enable debug mode: Set
DEBUG_OPENCODE=1to see validation warnings - Test your config: Use
validatePluginConfig()in tests to verify expected behavior
Development Workflow
Section titled “Development Workflow”1. Create a Feature Branch
Section titled “1. Create a Feature Branch”git checkout -b feature/your-feature-name2. Make Changes
Section titled “2. Make Changes”Edit source files in src/ directory:
// Example: Adding a new sound categoryexport const newUnitSounds = { newUnitSelected: ['new_unit_selected1.wav', 'new_unit_selected2.wav'], newUnitAcknowledge: ['new_unit_acknowledge1.wav'],};3. Run Tests
Section titled “3. Run Tests”# Run all testsbun run test
# Run tests in watch modebun run test:watch
# Run tests with coveragebun run test:coverage
# Run verbose testsbun run test:verbose4. Type Check
Section titled “4. Type Check”bun run type-check5. Lint Code
Section titled “5. Lint Code”# Check for linting errorsbun run lint
# Auto-fix linting errorsbun run lint --fix6. Format Code
Section titled “6. Format Code”# Format all filesbun run format
# Check formatting without changesbun run format:check7. Commit Changes
Section titled “7. Commit Changes”Use conventional commit messages:
git add .git commit -m "feat: add new unit sound category"Commit Message Format:
feat:- New featurefix:- Bug fixdocs:- Documentation changesstyle:- Code style changes (formatting, etc.)refactor:- Code refactoringtest:- Adding or updating testschore:- Maintenance tasks
8. Push and Create PR
Section titled “8. Push and Create PR”git push origin feature/your-feature-nameThen create a Pull Request on GitHub.
Testing
Section titled “Testing”Test Structure
Section titled “Test Structure”Writing Tests
Section titled “Writing Tests”Unit Tests
Section titled “Unit Tests”Test individual functions in isolation:
import { describe, test, expect } from 'bun:test';import { determineSoundFaction } from './sounds';
describe('determineSoundFaction', () => { test('should identify Alliance sounds', () => { expect(determineSoundFaction('human_selected1.wav')).toBe('alliance'); expect(determineSoundFaction('knight_acknowledge1.wav')).toBe('alliance'); });
test('should identify Horde sounds', () => { expect(determineSoundFaction('orc_selected1.wav')).toBe('horde'); expect(determineSoundFaction('death_knight_acknowledge1.wav')).toBe('horde'); });});Integration Tests
Section titled “Integration Tests”Test component interactions:
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';import { loadPluginConfig } from './plugin-config';import { createTempDir, cleanupTempDir } from './test-utils';
describe('Plugin Configuration Integration', () => { let tempDir: string;
beforeEach(async () => { tempDir = await createTempDir(); });
afterEach(async () => { await cleanupTempDir(tempDir); });
test('should load project-specific configuration', async () => { // Create test config const configPath = join(tempDir, '.opencode', 'plugin.json'); await mkdir(dirname(configPath), { recursive: true }); await writeFile( configPath, JSON.stringify({ '@pantheon-ai/opencode-warcraft-notifications': { faction: 'horde', }, }), );
// Load config const config = await loadPluginConfig('@pantheon-ai/opencode-warcraft-notifications'); expect(config.faction).toBe('horde'); });});Edge Case Tests
Section titled “Edge Case Tests”Test boundary conditions:
describe('Edge Cases', () => { test('should handle empty sound directory', async () => { const sounds = await getSoundsByFaction('alliance'); expect(sounds).toBeArray(); expect(sounds.length).toBeGreaterThan(0); });
test('should handle missing configuration file', async () => { const config = await loadPluginConfig('nonexistent-plugin'); expect(config).toEqual({}); });});Failure Tests
Section titled “Failure Tests”Test error handling:
describe('Error Handling', () => { test('should handle invalid faction', () => { expect(() => { // @ts-expect-error Testing invalid input getSoundsByFaction('invalid'); }).toThrow(); });
test('should handle missing sound file gracefully', async () => { const exists = await soundExists('nonexistent.wav', 'alliance'); expect(exists).toBe(false); });});Test Utilities
Section titled “Test Utilities”Use test-utils.ts for common testing patterns:
import { createMockContext, createTempDir, cleanupTempDir } from './test-utils';
// Create mock OpenCode contextconst ctx = createMockContext();
// Create temporary directory for testsconst tempDir = await createTempDir();
// Cleanup after testsawait cleanupTempDir(tempDir);Running Tests
Section titled “Running Tests”# Run all testsbun test
# Run specific test filebun test src/sounds.test.ts
# Run tests matching patternbun test --test-name-pattern "faction"
# Run tests with coveragebun test --coverage
# Run tests in watch modebun test --watchCoverage Goals
Section titled “Coverage Goals”- Overall coverage: > 80%
- Critical paths: > 95%
- Edge cases: > 70%
Code Quality
Section titled “Code Quality”Linting
Section titled “Linting”The project uses ESLint with TypeScript support:
# Check for linting errorsbun run lint
# Auto-fix linting errorsbun run lint --fixESLint Configuration (eslint.config.cjs):
- TypeScript rules
- Import/export rules
- JSDoc rules
- Code quality rules (SonarJS)
- Prettier integration
Formatting
Section titled “Formatting”The project uses Prettier for code formatting:
# Format all filesbun run format
# Check formattingbun run format:checkPrettier Configuration (.prettierrc):
{ "semi": true, "trailingComma": "all", "singleQuote": true, "printWidth": 100, "tabWidth": 2}Type Checking
Section titled “Type Checking”TypeScript is used for type safety:
# Type check without emitting filesbun run type-check
# Build TypeScriptbun run build
# Watch modebun run devTypeScript Configuration (tsconfig.json):
- Strict mode enabled
- ES2022 target
- Module resolution: bundler
- Path aliases supported
Debugging
Section titled “Debugging”Debug Mode
Section titled “Debug Mode”Enable debug logging:
DEBUG_OPENCODE=1 bun testDebug Output:
- Configuration loading attempts
- Sound installation progress
- File operation results
- Error details
Debugging Tests
Section titled “Debugging Tests”Use Bun’s built-in debugger:
# Run tests with debuggerbun --inspect test
# Run specific test with debuggerbun --inspect test src/sounds.test.tsDebugging Plugin in OpenCode
Section titled “Debugging Plugin in OpenCode”-
Enable debug mode:
Terminal window DEBUG_OPENCODE=1 opencode -
Check logs:
- Configuration loading
- Sound installation
- Event handling
- Sound playback
-
Verify sound files:
Terminal window # Check default data directoryls -la ~/.local/share/opencode/storage/plugin/@pantheon-ai/opencode-warcraft-notifications/sounds/# Check alliance soundsls -la ~/.local/share/opencode/storage/plugin/@pantheon-ai/opencode-warcraft-notifications/sounds/alliance/# Check horde soundsls -la ~/.local/share/opencode/storage/plugin/@pantheon-ai/opencode-warcraft-notifications/sounds/horde/
Common Issues
Section titled “Common Issues”Sound Not Playing
Section titled “Sound Not Playing”Configuration Not Loading
Section titled “Configuration Not Loading”# Check project configcat .opencode/plugin.json
# Check global configcat ~/.config/opencode/plugin.json
# Verify JSON syntaxbun run validate:schemaContributing
Section titled “Contributing”Contribution Workflow
Section titled “Contribution Workflow”Code Review Checklist
Section titled “Code Review Checklist”- Tests pass locally
- Code follows style guide
- Types are properly defined
- Documentation is updated
- Commit messages follow convention
- No console.log statements (use DEBUG_OPENCODE)
- Error handling is appropriate
- Performance impact is minimal
Pull Request Template
Section titled “Pull Request Template”## Description
Brief description of changes
## Type of Change
- [ ] Bug fix- [ ] New feature- [ ] Breaking change- [ ] Documentation update
## Testing
- [ ] Unit tests added/updated- [ ] Integration tests added/updated- [ ] Manual testing performed
## Checklist
- [ ] Code follows style guidelines- [ ] Self-review completed- [ ] Documentation updated- [ ] Tests pass locally- [ ] No new warningsRelease Process
Section titled “Release Process”Automated Release Workflow
Section titled “Automated Release Workflow”Version Bump Types
Section titled “Version Bump Types”The workflow automatically determines version bumps:
- MAJOR: Breaking changes, API changes
- MINOR: New features, backwards-compatible additions
- PATCH: Bug fixes, documentation, small improvements
Manual Release
Section titled “Manual Release”If needed, trigger a manual release:
# Trigger workflow with specific version typegh workflow run smart-version-bump.yml -f version_type=minorRelease Checklist
Section titled “Release Checklist”- All tests pass
- Documentation is up-to-date
- CHANGELOG is updated (automated)
- Version bump is appropriate
- No breaking changes (unless MAJOR)
Best Practices
Section titled “Best Practices”1. Code Organization
Section titled “1. Code Organization”- Keep functions small and focused
- Use descriptive variable names
- Group related functionality
- Avoid deep nesting
2. Error Handling
Section titled “2. Error Handling”// Good: Graceful error handlingtry { await installBundledSoundsIfMissing(dataDir);} catch (err) { if (process.env.DEBUG_OPENCODE) { console.warn('Installation failed:', err); } // Continue with existing sounds}
// Bad: Unhandled errorsawait installBundledSoundsIfMissing(dataDir); // May throw3. Type Safety
Section titled “3. Type Safety”// Good: Explicit typesconst getSoundPath = ( filename: string, faction: 'alliance' | 'horde', dataDir?: string,): string => { // ...};
// Bad: Implicit anyconst getSoundPath = (filename, faction, dataDir) => { // ...};4. Testing
Section titled “4. Testing”// Good: Descriptive test namestest('should return alliance for human unit sounds', () => { expect(determineSoundFaction('human_selected1.wav')).toBe('alliance');});
// Bad: Vague test namestest('faction test', () => { expect(determineSoundFaction('human_selected1.wav')).toBe('alliance');});5. Documentation
Section titled “5. Documentation”/** * Get the full path to a sound file in the faction-specific subdirectory. * * @param filename - Sound filename (e.g., 'human_selected1.wav') * @param faction - Faction the sound belongs to * @param dataDir - Optional override data directory * @returns Absolute path to the sound file * * @example * ```typescript * const path = getSoundPath('human_selected1.wav', 'alliance'); * // Returns: '/home/user/.local/share/.../alliance/human_selected1.wav' * ``` */export const getSoundPath = ( filename: string, faction: 'alliance' | 'horde', dataDir?: string,): string => { // ...};Resources
Section titled “Resources”Documentation
Section titled “Documentation”- Bun - JavaScript runtime and toolkit
- ESLint - Linting
- Prettier - Code formatting
- TypeScript - Type checking
Community
Section titled “Community”Related Documentation
Section titled “Related Documentation”- API Documentation - Complete technical API reference
- Architecture Documentation - System design and components
- Deployment Guide - Installation and operations
- CI/CD Pipeline - Pipeline technical reference
Document Version: 1.0
Last Updated: 2025-11-10
Maintained By: Pantheon AI Team