← All @molecule/* packages · App templates
@molecule/api-git-providerCore 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@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
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.tsJSDoc, 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.
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,
})
core
npm install @molecule/api-git-provider
GetRepositoryInputInput 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
}
GitProviderA 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>
}
GitProviderOAuthOAuth 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
}
GitRepositoryA 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
}
ListRepositoriesInputInput 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
}
GitProviderAuthHow 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
}
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.
| Provider | Package |
|---|---|
| Gitea | @molecule/api-git-provider-gitea |
| GitHub | @molecule/api-git-provider-github |
| GitLab | @molecule/api-git-provider-gitlab |
| SmolForge | @molecule/api-git-provider-smolforge |
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.