Skip to content

Architecture

WorkflowKt is organized into three modules with clear separation of concerns:

workflowkt/
├── core/                    # Runtime library
│   └── src/main/kotlin/ir/amirroid/workflowkt/
│       ├── GithubAction.kt           # Core interface
│       ├── ActionResult.kt           # Result types
│       ├── context/                   # Typed event contexts
│       ├── environment/               # Environment variable access
│       ├── models/                    # GitHub API data models
│       ├── core/                      # Context factory & mappers
│       ├── config/                    # JSON configuration
│       ├── serializer/                # Custom serializers
│       └── utils/                     # Constants & extensions
├── gradle-plugin/          # Gradle plugin for task execution
│   └── src/main/kotlin/ir/amirroid/workflowkt/plugin/
│       ├── WorkflowKtPlugin.kt        # Plugin entry point
│       ├── WorkflowKtExtension.kt     # DSL configuration
│       ├── WorkflowKtRunTask.kt       # Task execution logic
│       ├── exception/                 # Custom exceptions
│       └── utils/                     # Class name constants
└── sample/                 # Example actions

Execution Flow

How a Workflow Runs

flowchart TD
    A["./gradlew runWorkflowKt --key=..."] --> B[WorkflowKtRunTask.run]
    B --> C{Test Mode?}
    C -->|Yes| D[Load FakeEnvironment from workflowkt.properties]
    C -->|No| E[Load GithubActionEnvironment from System.getenv]
    D --> F[Create ActionContext via CompositeActionContextFactory]
    E --> F
    F --> G[Read GITHUB_EVENT_PATH JSON payload]
    G --> H[Match eventName to ActionContextMapper]
    H --> I[Map JSON to typed ActionContext]
    I --> J[Invoke GithubAction.run]
    J --> K{ActionResult}
    K -->|Success| L[Write outputs to GITHUB_OUTPUT]
    K -->|Failure| M[Throw WorkflowFailureException]

Key Components

  1. GithubAction<C> — The core interface every workflow implements. Generic over the context type C.

  2. ActionContextMapper<C> — Maps raw JSON event payloads to typed ActionContext instances. One mapper per GitHub event type.

  3. CompositeActionContextFactory — Delegates to the correct mapper based on the event name.

  4. GithubEnvironment — Composite interface providing typed access to all GitHub Actions environment variables.

  5. WorkflowKtRunTask — Gradle task that loads the action class via reflection, creates the environment and context, and invokes the action.

Class Loading

The Gradle plugin uses a URLClassLoader to load action classes at runtime. This provides:

  • Classpath isolation — Actions can use their own dependencies without conflicts
  • Reflection-based instantiation — Supports both classes with no-arg constructors and Kotlin object declarations
  • Generic type resolution — Determines the context type by inspecting the GithubAction<C> type parameter

Event Matching

When not in test mode, the task checks whether the action's context type matches the current GitHub event:

  1. Inspects GithubAction<C> to extract the concrete context type C
  2. Loads all ActionContextMapper instances
  3. Finds the mapper whose context type matches C
  4. Gets the expected event name from that mapper
  5. Compares with GITHUB_EVENT_NAME environment variable
  6. Skips execution if they don't match

Environment Layers

The GithubEnvironment interface composes four environment categories:

GithubEnvironment
├── GithubCoreEnvironments      (GITHUB_ACTION, GITHUB_SHA, GITHUB_WORKFLOW, ...)
├── GithubRefEnvironments       (GITHUB_REF, GITHUB_REF_NAME, GITHUB_HEAD_REF, ...)
├── GithubRunnerEnvironments    (RUNNER_OS, RUNNER_ARCH, RUNNER_TEMP, ...)
└── GithubSystemEnvironments    (CI, HOME, PATH)

In GitHub Actions, these read from System.getenv(). In test mode, a FakeEnvironment loads values from a properties file.

Data Model

All GitHub event payloads are modeled as Kotlin data classes with kotlinx.serialization:

classDiagram
    class ActionContext {
        <<sealed interface>>
        +repository: Repository
    }
    class IssueContext {
        +issue: Issue
        +action: String
        +sender: User
        +changes: Changes
    }
    class PushContext {
        +ref: String
        +commits: List~Commit~
        +headCommit: HeadCommit?
        +pusher: Actor
    }
    class PullRequestContext {
        +pullRequest: PullRequest
        +action: String
        +number: Int
    }
    class ReleaseContext {
        +release: Release
        +action: String
    }
    class ScheduleContext {
        +schedule: String
        +workflow: String
    }
    class WorkflowDispatchContext {
        +inputs: Map~String,String~?
        +ref: String
    }
    class CreateContext {
        +ref: String
        +refType: RefType
        +pusherType: PusherType
    }

    ActionContext <|-- IssueContext
    ActionContext <|-- PushContext
    ActionContext <|-- PullRequestContext
    ActionContext <|-- ReleaseContext
    ActionContext <|-- ScheduleContext
    ActionContext <|-- WorkflowDispatchContext
    ActionContext <|-- CreateContext

Serialization

Timestamps from GitHub payloads are handled by a custom TimestampSerializer that supports both:

  • ISO-8601 strings (e.g., "2025-12-23T21:38:14Z")
  • Unix epoch seconds (e.g., 1734964694)

The JSON configuration uses ignoreUnknownKeys = true to be resilient to GitHub API changes.