@dependabit/github-client 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/auth/basic.ts DELETED
@@ -1,102 +0,0 @@
1
- /**
2
- * Basic authentication handler for GitHub API
3
- * Supports username/password or username/personal access token
4
- */
5
-
6
- export interface BasicAuth {
7
- type: 'basic';
8
- username: string;
9
- password: string;
10
- }
11
-
12
- /**
13
- * Handler for HTTP Basic authentication
14
- */
15
- export class BasicAuthHandler {
16
- private username: string;
17
- private password: string;
18
-
19
- constructor(username: string, password: string) {
20
- if (!username || username.trim() === '') {
21
- throw new Error('Username cannot be empty');
22
- }
23
- if (!password || password.trim() === '') {
24
- throw new Error('Password cannot be empty');
25
- }
26
- this.username = username;
27
- this.password = password;
28
- }
29
-
30
- /**
31
- * Authenticate and return auth object
32
- */
33
- async authenticate(): Promise<BasicAuth> {
34
- return {
35
- type: 'basic',
36
- username: this.username,
37
- password: this.password
38
- };
39
- }
40
-
41
- /**
42
- * Get base64-encoded Basic auth header value
43
- */
44
- getAuthHeader(): string {
45
- const credentials = `${this.username}:${this.password}`;
46
- const encoded = Buffer.from(credentials).toString('base64');
47
- return `Basic ${encoded}`;
48
- }
49
-
50
- /**
51
- * Validate credentials format
52
- */
53
- validate(): boolean {
54
- // Check for invalid characters (newlines, etc.)
55
- if (this.username.includes('\n') || this.username.includes('\r')) {
56
- return false;
57
- }
58
- if (this.password.includes('\n') || this.password.includes('\r')) {
59
- return false;
60
- }
61
- return true;
62
- }
63
-
64
- /**
65
- * Get authentication type
66
- */
67
- getType(): string {
68
- return 'basic';
69
- }
70
-
71
- /**
72
- * Update credentials (for rotation)
73
- */
74
- updateCredentials(username: string, password: string): void {
75
- if (!username || username.trim() === '') {
76
- throw new Error('Username cannot be empty');
77
- }
78
- if (!password || password.trim() === '') {
79
- throw new Error('Password cannot be empty');
80
- }
81
- this.username = username;
82
- this.password = password;
83
- }
84
-
85
- /**
86
- * String representation (masks password)
87
- */
88
- toString(): string {
89
- return `BasicAuth(username=${this.username}, password=***)`;
90
- }
91
-
92
- /**
93
- * JSON representation (excludes password)
94
- */
95
- toJSON(): Record<string, unknown> {
96
- return {
97
- type: 'basic',
98
- username: this.username
99
- // password intentionally excluded
100
- };
101
- }
102
- }
package/src/auth/oauth.ts DELETED
@@ -1,183 +0,0 @@
1
- /**
2
- * OAuth 2.0 authentication handler for GitHub
3
- * Supports authorization code flow and token refresh
4
- */
5
-
6
- export interface OAuthConfig {
7
- clientId: string;
8
- clientSecret: string;
9
- redirectUri: string;
10
- }
11
-
12
- export interface OAuthAuth {
13
- type: 'oauth';
14
- token: string;
15
- tokenType: string;
16
- scope?: string | undefined;
17
- expiresIn?: number | undefined;
18
- refreshToken?: string | undefined;
19
- }
20
-
21
- interface TokenResponse {
22
- access_token: string;
23
- token_type: string;
24
- scope?: string;
25
- expires_in?: number;
26
- refresh_token?: string;
27
- }
28
-
29
- /**
30
- * Handler for OAuth 2.0 authentication
31
- */
32
- export class OAuthHandler {
33
- private config: OAuthConfig;
34
- private readonly GITHUB_OAUTH_URL = 'https://github.com/login/oauth';
35
-
36
- constructor(config: OAuthConfig) {
37
- if (!config.clientId || config.clientId.trim() === '') {
38
- throw new Error('clientId is required');
39
- }
40
- if (!config.clientSecret || config.clientSecret.trim() === '') {
41
- throw new Error('clientSecret is required');
42
- }
43
- this.config = config;
44
- }
45
-
46
- /**
47
- * Exchange authorization code for access token
48
- */
49
- async authenticate(code: string): Promise<OAuthAuth> {
50
- if (!code || code.trim() === '') {
51
- throw new Error('Authorization code is required');
52
- }
53
-
54
- const tokenResponse = await this.exchangeCodeForToken(code);
55
-
56
- return {
57
- type: 'oauth',
58
- token: tokenResponse.access_token,
59
- tokenType: tokenResponse.token_type,
60
- scope: tokenResponse.scope,
61
- expiresIn: tokenResponse.expires_in,
62
- refreshToken: tokenResponse.refresh_token
63
- };
64
- }
65
-
66
- /**
67
- * Generate authorization URL for OAuth flow
68
- */
69
- getAuthorizationUrl(scopes: string[], state?: string): string {
70
- const params = new URLSearchParams({
71
- client_id: this.config.clientId,
72
- redirect_uri: this.config.redirectUri,
73
- scope: scopes.join(' ')
74
- });
75
-
76
- if (state) {
77
- params.append('state', state);
78
- }
79
-
80
- return `${this.GITHUB_OAUTH_URL}/authorize?${params.toString()}`;
81
- }
82
-
83
- /**
84
- * Refresh an expired access token
85
- */
86
- async refreshToken(refreshToken: string): Promise<OAuthAuth> {
87
- if (!refreshToken || refreshToken.trim() === '') {
88
- throw new Error('Refresh token is required');
89
- }
90
-
91
- const tokenResponse = await this.performTokenRefresh(refreshToken);
92
-
93
- return {
94
- type: 'oauth',
95
- token: tokenResponse.access_token,
96
- tokenType: tokenResponse.token_type,
97
- scope: tokenResponse.scope,
98
- expiresIn: tokenResponse.expires_in
99
- };
100
- }
101
-
102
- /**
103
- * Validate OAuth configuration
104
- */
105
- validate(): boolean {
106
- try {
107
- // Validate redirect URI format
108
- new URL(this.config.redirectUri);
109
- return true;
110
- } catch {
111
- return false;
112
- }
113
- }
114
-
115
- /**
116
- * Get authentication type
117
- */
118
- getType(): string {
119
- return 'oauth';
120
- }
121
-
122
- /**
123
- * Exchange authorization code for token (internal)
124
- */
125
- private async exchangeCodeForToken(code: string): Promise<TokenResponse> {
126
- const response = await fetch(`${this.GITHUB_OAUTH_URL}/access_token`, {
127
- method: 'POST',
128
- headers: {
129
- 'Content-Type': 'application/json',
130
- Accept: 'application/json'
131
- },
132
- body: JSON.stringify({
133
- client_id: this.config.clientId,
134
- client_secret: this.config.clientSecret,
135
- code: code,
136
- redirect_uri: this.config.redirectUri
137
- })
138
- });
139
-
140
- if (!response.ok) {
141
- throw new Error(`Failed to exchange code for token: ${response.statusText}`);
142
- }
143
-
144
- const data = await response.json();
145
-
146
- if (data.error) {
147
- throw new Error(data.error_description || data.error);
148
- }
149
-
150
- return data;
151
- }
152
-
153
- /**
154
- * Perform token refresh (internal)
155
- */
156
- private async performTokenRefresh(refreshToken: string): Promise<TokenResponse> {
157
- const response = await fetch(`${this.GITHUB_OAUTH_URL}/access_token`, {
158
- method: 'POST',
159
- headers: {
160
- 'Content-Type': 'application/json',
161
- Accept: 'application/json'
162
- },
163
- body: JSON.stringify({
164
- client_id: this.config.clientId,
165
- client_secret: this.config.clientSecret,
166
- grant_type: 'refresh_token',
167
- refresh_token: refreshToken
168
- })
169
- });
170
-
171
- if (!response.ok) {
172
- throw new Error(`Failed to refresh token: ${response.statusText}`);
173
- }
174
-
175
- const data = await response.json();
176
-
177
- if (data.error) {
178
- throw new Error(data.error_description || data.error);
179
- }
180
-
181
- return data;
182
- }
183
- }
package/src/auth/token.ts DELETED
@@ -1,81 +0,0 @@
1
- /**
2
- * Token authentication handler for GitHub API
3
- * Supports GitHub PAT tokens, fine-grained tokens, and API keys
4
- */
5
-
6
- export interface TokenAuth {
7
- type: 'token';
8
- token: string;
9
- }
10
-
11
- /**
12
- * Handler for token-based authentication (GitHub PAT, API keys)
13
- */
14
- export class TokenAuthHandler {
15
- private token: string;
16
- private readonly GITHUB_TOKEN_PREFIXES = [
17
- 'ghp_', // Personal Access Token
18
- 'gho_', // OAuth Access Token
19
- 'ghu_', // User-to-Server Token
20
- 'ghs_', // Server-to-Server Token
21
- 'ghr_', // Refresh Token
22
- 'github_pat_' // Fine-grained PAT
23
- ];
24
-
25
- constructor(token: string) {
26
- if (!token || token.trim() === '') {
27
- throw new Error('Token cannot be empty');
28
- }
29
- this.token = token;
30
- }
31
-
32
- /**
33
- * Authenticate and return auth object
34
- */
35
- async authenticate(): Promise<TokenAuth> {
36
- return {
37
- type: 'token',
38
- token: this.token
39
- };
40
- }
41
-
42
- /**
43
- * Validate token format
44
- */
45
- validate(): boolean {
46
- // Check if token starts with valid GitHub prefix or is an API key
47
- const hasValidPrefix = this.GITHUB_TOKEN_PREFIXES.some((prefix) =>
48
- this.token.startsWith(prefix)
49
- );
50
-
51
- // Allow any token format, but prefer GitHub token prefixes
52
- return hasValidPrefix || this.token.length > 0;
53
- }
54
-
55
- /**
56
- * Get authentication type
57
- */
58
- getType(): string {
59
- return 'token';
60
- }
61
-
62
- /**
63
- * Update token (for rotation)
64
- */
65
- updateToken(newToken: string): void {
66
- if (!newToken || newToken.trim() === '') {
67
- throw new Error('Token cannot be empty');
68
- }
69
- this.token = newToken;
70
- }
71
-
72
- /**
73
- * Get current token
74
- *
75
- * @warning This method exposes the raw token value. Use with caution and avoid
76
- * logging or displaying the token. Prefer using authenticate() for auth operations.
77
- */
78
- getToken(): string {
79
- return this.token;
80
- }
81
- }
package/src/auth.ts DELETED
@@ -1,100 +0,0 @@
1
- /**
2
- * Authentication support for GitHub API client
3
- * Provides token, OAuth, and basic authentication methods
4
- */
5
-
6
- import { TokenAuthHandler, type TokenAuth } from './auth/token.js';
7
- import { OAuthHandler, type OAuthAuth, type OAuthConfig } from './auth/oauth.js';
8
- import { BasicAuthHandler, type BasicAuth } from './auth/basic.js';
9
-
10
- export type AuthType = 'token' | 'oauth' | 'basic';
11
- export type AuthResult = TokenAuth | OAuthAuth | BasicAuth;
12
-
13
- export interface AuthConfig {
14
- type: AuthType;
15
- token?: string;
16
- oauth?: OAuthConfig;
17
- username?: string;
18
- password?: string;
19
- }
20
-
21
- /**
22
- * Authentication manager that supports multiple auth methods
23
- */
24
- export class AuthManager {
25
- private handler: TokenAuthHandler | OAuthHandler | BasicAuthHandler;
26
-
27
- constructor(config: AuthConfig) {
28
- switch (config.type) {
29
- case 'token':
30
- if (!config.token) {
31
- throw new Error('Token is required for token authentication');
32
- }
33
- this.handler = new TokenAuthHandler(config.token);
34
- break;
35
-
36
- case 'oauth':
37
- if (!config.oauth) {
38
- throw new Error('OAuth config is required for OAuth authentication');
39
- }
40
- this.handler = new OAuthHandler(config.oauth);
41
- break;
42
-
43
- case 'basic':
44
- if (!config.username || !config.password) {
45
- throw new Error('Username and password are required for basic authentication');
46
- }
47
- this.handler = new BasicAuthHandler(config.username, config.password);
48
- break;
49
-
50
- default:
51
- throw new Error(`Unsupported authentication type: ${config.type}`);
52
- }
53
- }
54
-
55
- /**
56
- * Perform authentication
57
- */
58
- async authenticate(code?: string): Promise<AuthResult> {
59
- if (this.handler instanceof OAuthHandler && code) {
60
- return this.handler.authenticate(code);
61
- }
62
- if (this.handler instanceof TokenAuthHandler || this.handler instanceof BasicAuthHandler) {
63
- return this.handler.authenticate();
64
- }
65
- throw new Error('Invalid authentication flow');
66
- }
67
-
68
- /**
69
- * Validate authentication configuration
70
- */
71
- validate(): boolean {
72
- return this.handler.validate();
73
- }
74
-
75
- /**
76
- * Get authentication type
77
- */
78
- getType(): string {
79
- return this.handler.getType();
80
- }
81
-
82
- /**
83
- * Get underlying handler
84
- */
85
- getHandler(): TokenAuthHandler | OAuthHandler | BasicAuthHandler {
86
- return this.handler;
87
- }
88
- }
89
-
90
- /**
91
- * Create authentication manager from config
92
- */
93
- export function createAuth(config: AuthConfig): AuthManager {
94
- return new AuthManager(config);
95
- }
96
-
97
- // Re-export handler classes and types
98
- export { TokenAuthHandler, type TokenAuth } from './auth/token.js';
99
- export { OAuthHandler, type OAuthAuth, type OAuthConfig } from './auth/oauth.js';
100
- export { BasicAuthHandler, type BasicAuth } from './auth/basic.js';
@@ -1,115 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { GitHubClient, createGitHubClient } from '../src/client.js';
3
-
4
- // Mock the octokit module
5
- vi.mock('octokit', () => {
6
- const mockRateLimitGet = vi.fn().mockResolvedValue({
7
- data: {
8
- rate: {
9
- limit: 5000,
10
- remaining: 4900,
11
- reset: Math.floor(Date.now() / 1000) + 3600,
12
- used: 100
13
- }
14
- }
15
- });
16
-
17
- class MockOctokit {
18
- rest = {
19
- rateLimit: {
20
- get: mockRateLimitGet
21
- }
22
- };
23
- }
24
-
25
- return {
26
- Octokit: MockOctokit
27
- };
28
- });
29
-
30
- describe('GitHubClient Tests', () => {
31
- beforeEach(() => {
32
- vi.clearAllMocks();
33
- });
34
-
35
- describe('GitHubClient', () => {
36
- it('should create client with default configuration', () => {
37
- const client = new GitHubClient();
38
- expect(client).toBeDefined();
39
- expect(client.getOctokit()).toBeDefined();
40
- });
41
-
42
- it('should create client with authentication', () => {
43
- const client = new GitHubClient({ auth: 'test-token' });
44
- expect(client).toBeDefined();
45
- });
46
-
47
- it('should get rate limit information', async () => {
48
- const client = new GitHubClient();
49
- const rateLimit = await client.getRateLimit();
50
-
51
- expect(rateLimit).toHaveProperty('limit');
52
- expect(rateLimit).toHaveProperty('remaining');
53
- expect(rateLimit).toHaveProperty('reset');
54
- expect(rateLimit).toHaveProperty('used');
55
- expect(rateLimit.limit).toBe(5000);
56
- expect(rateLimit.remaining).toBe(4900);
57
- });
58
-
59
- it('should cache last rate limit check', async () => {
60
- const client = new GitHubClient();
61
- await client.getRateLimit();
62
-
63
- const cached = client.getLastRateLimitCheck();
64
- expect(cached).toBeDefined();
65
- expect(cached?.limit).toBe(5000);
66
- });
67
-
68
- it('should check rate limit before requests', async () => {
69
- const client = new GitHubClient();
70
- await expect(client.checkRateLimit()).resolves.not.toThrow();
71
- });
72
-
73
- it('should throw error when rate limit is exceeded', async () => {
74
- const client = new GitHubClient({ rateLimitMinRemaining: 10 });
75
-
76
- // This test is tricky with mocks, so we'll test the behavior when remaining is low
77
- // In a real scenario, this would wait or throw
78
- const checkResult = client.checkRateLimit();
79
-
80
- // Since our mock has 4900 remaining, this should not throw
81
- await expect(checkResult).resolves.not.toThrow();
82
- });
83
-
84
- it('should execute function with rate limit checking', async () => {
85
- const client = new GitHubClient();
86
- const mockFn = vi.fn(async () => 'result');
87
-
88
- const result = await client.withRateLimit(mockFn);
89
-
90
- expect(result).toBe('result');
91
- expect(mockFn).toHaveBeenCalled();
92
- });
93
-
94
- it('should respect custom rate limit thresholds', () => {
95
- const client = new GitHubClient({
96
- rateLimitWarningThreshold: 200,
97
- rateLimitMinRemaining: 50
98
- });
99
-
100
- expect(client).toBeDefined();
101
- });
102
- });
103
-
104
- describe('createGitHubClient', () => {
105
- it('should create a client instance', () => {
106
- const client = createGitHubClient();
107
- expect(client).toBeInstanceOf(GitHubClient);
108
- });
109
-
110
- it('should accept configuration', () => {
111
- const client = createGitHubClient({ auth: 'test-token' });
112
- expect(client).toBeInstanceOf(GitHubClient);
113
- });
114
- });
115
- });
package/src/client.ts DELETED
@@ -1,109 +0,0 @@
1
- import { Octokit } from 'octokit';
2
-
3
- /**
4
- * Rate limit information
5
- */
6
- export interface RateLimitInfo {
7
- limit: number;
8
- remaining: number;
9
- reset: number;
10
- used: number;
11
- }
12
-
13
- /**
14
- * GitHub client configuration
15
- */
16
- export interface GitHubClientConfig {
17
- auth?: string;
18
- rateLimitWarningThreshold?: number; // Warn when remaining falls below this
19
- rateLimitMinRemaining?: number; // Wait when remaining falls below this
20
- }
21
-
22
- /**
23
- * GitHub API client wrapper with rate limit handling
24
- */
25
- export class GitHubClient {
26
- private octokit: Octokit;
27
- private rateLimitWarningThreshold: number;
28
- private rateLimitMinRemaining: number;
29
- private lastRateLimitCheck?: RateLimitInfo;
30
-
31
- constructor(config: GitHubClientConfig = {}) {
32
- this.octokit = new Octokit({
33
- auth: config.auth
34
- });
35
- this.rateLimitWarningThreshold = config.rateLimitWarningThreshold ?? 100;
36
- this.rateLimitMinRemaining = config.rateLimitMinRemaining ?? 10;
37
- }
38
-
39
- /**
40
- * Get current rate limit status
41
- */
42
- async getRateLimit(): Promise<RateLimitInfo> {
43
- const response = await this.octokit.rest.rateLimit.get();
44
- const core = response.data.rate;
45
-
46
- const info: RateLimitInfo = {
47
- limit: core.limit,
48
- remaining: core.remaining,
49
- reset: core.reset,
50
- used: core.used
51
- };
52
-
53
- this.lastRateLimitCheck = info;
54
- return info;
55
- }
56
-
57
- /**
58
- * Check rate limit and throw if exceeded; log a warning when remaining is low.
59
- */
60
- async checkRateLimit(): Promise<void> {
61
- const rateLimit = await this.getRateLimit();
62
-
63
- if (rateLimit.remaining <= this.rateLimitMinRemaining) {
64
- const resetTime = new Date(rateLimit.reset * 1000);
65
- const waitMs = resetTime.getTime() - Date.now();
66
-
67
- if (waitMs > 0) {
68
- throw new Error(
69
- `Rate limit exceeded. ${rateLimit.remaining} requests remaining. Reset at ${resetTime.toISOString()}`
70
- );
71
- }
72
- }
73
-
74
- if (rateLimit.remaining <= this.rateLimitWarningThreshold) {
75
- console.warn(
76
- `Rate limit warning: ${rateLimit.remaining}/${rateLimit.limit} requests remaining`
77
- );
78
- }
79
- }
80
-
81
- /**
82
- * Execute a request with rate limit checking
83
- */
84
- async withRateLimit<T>(fn: () => Promise<T>): Promise<T> {
85
- await this.checkRateLimit();
86
- return fn();
87
- }
88
-
89
- /**
90
- * Get the underlying Octokit instance
91
- */
92
- getOctokit(): Octokit {
93
- return this.octokit;
94
- }
95
-
96
- /**
97
- * Get last known rate limit info (cached)
98
- */
99
- getLastRateLimitCheck(): RateLimitInfo | undefined {
100
- return this.lastRateLimitCheck;
101
- }
102
- }
103
-
104
- /**
105
- * Create a GitHub client instance
106
- */
107
- export function createGitHubClient(config?: GitHubClientConfig): GitHubClient {
108
- return new GitHubClient(config);
109
- }