Skip to content

Latest commit

 

History

History
488 lines (356 loc) · 13.6 KB

File metadata and controls

488 lines (356 loc) · 13.6 KB

Contributing to Stream Deck iCal Plugin

Thank you for your interest in contributing to the Stream Deck iCal Plugin! This document provides guidelines and information for developers.

Table of Contents

Development Environment Setup

Prerequisites

  • Node.js 20+ (required for Stream Deck Node.js runtime)
  • npm (comes with Node.js)
  • Git
  • Elgato Stream Deck Software 6.0+ (for testing)

Installation

  1. Clone the repository:
git clone https://github.com/pedrofuentes/stream-deck-ical.git
cd stream-deck-ical
  1. Install dependencies:
npm install
  1. Build the plugin:
npm run build

Project Architecture

The plugin is built using:

  • TypeScript for type safety and better developer experience
  • Rollup for bundling the Node.js plugin and Property Inspector
  • Vitest for fast, modern testing
  • @elgato/streamdeck Node.js SDK v2 for Stream Deck integration

Directory Structure

stream-deck-ical/
├── src/                      # TypeScript source code
│   ├── plugin.ts            # Main plugin entry point
│   ├── actions/             # Action implementations
│   │   ├── base-action.ts   # Base class for all actions
│   │   ├── next-meeting.ts  # NextMeeting action
│   │   └── time-left.ts     # TimeLeft action
│   ├── services/            # Business logic layer
│   │   ├── calendar-service.ts     # Calendar fetching & caching
│   │   ├── ical-parser.ts          # iCal parsing
│   │   ├── recurrence-expander.ts  # RRULE expansion
│   │   └── timezone-service.ts     # Timezone conversions
│   ├── utils/               # Utility functions
│   │   ├── time-utils.ts    # Time formatting
│   │   ├── event-utils.ts   # Event filtering/sorting
│   │   └── logger.ts        # Debug logging
│   └── types/               # TypeScript type definitions
│       └── index.ts
├── pi/                      # Property Inspector (HTML/JS)
│   ├── pi.html             # Main PI interface
│   ├── pi.js               # PI logic
│   ├── setup.html          # Settings window
│   ├── setup.js            # Settings logic
│   └── css/                # Styles
├── assets/                  # Images and icons
├── __fixtures__/            # Test data (iCal files)
│   ├── google-calendar/
│   ├── outlook/
│   └── apple/
├── tests/                   # Test files
│   ├── *.test.ts           # Unit tests
│   └── integration/        # Integration tests
├── dist/                    # Build output (gitignored)
├── manifest.json            # Plugin metadata
├── tsconfig.json            # TypeScript configuration
├── rollup.config.js         # Build configuration
└── vitest.config.ts         # Test configuration

Key Concepts

Actions

Actions are the buttons users place on their Stream Deck. Each action:

  • Extends BaseAction class
  • Uses @action decorator with UUID
  • Implements updateDisplay() for live updates
  • Handles user interactions (key press, double press)

Services

Services handle the business logic:

  • CalendarService: Fetches and caches iCal feeds, filters all-day events
  • ICalParser: Parses iCal format with timezone support
  • RecurrenceExpander: Expands recurring events using RRULE
  • TimezoneService: Converts Windows timezones to IANA format

Property Inspector

The PI is a web-based UI (HTML/CSS/JS) that appears in Stream Deck software when configuring the plugin. It communicates with the plugin via WebSocket.

Building the Plugin

Development Build

npm run build

Creates a development build in dist/ with source maps.

Production Build

npm run build:production

Creates an optimized production build in release/com.pedrofuentes.ical.sdPlugin/.

Watch Mode

npm run watch

Automatically rebuilds on file changes (useful during development).

Clean Build

npm run clean
npm run build

Testing

Run All Tests

npm test

Run Tests in Watch Mode

npm run test:watch

Run Tests with Coverage

npm run test:coverage

Writing Tests

Tests use Vitest and follow this structure:

import { describe, it, expect } from 'vitest';
import { myFunction } from '../src/utils/my-util';

describe('myFunction', () => {
  it('should do something', () => {
    expect(myFunction(input)).toBe(expectedOutput);
  });
});

Test Fixtures

iCal test fixtures are in __fixtures__/ organized by provider:

  • google-calendar/ - Google Calendar exports
  • outlook/ - Microsoft Outlook/Office 365 exports
  • apple/ - Apple Calendar/iCloud exports

When adding fixtures:

  1. Use realistic, anonymized data
  2. Include PRODID to test provider detection
  3. Test edge cases (all-day events, recurring, timezones)

Debugging with Stream Deck

Using Stream Deck CLI (Recommended)

The project includes npm scripts for the Stream Deck CLI:

# Link plugin for development (run once)
npm run streamdeck:link

# Start Stream Deck with plugin in debug mode
npm run streamdeck:dev

# Restart the plugin without restarting Stream Deck
npm run streamdeck:restart

# Package plugin for distribution
npm run streamdeck:pack

Enable Debug Mode

Set the STREAMDECK_DEBUG environment variable before building to enable detailed logging and the debug panel:

Windows (PowerShell):

$env:STREAMDECK_DEBUG = "1"
npm run build
streamdeck restart com.pedrofuentes.ical

macOS/Linux:

STREAMDECK_DEBUG=1 npm run build

Debug Mode Behavior:

Feature Debug Mode OFF Debug Mode ON
Log Level INFO (minimal) TRACE (verbose)
Debug Panel Hidden Visible
DEBUG/TRACE logs Suppressed Written to log file
Performance Optimized More overhead

When debug mode is enabled:

  • Log level is set to TRACE (all DEBUG and TRACE messages logged)
  • A Debug Panel appears in the Settings popup showing:
    • Cache status (INIT/LOADING/OK/ERROR)
    • Event count and last fetch time
    • List of upcoming events (first 10)
    • Recent debug logs with timestamps
  • Actions log state changes for troubleshooting

When debug mode is disabled:

  • Log level is set to INFO (cleaner, less verbose logs)
  • Debug Panel is hidden from the Settings popup
  • Only important messages (INFO, WARN, ERROR) are logged

Manual Plugin Linking

If not using the CLI, create a symlink to the Stream Deck plugins directory:

macOS:

ln -s "$(pwd)/dist/com.pedrofuentes.ical.sdPlugin" "$HOME/Library/Application Support/com.elgato.StreamDeck/Plugins/com.pedrofuentes.ical.sdPlugin"

Windows (PowerShell as Administrator):

New-Item -ItemType SymbolicLink -Path "$env:APPDATA\Elgato\StreamDeck\Plugins\com.pedrofuentes.ical.sdPlugin" -Target "$(pwd)\dist\com.pedrofuentes.ical.sdPlugin"

Restart Stream Deck Software

The plugin will now appear in the Stream Deck software.

View Logs

Plugin Logs (Node.js):

  • The plugin uses @elgato/streamdeck logger
  • Set STREAMDECK_DEBUG=1 for verbose debug logging
  • Logs visible in Stream Deck CLI output or log files

Stream Deck Logs Location:

  • macOS: ~/Library/Logs/ElgatoStreamDeck/
  • Windows: %APPDATA%\Elgato\StreamDeck\logs\

Debug with Chrome DevTools

Property Inspector can be debugged:

  1. Right-click on action in Stream Deck software
  2. Select "Inspect Property Inspector"
  3. Chrome DevTools opens for the PI

Code Style and Conventions

TypeScript

  • Use strict mode (strict: true in tsconfig.json)
  • Prefer interface over type for object shapes
  • Use explicit return types for public functions
  • Avoid any - use unknown if type is truly unknown

Naming

  • Files: kebab-case (calendar-service.ts)
  • Classes: PascalCase (CalendarService)
  • Functions/Variables: camelCase (updateCalendar)
  • Constants: UPPER_SNAKE_CASE (UPDATE_INTERVAL)
  • Types/Interfaces: PascalCase (CalendarEvent)

Imports

Always use .js extension in imports (TypeScript transpiles .ts → .js):

import { myFunction } from './my-file.js';  // ✅ Correct
import { myFunction } from './my-file';     // ❌ Wrong

Error Handling

  • Log errors with context using logger.error()
  • Use specific error types when possible
  • Handle errors gracefully - plugin should never crash

Comments

  • Use JSDoc for public APIs
  • Inline comments for complex logic only
  • Keep comments up-to-date with code

Commit Messages

Follow Conventional Commits:

<type>(<scope>): <subject>

<body>

<footer>

Types

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • test: Test changes
  • refactor: Code refactoring
  • chore: Build/tooling changes

Examples

feat(timezone): add Windows timezone conversion

Implement conversion of Windows timezone names (e.g., "Eastern Standard Time")
to IANA format using windows-iana library.

Closes #12
fix(parser): handle escaped characters in iCal text fields

Parse \n, \,, \; correctly according to RFC 5545.

Provider-Specific Notes

Google Calendar

  • Uses IANA timezone names (e.g., America/New_York)
  • PRODID: -//Google Inc//Google Calendar//EN
  • May include X-GOOGLE-CONFERENCE for Meet links
  • Includes DTSTAMP, CREATED, LAST-MODIFIED fields

Microsoft Outlook / Office 365

  • Uses Windows timezone names (e.g., Eastern Standard Time)
  • Often wraps TZID in quotes: TZID="Eastern Standard Time"
  • PRODID: -//Microsoft Corporation//Outlook//EN
  • May include X-MICROSOFT-CDO-* properties
  • Requires windows-iana conversion

Apple Calendar

  • Uses IANA timezone names
  • PRODID: -//Apple Inc.//Mac OS X//EN or -//Apple Inc.//iOS//EN
  • May include X-APPLE-* properties
  • Clean iCal format, follows spec closely

Common Edge Cases

  1. All-day events: VALUE=DATE parameter, no time component (filtered by default via excludeAllDay setting)
  2. Recurring events: RRULE with various frequency types
  3. EXDATE: Dates excluded from recurrence
  4. Folded lines: Long lines split with leading whitespace
  5. Escaped text: \n, \,, \; in SUMMARY/DESCRIPTION
  6. DST transitions: Events during daylight saving time changes

Creating Releases

Release Process

  1. Update version numbers in manifest.json and package.json:

    // manifest.json
    "Version": "X.Y.Z.0"
    
    // package.json
    "version": "X.Y.Z"
  2. Build for production:

    npm run build:production

    This creates an optimized build in release/com.pedrofuentes.ical.sdPlugin/.

  3. Create the plugin package using Stream Deck CLI:

    streamdeck pack "release/com.pedrofuentes.ical.sdPlugin" --output release

    This creates release/com.pedrofuentes.ical.streamDeckPlugin - the official package format.

    Important: Do NOT use Compress-Archive or manual zipping. The Stream Deck CLI creates a properly formatted package that Stream Deck can install.

  4. Create a Git tag:

    git tag -a vX.Y.Z -m "vX.Y.Z - Release description"
    git push origin vX.Y.Z
  5. Create GitHub release with the plugin package:

    gh release create vX.Y.Z "release/com.pedrofuentes.ical.streamDeckPlugin" \
      --title "vX.Y.Z - Release Title" \
      --notes "Release notes here"

Testing a Release Package

Before publishing, test the package:

  1. Uninstall the development plugin:

    # Stop Stream Deck
    Stop-Process -Name "StreamDeck" -Force
    
    # Remove plugin
    Remove-Item "$env:APPDATA\Elgato\StreamDeck\Plugins\com.pedrofuentes.ical.sdPlugin" -Recurse -Force
    
    # Start Stream Deck
    Start-Process "$env:ProgramFiles\Elgato\StreamDeck\StreamDeck.exe"
  2. Install the release package: Double-click com.pedrofuentes.ical.streamDeckPlugin

  3. Verify functionality: Test all actions and settings

Version Numbering

Follow Semantic Versioning:

  • MAJOR: Breaking changes or major rewrites
  • MINOR: New features, backward compatible
  • PATCH: Bug fixes, backward compatible

The manifest uses 4-part version (X.Y.Z.0), package.json uses 3-part (X.Y.Z).

Global Settings

The plugin uses these global settings stored via Stream Deck SDK:

Setting Type Default Description
url string '' iCal feed URL
timeWindow number 3 Time window in days (1, 3, 5, or 7)
excludeAllDay boolean true Filter out all-day events
urlVersion number 0 Incremented on force refresh to trigger update

Settings are managed in src/plugin.ts and pi/setup.js.

Questions or Issues?

Thank you for contributing! 🎉