Skip to content

Custom Adapters

For special use cases, you can create custom adapters to control how type-git executes git commands and handles file operations.

When to Create Custom Adapters

  • Testing with mocked git commands
  • Custom process spawning (containers, SSH)
  • Custom file system (virtual FS, cloud storage)
  • Logging/auditing all git operations

ExecAdapter Interface

interface ExecAdapter {
getCapabilities(): Capabilities;
spawn(options: SpawnOptions, handlers?: StreamHandler): Promise<SpawnResult>;
spawnStreaming?(options: SpawnOptions): SpawnHandle;
}
type SpawnOptions = {
argv: string[];
env?: Record<string, string>;
cwd?: string;
signal?: AbortSignal;
};
type SpawnResult = {
stdout: string;
stderr: string;
exitCode: number;
aborted: boolean;
};

FsAdapter Interface

interface FsAdapter {
createTempFile(prefix?: string): Promise<string>;
deleteFile(filePath: string): Promise<void>;
deleteDirectory(dirPath: string): Promise<void>;
exists(filePath: string): Promise<boolean>;
tail(options: TailOptions): Promise<void>;
tailStreaming?(filePath: string, options?: TailStreamingOptions): TailHandle;
}

Example: Logging Adapter

import { createGit, createNodeAdapters } from 'type-git/node';
import type { ExecAdapter, SpawnOptions, SpawnResult } from 'type-git';
class LoggingExecAdapter implements ExecAdapter {
private inner: ExecAdapter;
constructor(inner: ExecAdapter) {
this.inner = inner;
}
getCapabilities() {
return this.inner.getCapabilities();
}
async spawn(options: SpawnOptions, handlers?: StreamHandler): Promise<SpawnResult> {
console.log('[GIT]', options.argv.join(' '));
const start = Date.now();
const result = await this.inner.spawn(options, handlers);
console.log(`[GIT] Completed in ${Date.now() - start}ms (exit: ${result.exitCode})`);
return result;
}
}
// Usage
const nodeAdapters = createNodeAdapters();
const git = createGit({
adapters: {
exec: new LoggingExecAdapter(nodeAdapters.exec),
fs: nodeAdapters.fs,
},
});

Example: Mock Adapter for Testing

class MockExecAdapter implements ExecAdapter {
private responses: Map<string, SpawnResult> = new Map();
getCapabilities() {
return { runtime: 'node' as const };
}
mockCommand(pattern: string, result: Partial<SpawnResult>) {
this.responses.set(pattern, {
stdout: result.stdout ?? '',
stderr: result.stderr ?? '',
exitCode: result.exitCode ?? 0,
aborted: false,
});
}
async spawn(options: SpawnOptions): Promise<SpawnResult> {
const command = options.argv.join(' ');
for (const [pattern, result] of this.responses) {
if (command.includes(pattern)) {
return result;
}
}
throw new Error(`Unmocked command: ${command}`);
}
}
// Usage in tests
const mockExec = new MockExecAdapter();
mockExec.mockCommand('--version', { stdout: 'git version 2.40.0' });
mockExec.mockCommand('status --porcelain', { stdout: '' });
const git = createGit({
adapters: {
exec: mockExec,
fs: createMockFsAdapter(),
},
});

Environment Isolation

type-git supports environment isolation for scenarios like:

  • Using a custom HOME directory for isolated git config
  • Using a specific version of git from a custom path
  • Setting up credential helpers
  • Running git with custom environment variables

TypeGit Options

The TypeGit class accepts the same options as createGit (except adapters):

import { TypeGit } from 'type-git/node';
// Use isolated HOME for git config
const git = new TypeGit({
home: '/isolated/home',
});
// Global config operations will use /isolated/home/.gitconfig
await git.config.set('user.name', 'Test User');
await git.config.set('user.email', 'test@example.com');
// Clone using isolated config
const repo = await git.clone('https://github.com/user/repo.git', './repo');

Custom Git Binary Path

Use a specific version of git by setting pathPrefix:

const git = new TypeGit({
pathPrefix: ['/opt/git-2.45/bin'], // Prepended to PATH
});
const version = await git.version(); // Uses /opt/git-2.45/bin/git

Credential Helper Configuration

Configure credential helpers for authentication:

const git = new TypeGit({
credential: {
helper: 'store', // Use git-credential-store
},
});
// Or with a custom helper path
const git = new TypeGit({
credential: {
helper: 'my-helper',
helperPath: '/path/to/git-credential-my-helper',
},
});

Combined Example

import { TypeGit } from 'type-git/node';
// Fully isolated git environment
const git = new TypeGit({
home: '/app/git-home', // Isolated .gitconfig
pathPrefix: ['/app/git/bin'], // Custom git binary
env: {
GIT_AUTHOR_NAME: 'CI Bot',
GIT_AUTHOR_EMAIL: 'ci@example.com',
},
credential: {
helper: 'store',
},
});
// All operations use the isolated environment
await git.config.set('user.name', 'CI Bot');
const repo = await git.clone('https://github.com/user/repo.git', './repo');

Using createGit

For full control (including custom adapters), use createGit instead of TypeGit:

import { createGit, createNodeAdapters } from 'type-git/node';
const git = createGit({
adapters: createNodeAdapters(),
gitBinary: '/custom/path/to/git',
env: { GIT_AUTHOR_NAME: 'Custom Author' },
});

Available Options

OptionDescription
adaptersRuntime adapters (required for createGit, auto-configured for TypeGit)
gitBinaryGit executable name (default: 'git')
envAdditional environment variables
pathPrefixDirectories to prepend to PATH
homeCustom HOME directory (also sets USERPROFILE on Windows)
credentialCredential helper configuration