@dependabit/github-client 0.1.1

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.
Files changed (70) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +266 -0
  4. package/dist/auth/basic.d.ts +46 -0
  5. package/dist/auth/basic.d.ts.map +1 -0
  6. package/dist/auth/basic.js +88 -0
  7. package/dist/auth/basic.js.map +1 -0
  8. package/dist/auth/oauth.d.ts +48 -0
  9. package/dist/auth/oauth.d.ts.map +1 -0
  10. package/dist/auth/oauth.js +139 -0
  11. package/dist/auth/oauth.js.map +1 -0
  12. package/dist/auth/token.d.ts +40 -0
  13. package/dist/auth/token.d.ts.map +1 -0
  14. package/dist/auth/token.js +67 -0
  15. package/dist/auth/token.js.map +1 -0
  16. package/dist/auth.d.ts +47 -0
  17. package/dist/auth.d.ts.map +1 -0
  18. package/dist/auth.js +78 -0
  19. package/dist/auth.js.map +1 -0
  20. package/dist/client.d.ts +53 -0
  21. package/dist/client.d.ts.map +1 -0
  22. package/dist/client.js +74 -0
  23. package/dist/client.js.map +1 -0
  24. package/dist/commits.d.ts +57 -0
  25. package/dist/commits.d.ts.map +1 -0
  26. package/dist/commits.js +113 -0
  27. package/dist/commits.js.map +1 -0
  28. package/dist/feedback.d.ts +69 -0
  29. package/dist/feedback.d.ts.map +1 -0
  30. package/dist/feedback.js +111 -0
  31. package/dist/feedback.js.map +1 -0
  32. package/dist/index.d.ts +15 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +11 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/issues.d.ts +55 -0
  37. package/dist/issues.d.ts.map +1 -0
  38. package/dist/issues.js +123 -0
  39. package/dist/issues.js.map +1 -0
  40. package/dist/rate-limit.d.ts +71 -0
  41. package/dist/rate-limit.d.ts.map +1 -0
  42. package/dist/rate-limit.js +145 -0
  43. package/dist/rate-limit.js.map +1 -0
  44. package/dist/releases.d.ts +50 -0
  45. package/dist/releases.d.ts.map +1 -0
  46. package/dist/releases.js +113 -0
  47. package/dist/releases.js.map +1 -0
  48. package/package.json +39 -0
  49. package/src/auth/basic.ts +102 -0
  50. package/src/auth/oauth.ts +183 -0
  51. package/src/auth/token.ts +81 -0
  52. package/src/auth.ts +100 -0
  53. package/src/client.test.ts +115 -0
  54. package/src/client.ts +109 -0
  55. package/src/commits.ts +184 -0
  56. package/src/feedback.ts +166 -0
  57. package/src/index.ts +15 -0
  58. package/src/issues.ts +185 -0
  59. package/src/rate-limit.ts +210 -0
  60. package/src/releases.ts +149 -0
  61. package/test/auth/basic.test.ts +122 -0
  62. package/test/auth/oauth.test.ts +196 -0
  63. package/test/auth/token.test.ts +97 -0
  64. package/test/commits.test.ts +169 -0
  65. package/test/feedback.test.ts +203 -0
  66. package/test/issues.test.ts +197 -0
  67. package/test/rate-limit.test.ts +154 -0
  68. package/test/releases.test.ts +187 -0
  69. package/tsconfig.json +10 -0
  70. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,197 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { IssueManager } from '../src/issues.js';
3
+
4
+ // Mock the octokit module
5
+ vi.mock('octokit', () => {
6
+ const mockIssuesCreate = vi.fn().mockResolvedValue({
7
+ data: {
8
+ number: 123,
9
+ html_url: 'https://github.com/test-owner/test-repo/issues/123',
10
+ labels: []
11
+ }
12
+ });
13
+
14
+ const mockIssuesGet = vi.fn().mockResolvedValue({
15
+ data: {
16
+ number: 123,
17
+ body: 'Existing issue body',
18
+ labels: [{ name: 'existing-label' }]
19
+ }
20
+ });
21
+
22
+ const mockIssuesUpdate = vi.fn().mockResolvedValue({
23
+ data: {
24
+ number: 123,
25
+ html_url: 'https://github.com/test-owner/test-repo/issues/123',
26
+ labels: []
27
+ }
28
+ });
29
+
30
+ const mockSearchIssues = vi.fn().mockResolvedValue({
31
+ data: {
32
+ items: []
33
+ }
34
+ });
35
+
36
+ class MockOctokit {
37
+ rest = {
38
+ issues: {
39
+ create: mockIssuesCreate,
40
+ get: mockIssuesGet,
41
+ update: mockIssuesUpdate
42
+ },
43
+ search: {
44
+ issuesAndPullRequests: mockSearchIssues
45
+ }
46
+ };
47
+ }
48
+
49
+ return {
50
+ Octokit: MockOctokit
51
+ };
52
+ });
53
+
54
+ describe('IssueManager', () => {
55
+ let issueManager: IssueManager;
56
+
57
+ beforeEach(() => {
58
+ issueManager = new IssueManager();
59
+ vi.clearAllMocks();
60
+ });
61
+
62
+ describe('createIssue', () => {
63
+ it('should create issue with severity label', async () => {
64
+ const issueData = {
65
+ owner: 'test-owner',
66
+ repo: 'test-repo',
67
+ title: 'Dependency Update: test-package',
68
+ body: 'New version detected',
69
+ severity: 'major' as const,
70
+ dependency: {
71
+ id: 'test-id',
72
+ url: 'https://github.com/owner/repo'
73
+ }
74
+ };
75
+
76
+ const result = await issueManager.createIssue(issueData);
77
+
78
+ expect(result).toBeDefined();
79
+ expect(result.number).toBeDefined();
80
+ expect(result.url).toBeDefined();
81
+ expect(result.labels).toContain('severity:major');
82
+ });
83
+
84
+ it('should include dependabit label', async () => {
85
+ const issueData = {
86
+ owner: 'test-owner',
87
+ repo: 'test-repo',
88
+ title: 'Dependency Update',
89
+ body: 'Changes detected',
90
+ severity: 'minor' as const,
91
+ dependency: {
92
+ id: 'test-id',
93
+ url: 'https://example.com'
94
+ }
95
+ };
96
+
97
+ const result = await issueManager.createIssue(issueData);
98
+
99
+ expect(result.labels).toContain('dependabit');
100
+ });
101
+
102
+ it('should handle breaking changes with appropriate label', async () => {
103
+ const issueData = {
104
+ owner: 'test-owner',
105
+ repo: 'test-repo',
106
+ title: 'Breaking Change Detected',
107
+ body: 'Major version update with breaking changes',
108
+ severity: 'breaking' as const,
109
+ dependency: {
110
+ id: 'test-id',
111
+ url: 'https://github.com/owner/repo'
112
+ }
113
+ };
114
+
115
+ const result = await issueManager.createIssue(issueData);
116
+
117
+ expect(result.labels).toContain('severity:breaking');
118
+ });
119
+
120
+ it('should assign issue to AI agent when specified', async () => {
121
+ const issueData = {
122
+ owner: 'test-owner',
123
+ repo: 'test-repo',
124
+ title: 'Dependency Update',
125
+ body: 'Changes detected',
126
+ severity: 'major' as const,
127
+ dependency: {
128
+ id: 'test-id',
129
+ url: 'https://example.com'
130
+ },
131
+ assignee: 'copilot'
132
+ };
133
+
134
+ const result = await issueManager.createIssue(issueData);
135
+
136
+ expect(result).toBeDefined();
137
+ expect(result.assignees).toContain('copilot');
138
+ });
139
+ });
140
+
141
+ describe('findExistingIssue', () => {
142
+ it('should find existing issue for dependency', async () => {
143
+ const params = {
144
+ owner: 'test-owner',
145
+ repo: 'test-repo',
146
+ dependencyId: 'test-id'
147
+ };
148
+
149
+ const result = await issueManager.findExistingIssue(params);
150
+
151
+ expect(result).toBeDefined();
152
+ });
153
+
154
+ it('should return null when no existing issue found', async () => {
155
+ const params = {
156
+ owner: 'test-owner',
157
+ repo: 'test-repo',
158
+ dependencyId: 'non-existent-id'
159
+ };
160
+
161
+ const result = await issueManager.findExistingIssue(params);
162
+
163
+ expect(result).toBeNull();
164
+ });
165
+ });
166
+
167
+ describe('updateIssue', () => {
168
+ it('should update existing issue with new information', async () => {
169
+ const updateData = {
170
+ owner: 'test-owner',
171
+ repo: 'test-repo',
172
+ issueNumber: 123,
173
+ body: 'Updated information',
174
+ severity: 'breaking' as const
175
+ };
176
+
177
+ const result = await issueManager.updateIssue(updateData);
178
+
179
+ expect(result).toBeDefined();
180
+ expect(result.number).toBe(123);
181
+ });
182
+
183
+ it('should append to existing issue body when specified', async () => {
184
+ const updateData = {
185
+ owner: 'test-owner',
186
+ repo: 'test-repo',
187
+ issueNumber: 123,
188
+ body: 'Additional update',
189
+ append: true
190
+ };
191
+
192
+ const result = await issueManager.updateIssue(updateData);
193
+
194
+ expect(result).toBeDefined();
195
+ });
196
+ });
197
+ });
@@ -0,0 +1,154 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { RateLimitHandler } from '../src/rate-limit.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: 4000,
11
+ reset: Math.floor(Date.now() / 1000) + 3600,
12
+ used: 1000
13
+ },
14
+ resources: {
15
+ core: {
16
+ limit: 5000,
17
+ remaining: 4000,
18
+ reset: Math.floor(Date.now() / 1000) + 3600,
19
+ used: 1000
20
+ },
21
+ search: {
22
+ limit: 30,
23
+ remaining: 25,
24
+ reset: Math.floor(Date.now() / 1000) + 3600,
25
+ used: 5
26
+ },
27
+ graphql: {
28
+ limit: 5000,
29
+ remaining: 4500,
30
+ reset: Math.floor(Date.now() / 1000) + 3600,
31
+ used: 500
32
+ }
33
+ }
34
+ }
35
+ });
36
+
37
+ class MockOctokit {
38
+ rest = {
39
+ rateLimit: {
40
+ get: mockRateLimitGet
41
+ }
42
+ };
43
+ }
44
+
45
+ return {
46
+ Octokit: MockOctokit
47
+ };
48
+ });
49
+
50
+ describe('RateLimitHandler', () => {
51
+ let handler: RateLimitHandler;
52
+
53
+ beforeEach(() => {
54
+ handler = new RateLimitHandler();
55
+ vi.clearAllMocks();
56
+ });
57
+
58
+ describe('checkRateLimit', () => {
59
+ it('should return rate limit status', async () => {
60
+ const result = await handler.checkRateLimit();
61
+
62
+ expect(result).toBeDefined();
63
+ expect(result.limit).toBeGreaterThan(0);
64
+ expect(result.remaining).toBeDefined();
65
+ expect(result.reset).toBeInstanceOf(Date);
66
+ });
67
+
68
+ it('should warn when approaching rate limit', async () => {
69
+ const result = await handler.checkRateLimit();
70
+
71
+ if (result.remaining < result.limit * 0.1) {
72
+ expect(result.warning).toBeDefined();
73
+ }
74
+ });
75
+ });
76
+
77
+ describe('waitIfNeeded', () => {
78
+ it('should not wait when rate limit is sufficient', async () => {
79
+ const startTime = Date.now();
80
+ await handler.waitIfNeeded();
81
+ const endTime = Date.now();
82
+
83
+ expect(endTime - startTime).toBeLessThan(1000);
84
+ });
85
+
86
+ it('should calculate wait time when rate limited', async () => {
87
+ const rateLimitInfo = {
88
+ limit: 5000,
89
+ remaining: 0,
90
+ reset: new Date(Date.now() + 60000) // 1 minute from now
91
+ };
92
+
93
+ const waitTime = handler.calculateWaitTime(rateLimitInfo);
94
+
95
+ expect(waitTime).toBeGreaterThan(0);
96
+ expect(waitTime).toBeLessThanOrEqual(60000);
97
+ });
98
+
99
+ it('should return 0 wait time when not rate limited', () => {
100
+ const rateLimitInfo = {
101
+ limit: 5000,
102
+ remaining: 4000,
103
+ reset: new Date(Date.now() + 3600000)
104
+ };
105
+
106
+ const waitTime = handler.calculateWaitTime(rateLimitInfo);
107
+
108
+ expect(waitTime).toBe(0);
109
+ });
110
+ });
111
+
112
+ describe('reserveBudget', () => {
113
+ it('should reserve API call budget', async () => {
114
+ const result = await handler.reserveBudget(10);
115
+
116
+ expect(result).toBeDefined();
117
+ expect(result.reserved).toBe(true);
118
+ });
119
+
120
+ it('should reject when insufficient budget', async () => {
121
+ const result = await handler.reserveBudget(10000);
122
+
123
+ expect(result.reserved).toBe(false);
124
+ expect(result.reason).toBeDefined();
125
+ });
126
+
127
+ it('should provide wait time when budget unavailable', async () => {
128
+ const result = await handler.reserveBudget(10000);
129
+
130
+ if (!result.reserved) {
131
+ expect(result.waitTime).toBeDefined();
132
+ }
133
+ });
134
+ });
135
+
136
+ describe('getRateLimitStatus', () => {
137
+ it('should return current rate limit status', async () => {
138
+ const status = await handler.getRateLimitStatus();
139
+
140
+ expect(status).toBeDefined();
141
+ expect(status.core).toBeDefined();
142
+ expect(status.search).toBeDefined();
143
+ expect(status.graphql).toBeDefined();
144
+ });
145
+
146
+ it('should include percentage remaining', async () => {
147
+ const status = await handler.getRateLimitStatus();
148
+
149
+ expect(status.core.percentageRemaining).toBeDefined();
150
+ expect(status.core.percentageRemaining).toBeGreaterThanOrEqual(0);
151
+ expect(status.core.percentageRemaining).toBeLessThanOrEqual(100);
152
+ });
153
+ });
154
+ });
@@ -0,0 +1,187 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { ReleaseManager } from '../src/releases.js';
3
+
4
+ // Mock the octokit module
5
+ vi.mock('octokit', () => {
6
+ const mockGetLatestRelease = vi.fn().mockImplementation((params: any) => {
7
+ // Return 404 for specific repos
8
+ if (params.repo === 'no-releases') {
9
+ const error: any = new Error('Not Found');
10
+ error.status = 404;
11
+ return Promise.reject(error);
12
+ }
13
+ return Promise.resolve({
14
+ data: {
15
+ tag_name: 'v1.0.0',
16
+ name: 'Release v1.0.0',
17
+ published_at: '2024-01-01T00:00:00Z',
18
+ created_at: '2024-01-01T00:00:00Z',
19
+ body: 'Release notes',
20
+ html_url: 'https://github.com/test-owner/test-repo/releases/v1.0.0',
21
+ prerelease: false,
22
+ draft: false
23
+ }
24
+ });
25
+ });
26
+
27
+ const mockListReleases = vi.fn().mockImplementation((params: any) => {
28
+ // Return empty for specific repos
29
+ if (params.repo === 'no-releases') {
30
+ const error: any = new Error('Not Found');
31
+ error.status = 404;
32
+ return Promise.reject(error);
33
+ }
34
+ return Promise.resolve({
35
+ data: [
36
+ {
37
+ tag_name: 'v1.0.0',
38
+ name: 'Release v1.0.0',
39
+ published_at: '2024-01-01T00:00:00Z',
40
+ created_at: '2024-01-01T00:00:00Z',
41
+ body: 'Release notes',
42
+ html_url: 'https://github.com/test-owner/test-repo/releases/v1.0.0',
43
+ prerelease: false,
44
+ draft: false
45
+ }
46
+ ]
47
+ });
48
+ });
49
+
50
+ const mockGetReleaseByTag = vi.fn().mockResolvedValue({
51
+ data: {
52
+ tag_name: 'v1.0.0',
53
+ name: 'Release v1.0.0',
54
+ published_at: '2024-01-01T00:00:00Z',
55
+ created_at: '2024-01-01T00:00:00Z',
56
+ body: 'Release notes',
57
+ html_url: 'https://github.com/test-owner/test-repo/releases/v1.0.0',
58
+ prerelease: false,
59
+ draft: false
60
+ }
61
+ });
62
+
63
+ class MockOctokit {
64
+ rest = {
65
+ repos: {
66
+ getLatestRelease: mockGetLatestRelease,
67
+ listReleases: mockListReleases,
68
+ getReleaseByTag: mockGetReleaseByTag
69
+ }
70
+ };
71
+ }
72
+
73
+ return {
74
+ Octokit: MockOctokit
75
+ };
76
+ });
77
+
78
+ describe('ReleaseManager', () => {
79
+ let releaseManager: ReleaseManager;
80
+
81
+ beforeEach(() => {
82
+ releaseManager = new ReleaseManager();
83
+ vi.clearAllMocks();
84
+ });
85
+
86
+ describe('getLatestRelease', () => {
87
+ it('should fetch latest release', async () => {
88
+ const params = {
89
+ owner: 'test-owner',
90
+ repo: 'test-repo'
91
+ };
92
+
93
+ const result = await releaseManager.getLatestRelease(params);
94
+
95
+ expect(result).toBeDefined();
96
+ expect(result.tagName).toBeDefined();
97
+ expect(result.publishedAt).toBeInstanceOf(Date);
98
+ });
99
+
100
+ it('should return null when no releases exist', async () => {
101
+ const params = {
102
+ owner: 'test-owner',
103
+ repo: 'no-releases'
104
+ };
105
+
106
+ const result = await releaseManager.getLatestRelease(params);
107
+
108
+ expect(result).toBeNull();
109
+ });
110
+
111
+ it('should include release notes', async () => {
112
+ const params = {
113
+ owner: 'test-owner',
114
+ repo: 'test-repo'
115
+ };
116
+
117
+ const result = await releaseManager.getLatestRelease(params);
118
+
119
+ expect(result?.body).toBeDefined();
120
+ expect(result?.htmlUrl).toBeDefined();
121
+ });
122
+ });
123
+
124
+ describe('getAllReleases', () => {
125
+ it('should fetch all releases', async () => {
126
+ const params = {
127
+ owner: 'test-owner',
128
+ repo: 'test-repo'
129
+ };
130
+
131
+ const result = await releaseManager.getAllReleases(params);
132
+
133
+ expect(Array.isArray(result)).toBe(true);
134
+ expect(result.length).toBeGreaterThan(0);
135
+ });
136
+
137
+ it('should support pagination', async () => {
138
+ const params = {
139
+ owner: 'test-owner',
140
+ repo: 'test-repo',
141
+ page: 2,
142
+ perPage: 10
143
+ };
144
+
145
+ const result = await releaseManager.getAllReleases(params);
146
+
147
+ expect(Array.isArray(result)).toBe(true);
148
+ });
149
+
150
+ it('should return empty array when no releases', async () => {
151
+ const params = {
152
+ owner: 'test-owner',
153
+ repo: 'no-releases'
154
+ };
155
+
156
+ const result = await releaseManager.getAllReleases(params);
157
+
158
+ expect(result).toEqual([]);
159
+ });
160
+ });
161
+
162
+ describe('compareReleases', () => {
163
+ it('should detect new releases', () => {
164
+ const oldReleases = [{ tagName: 'v1.0.0', publishedAt: new Date('2024-01-01') }];
165
+
166
+ const newReleases = [
167
+ { tagName: 'v1.1.0', publishedAt: new Date('2024-01-02') },
168
+ { tagName: 'v1.0.0', publishedAt: new Date('2024-01-01') }
169
+ ];
170
+
171
+ const result = releaseManager.compareReleases(oldReleases, newReleases);
172
+
173
+ expect(result.newReleases).toHaveLength(1);
174
+ expect(result.newReleases[0].tagName).toBe('v1.1.0');
175
+ });
176
+
177
+ it('should return empty when no new releases', () => {
178
+ const oldReleases = [{ tagName: 'v1.0.0', publishedAt: new Date('2024-01-01') }];
179
+
180
+ const newReleases = [{ tagName: 'v1.0.0', publishedAt: new Date('2024-01-01') }];
181
+
182
+ const result = releaseManager.compareReleases(oldReleases, newReleases);
183
+
184
+ expect(result.newReleases).toHaveLength(0);
185
+ });
186
+ });
187
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"],
9
+ "references": []
10
+ }