Skip to main content

GitHub Plugin Deep Dive

Overview

The GitHub plugin is the core Git provider for the Ever Works platform. It manages repositories, branches, commits, pull requests, and OAuth authentication through the GitHub API. As a system plugin, it underpins the entire deployment pipeline by handling the Git operations that push generated sites to GitHub and trigger CI/CD workflows.

Architecture

The plugin implements three interfaces: IPlugin, IGitProviderPlugin, and IOAuthPlugin. Internally it delegates work to three service classes:

  • GitHubApiService -- wraps the Octokit SDK to perform all REST API calls against GitHub (repositories, branches, pull requests, content access).
  • GitHubActionsService -- manages GitHub Actions secrets, variables, workflow dispatch, and workflow enable/disable operations. Uses libsodium-wrappers for secret encryption.
  • GitOperations (from @ever-works/plugin/git) -- handles local Git operations such as clone, pull, commit, push, and branch management via isomorphic-git.
GitHubPlugin
|-- GitHubApiService (Octokit, REST API)
|-- GitHubActionsService (Octokit + libsodium)
|-- GitOperations (isomorphic-git, local fs)

The plugin is loaded during platform startup. On onLoad, it initialises GitOperations with auth and clone-URL callbacks so that local Git operations use the correct GitHub token format (x-access-token).

Configuration

Environment Variables

VariableRequiredDescription
PLUGIN_GITHUB_CLIENT_IDYes (admin)GitHub OAuth App Client ID
PLUGIN_GITHUB_CLIENT_SECRETYes (admin)GitHub OAuth App Client Secret

Settings Schema

interface GitHubSettings {
readonly clientId?: string; // OAuth App Client ID
readonly clientSecret?: string; // OAuth App Client Secret (x-secret)
readonly apiBaseUrl?: string; // Default: 'https://api.github.com'
}
  • configurationMode: admin-only -- only platform administrators configure credentials.
  • apiBaseUrl supports GitHub Enterprise installations by pointing to a custom API endpoint.

Capabilities

CapabilityDescription
git-providerFull repository lifecycle: create, read, update, delete, fork, template
oauthGitHub OAuth 2.0 authorization code flow

Default OAuth scopes requested: user:email, read:user, repo, delete_repo, workflow, write:repo_hook, read:org, project.

API Reference

Authentication

MethodSignatureDescription
getAuth(token: string) => GitAuthReturns { username: 'x-access-token', password: token }
getAuthorizationUrl(state: string, config?: Partial<OAuthConfig>) => stringBuilds GitHub OAuth authorize URL
exchangeCodeForToken(code: string, config?: Partial<OAuthConfig>) => Promise<OAuthToken>Exchanges authorization code for access token
getAuthenticatedUser(token: string) => Promise<OAuthUser>Returns authenticated user info

Repository Operations

MethodSignature
getRepository(owner, repo, token) => Promise<GitRepository | null>
listRepositories(token, page?, perPage?, options?) => Promise<GitRepositoryWithPermissions[]>
createRepository(options: CreateRepoOptions, token) => Promise<GitRepository>
deleteRepository(owner, repo, token) => Promise<void>
updateRepository(owner, repo, data, token) => Promise<GitRepository>
forkRepository(owner, repo, options, token) => Promise<GitRepository | null>
createRepositoryFromTemplate(templateOwner, templateRepo, options, token) => Promise<GitRepository | null>

Branch & Commit Operations

MethodSignature
listBranches(owner, repo, token) => Promise<GitBranch[]>
createBranch(owner, repo, name, fromRef, token) => Promise<GitBranch>
deleteBranch(owner, repo, name, token) => Promise<void>
getLatestCommit(owner, repo, branch, token) => Promise<GitCommit | null>

Pull Request Operations

MethodSignature
createPullRequest(options: CreatePROptions, token) => Promise<GitPullRequest>
getPullRequest(owner, repo, prNumber, token) => Promise<GitPullRequest | null>
mergePullRequest(owner, repo, prNumber, options, token) => Promise<MergeResult>
listPullRequests(owner, repo, options, token) => Promise<GitPullRequest[]>
closePullRequest(owner, repo, prNumber, token) => Promise<GitPullRequest>

GitHub Actions

MethodSignature
getRepositoryPublicKey(owner, repo, token) => Promise<GitHubPublicKey>
setActionSecret(data, publicKey, token) => Promise<void>
setActionVariable(data, token) => Promise<void>
enableDeploymentWorkflows(owner, repo, token, withDelay?) => Promise<void>
dispatchWorkflow(data, token) => Promise<void>

Local Git Operations

MethodSignature
cloneOrPull(options: GitCloneOptions) => Promise<string>
commit(dir, message, committer?) => Promise<string>
push(options: GitPushOptions) => Promise<void>
switchBranch(dir, branch, create?) => Promise<string>
getStatus(dir) => Promise<GitFileChange[]>

Implementation Details

Octokit Client Creation

Every API call creates a fresh Octokit instance with the provided token and optional baseUrl. This stateless approach ensures per-request authentication and avoids stale token issues.

Fork Polling

forkRepository polls the newly created fork up to 24 times with a 5-second interval (REPO_CHECK_INTERVAL_MS = 5000, MAX_REPO_CHECK_ATTEMPTS = 24) because GitHub forks are asynchronous and may take time to become available.

Secret Encryption

setActionSecret uses libsodium-wrappers to perform Curve25519 sealed-box encryption before sending the encrypted value to the GitHub Actions Secrets API. The repository public key is fetched first via getRepositoryPublicKey.

Deployment Workflow Management

enableDeploymentWorkflows activates only the predefined deployment workflows (Vercel Deployment, Production deployment) and disables all others. It includes an optional 7-second delay to allow newly created repositories to register their workflows.

Description Sanitization

Repository descriptions are sanitized by stripping newlines and truncating to 500 characters to comply with GitHub API constraints.

Usage Examples

// Get authenticated user
const user = await githubPlugin.getUser(token);

// Create a repository
const repo = await githubPlugin.createRepository({ name: 'my-work', isPrivate: true, organization: 'my-org' }, token);

// Create a branch and push changes
await githubPlugin.cloneOrPull({
owner: 'my-org',
repo: 'my-work',
token,
branch: 'main'
});
await githubPlugin.switchBranch(localDir, 'feature-update', true);
await githubPlugin.addAll(localDir);
await githubPlugin.commit(localDir, 'Update work content');
await githubPlugin.push({ dir: localDir, token, remote: 'origin', branch: 'feature-update' });

// Dispatch a deployment workflow
await githubPlugin.dispatchWorkflow(
{ workflow: 'deploy_vercel.yaml', branch: 'main', owner: 'my-org', repo: 'my-work' },
token
);

Rate Limiting & Quotas

  • GitHub REST API: 5,000 requests per hour for authenticated requests (standard GitHub rate limit).
  • Pagination: listBranches uses Octokit's automatic pagination iterator with per_page: 100.
  • Concurrent operations: enableDeploymentWorkflows and Promise.allSettled are used to handle multiple workflow toggles concurrently without failing on individual errors.
  • The plugin does not implement its own rate-limit tracking; it relies on GitHub's X-RateLimit-* response headers and Octokit's built-in handling.

Error Handling

  • 404 errors are caught and returned as null or false for methods like getRepository, getPullRequest, getLatestCommit, and hasRepositoryAccess, allowing callers to distinguish "not found" from unexpected errors.
  • 403 errors are treated the same as 404 in listRepositories (org scope) and hasRepositoryAccess to handle permission-denied cases gracefully.
  • OAuth errors from the token exchange endpoint are parsed from the JSON response and rethrown with the error_description field.
  • Promise.allSettled is used for batch operations (workflow enable/disable, Actions permissions) so that one failure does not block the rest.