← All @molecule/* packages · App templates

@molecule/api-git-provider

Core interface · git-provider · API (Node) · v1.0.3 · Apache-2.0

Abstract interface for a git hosting provider — OAuth endpoints, token auth shape, repository listing and lookup — so an app can support GitHub, GitLab, Gitea or any other host without naming one.

npm install @molecule/api-git-provider

npm · Source on GitHub

How it works

@molecule/api-git-provider is the git-provider core interface on the API (Node) side: the API your app calls, with no vendor inside.

Choose the implementation by bonding one of its 4 providers: @molecule/api-git-provider-gitea, @molecule/api-git-provider-github, @molecule/api-git-provider-gitlab, @molecule/api-git-provider-smolforge.

import {
  listGitProviders,
  registerGitProvider,
  requireGitProvider,
} from '@molecule/api-git-provider'
import { provider as github } from '@molecule/api-git-provider-github'
import { provider as gitea } from '@molecule/api-git-provider-gitea'

registerGitProvider(github)
registerGitProvider(gitea)

// What a "connect your repo" picker offers — not a hardcoded list.
listGitProviders().map((p) => ({ id: p.id, label: p.label }))

const p = requireGitProvider('github')
const repos = await p.listRepositories({
  host: p.defaultHost,
  token: '<oauth token>',
  page: 1,
  perPage: 30,
})

Providers (4): @molecule/api-git-provider-gitea, @molecule/api-git-provider-github, @molecule/api-git-provider-gitlab, @molecule/api-git-provider-smolforge

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

@molecule/api-git-provider — the abstract interface for a git hosting provider: OAuth endpoints, the token auth shape a push expects, and repository listing/lookup.

This exists because "which git hosts do we support?" kept being answered by a closed union inside the application:

const GIT_PROVIDERS = ['github', 'gitlab'] as const
const PROVIDER_DEFAULTS: Record<GitProvider, …>      // OAuth URLs + scopes
const TOKEN_USERNAME:    Record<string, string>      // x-access-token | oauth2
const DEFAULT_PROVIDER_HOSTS: Record<GitProvider, …> // github.com | gitlab.com
provider === 'github' ? `${base}/repos/${path}` : `${base}/projects/${…}`

Four parallel tables and a scatter of ternaries, all keyed off that union, so adding a host meant editing the app. Behind this interface the supported set becomes whichever bonds are wired, and a consumer never names a vendor.

Quick Start

import {
  listGitProviders,
  registerGitProvider,
  requireGitProvider,
} from '@molecule/api-git-provider'
import { provider as github } from '@molecule/api-git-provider-github'
import { provider as gitea } from '@molecule/api-git-provider-gitea'

registerGitProvider(github)
registerGitProvider(gitea)

// What a "connect your repo" picker offers — not a hardcoded list.
listGitProviders().map((p) => ({ id: p.id, label: p.label }))

const p = requireGitProvider('github')
const repos = await p.listRepositories({
  host: p.defaultHost,
  token: '<oauth token>',
  page: 1,
  perPage: 30,
})

Type

core

Installation

npm install @molecule/api-git-provider

API

Interfaces

GetRepositoryInput

Input for a single repository lookup.

interface GetRepositoryInput {
  /** Host to query. */
  host: string
  /** OAuth access token, or null for an unauthenticated (public) lookup. */
  token: string | null
  /** `owner/name` path. */
  path: string
}

GitProvider

A git hosting provider.

Everything here was a per-vendor branch or lookup table inside an application before this interface existed: provider === 'github' ? … : … for API bases, headers, list endpoints and response shapes, plus four parallel Record<GitProvider, …> tables keyed off a closed union. Adding a host meant editing the app. A provider bond absorbs all of it, so the set of supported hosts becomes "whichever bonds are wired".

interface GitProvider {
  /** Stable identifier, e.g. `github`. Used as the bond name and in stored credentials. */
  id: string

  /** Human-readable name for pickers, e.g. `GitHub`. */
  label: string

  /**
   * The public host this provider lives on, e.g. `github.com`.
   *
   * Load-bearing for security, not just defaults: an OAuth token that grants
   * repo read/write must never be embedded in a remote URL for an arbitrary
   * user-supplied host, so a consumer binds tokens to this host (plus any
   * configured self-hosted endpoint) and refuses everything else.
   */
  defaultHost: string

  /** How this provider authenticates — OAuth flow, or a user-minted token. */
  auth: GitProviderAuth

  /**
   * HTTPS basic-auth username to pair with the token as the password, or NULL
   * when the provider expects the account's own username.
   *
   * Nullable because it is not always a per-provider constant: GitHub wants the
   * literal `x-access-token` and GitLab `oauth2`, but SmolForge wants the
   * user's Forge username — a per-CREDENTIAL value the provider cannot know.
   * A consumer that finds null must substitute the connected account's
   * username. The wrong username fails as an opaque 401 at push time, nowhere
   * near the code that chose it.
   */
  basicAuthUsername: string | null

  /**
   * REST API base URL for a host.
   *
   * A parameter rather than a constant because the same provider serves a
   * different base for its public host than for a self-hosted instance —
   * `api.github.com` vs `<host>/api/v3`.
   *
   * @param host - The host being addressed.
   * @returns The API base URL, without a trailing slash.
   */
  apiBaseForHost(host: string): string

  /**
   * Headers for an API call, including auth when a token is given.
   *
   * @param token - OAuth access token, or null for unauthenticated requests.
   * @returns Headers to send.
   */
  apiHeaders(token: string | null): Record<string, string>

  /**
   * List repositories the token can see, newest activity first.
   *
   * @param input - Host, token and pagination.
   * @returns Normalized repositories. Empty array when the page is past the end.
   */
  listRepositories(input: ListRepositoriesInput): Promise<GitRepository[]>

  /**
   * Look up one repository.
   *
   * @param input - Host, token and `owner/name` path.
   * @returns The repository, or null when it does not exist or is not visible.
   */
  getRepository(input: GetRepositoryInput): Promise<GitRepository | null>
}

GitProviderOAuth

OAuth endpoints and scope for a provider.

interface GitProviderOAuth {
  /** Authorization endpoint the user is redirected to. */
  authorizeUrl: string
  /** Token exchange endpoint. */
  tokenUrl: string
  /**
   * Space-separated scopes.
   *
   * State what each scope is FOR. GitLab needs `read_api` on top of the
   * `*_repository` scopes because the repository scopes cover only the git
   * protocol, not the REST API a repo picker calls — an omission that fails
   * only at the picker, long after the OAuth flow looks successful.
   */
  scope: string
}

GitRepository

A repository as this app understands it, independent of whose API described it. GitHub calls it full_name/clone_url, GitLab path_with_namespace/ http_url_to_repo; a consumer should never have to know which.

interface GitRepository {
  /** `owner/name`, however the provider spells it. */
  fullName: string
  /** HTTPS clone URL. */
  url: string
  /** Whether the repository is private. Null when the provider does not say. */
  private: boolean | null
  /** Default branch name, or null when the provider does not report one. */
  defaultBranch: string | null
  /** Approximate size in KB, or null when unknown. */
  sizeKb: number | null
  /** ISO 8601 timestamp of the last push/activity, or null. */
  updatedAt: string | null
  /** Short description, or null. */
  description: string | null
}

ListRepositoriesInput

Input for a paginated repository listing.

interface ListRepositoriesInput {
  /** Host to query — the provider's default, or a self-hosted instance. */
  host: string
  /** OAuth access token. */
  token: string
  /** 1-based page number. */
  page: number
  /** Page size. */
  perPage: number
}

Types

GitProviderAuth

How a provider authenticates.

A discriminated union rather than an optional OAuth block, because the first non-OAuth provider proved the difference is structural, not a missing field. SmolForge has no authorize/token endpoints at all — the user mints a personal access token and uses it directly. Modelling that as "OAuth with empty URLs" would let a consumer start an authorize redirect to "".

type GitProviderAuth =
  | ({ kind: 'oauth' } & GitProviderOAuth)
  | {
      kind: 'pat'
      /** Where the user creates a token, for the UI to link to. */
      tokensUrl?: string
    }

Functions

clearGitProviders()

Remove every registered provider. Test teardown only.

function clearGitProviders(): void

getGitProvider(id)

Look up a registered provider.

function getGitProvider(id: string): GitProvider | undefined
  • id — The provider id, e.g. github.

Returns: The provider, or undefined when it is not wired.

hasGitProvider(id)

Whether a provider id is wired.

function hasGitProvider(id: string): boolean
  • id — The provider id.

Returns: True when registered.

listGitProviders()

Every registered provider, sorted by id.

This is what a UI should render as the list of connectable hosts — the set is whatever the deployment wired, never a hardcoded union.

function listGitProviders(): GitProvider[]

Returns: The registered providers.

registerGitProvider(provider)

Register a git provider. Re-registering the same id replaces it.

function registerGitProvider(provider: GitProvider): void
  • provider — The provider to register.

requireGitProvider(id)

Look up a provider, throwing when it is absent.

Use this on a path where a missing provider is a configuration error rather than a branch: the error names what IS wired, because "unknown provider gitlab" is unactionable while "gitlab is not wired; github is" says exactly what to do.

function requireGitProvider(id: string): GitProvider
  • id — The provider id.

Returns: The provider.

Available Providers

ProviderPackage
Gitea@molecule/api-git-provider-gitea
GitHub@molecule/api-git-provider-github
GitLab@molecule/api-git-provider-gitlab
SmolForge@molecule/api-git-provider-smolforge

Injection Notes

  • Named multi-provider, like ai — not a singleton. One deployment has several wired at once because different users connect different hosts. Register by id and look up by id; there is no "current" git provider.
  • defaultHost is a security boundary, not a default. An OAuth token here grants repository read/write, so it must never be embedded in a remote URL for an arbitrary user-supplied host. Bind tokens to defaultHost plus any configured self-hosted endpoint, and refuse the rest.
  • tokenUsername is not cosmetic. Pushing over HTTPS with a token as the password needs the username the provider expects (x-access-token for GitHub, oauth2 for GitLab). The wrong one fails as an opaque auth error at push time, nowhere near the OAuth code that chose it.
  • apiBaseForHost takes the host because the same provider serves a different base for its public host than for a self-hosted instance (api.github.com vs <host>/api/v3). A constant cannot express that.
  • listRepositories returns [] past the last page, never an error — a caller paginating until empty is the normal shape, and a throw there turns an ordinary end-of-list into a failed import.