spec-up-t 1.1.52 → 1.1.54

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.
@@ -3,6 +3,8 @@ const path = require('path');
3
3
  const crypto = require('crypto'); // For generating cache keys
4
4
  const isLineWithDefinition = require('../utils/isLineWithDefinition').isLineWithDefinition;
5
5
  const { addPath, getPath, getAllPaths } = require('../config/paths');
6
+ const { getSearchClient, getContentClient } = require('./octokitClient');
7
+
6
8
 
7
9
  // Directory to store cached files
8
10
  const CACHE_DIR = getPath('githubcache');
@@ -13,36 +15,21 @@ function generateCacheKey(...args) {
13
15
  return hash;
14
16
  }
15
17
 
16
- async function fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
17
- const { Octokit } = await import("octokit");
18
- const { throttling } = await import("@octokit/plugin-throttling");
19
-
20
- // Create a throttled Octokit instance
21
- const ThrottledOctokit = Octokit.plugin(throttling);
22
- const octokit = new ThrottledOctokit({
23
- auth: GITHUB_API_TOKEN,
24
- throttle: {
25
- onRateLimit: (retryAfter, options) => {
26
- console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
27
- if (options.request.retryCount <= 1) {
28
- console.log(`Retrying after ${retryAfter} seconds...`);
29
- return true;
30
- }
31
- },
32
- onAbuseLimit: (retryAfter, options) => {
33
- console.warn(`Abuse detected for request ${options.method} ${options.url}`);
34
- },
35
- onSecondaryRateLimit: (retryAfter, options) => {
36
- console.warn(`Secondary rate limit hit for request ${options.method} ${options.url}`);
37
- if (options.request.retryCount <= 1) {
38
- console.log(`Retrying after ${retryAfter} seconds...`);
39
- return true;
40
- }
41
- },
42
- },
43
- });
44
18
 
19
+
20
+
21
+
22
+
23
+
24
+
25
+ async function fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
26
+ console.log(`Searching for '${searchString}' in ${owner}/${repo}/${subdirectory}`);
45
27
  try {
28
+ // Create directory if it doesn't exist
29
+ if (!fs.existsSync(CACHE_DIR)) {
30
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
31
+ }
32
+
46
33
  // Generate a cache key for the search query
47
34
  const searchCacheKey = generateCacheKey('search', searchString, owner, repo, subdirectory);
48
35
  const searchCacheFilePath = path.join(CACHE_DIR, `${searchCacheKey}.json`);
@@ -54,21 +41,22 @@ async function fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, searchString, ow
54
41
  console.log(`Serving search results from cache: ${searchCacheFilePath}`);
55
42
  searchResponse = JSON.parse(fs.readFileSync(searchCacheFilePath, 'utf-8'));
56
43
  } else {
57
- // Perform the search using Octokit with exact match
44
+ // Get the search client
58
45
  console.log(`Performing search and caching results: ${searchCacheFilePath}`);
59
- searchResponse = await octokit.rest.search.code({
60
- q: `"${searchString}" repo:${owner}/${repo} path:${subdirectory}`, // Exact match in subdirectory
61
- headers: {
62
- Accept: "application/vnd.github.v3.text-match+json", // Include text-match media type
63
- },
64
- });
46
+ const searchClient = await getSearchClient(GITHUB_API_TOKEN);
47
+
48
+ // Perform the search
49
+ searchResponse = await searchClient.search(searchString, owner, repo, subdirectory);
65
50
 
66
51
  // Cache the search response
67
52
  fs.writeFileSync(searchCacheFilePath, JSON.stringify(searchResponse), 'utf-8');
68
53
  }
69
-
70
- // Log the search results
71
- console.log(`Total matches for ${searchString} :`, searchResponse.data.total_count);
54
+ // After search
55
+ console.log(`Search found ${searchResponse.data.total_count} results`);
56
+ if (searchResponse.data.total_count === 0) {
57
+ console.log("No matches found - check if term exists in repository");
58
+ return null;
59
+ }
72
60
 
73
61
  /*
74
62
 
@@ -195,11 +183,12 @@ async function fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, searchString, ow
195
183
  // Fetch file content from GitHub
196
184
  console.log(`Downloading and caching file: ${fileCacheFilePath}`);
197
185
  try {
198
- const fileContentResponse = await octokit.rest.repos.getContent({
199
- owner: item.repository.owner.login, // Repository owner
200
- repo: item.repository.name, // Repository name
201
- path: item.path, // File path
202
- });
186
+ const contentClient = await getContentClient(GITHUB_API_TOKEN);
187
+ const fileContentResponse = await contentClient.getContent(
188
+ item.repository.owner.login,
189
+ item.repository.name,
190
+ item.path
191
+ );
203
192
 
204
193
  // Decode the file content (it's base64-encoded)
205
194
  if (fileContentResponse.data.content) {
@@ -227,7 +216,13 @@ async function fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, searchString, ow
227
216
  }
228
217
  }
229
218
  } catch (error) {
230
- console.error("Error searching GitHub or fetching file content:", error);
219
+ console.error("Error details:", {
220
+ message: error.message,
221
+ status: error.status,
222
+ type: error.constructor.name,
223
+ details: error.response?.data?.message || "No additional details"
224
+ });
225
+ return null;
231
226
  }
232
227
 
233
228
  // If no item is found, return null or undefined
@@ -0,0 +1,385 @@
1
+ /*
2
+ This test suite covers the main functionality of the fetchTermsFromGitHubRepository function including:
3
+
4
+ - Retrieving cached search results when available
5
+ - Fetching and caching new results when not in cache
6
+ - Handling API errors gracefully
7
+ - Processing empty search results correctly
8
+
9
+ The tests use Jest's mocking capabilities to avoid actual GitHub API calls and filesystem operations, making the tests fast and reliable.
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+
15
+ // Mock dependencies
16
+ jest.mock('fs');
17
+ jest.mock('path');
18
+ jest.mock('crypto');
19
+ jest.mock('../utils/isLineWithDefinition');
20
+ jest.mock('../config/paths');
21
+
22
+ // Replace your static mock with a configurable one
23
+ jest.mock('./octokitClient', () => {
24
+ // Create mock functions that can be reconfigured in each test
25
+ const mockSearchClient = {
26
+ search: jest.fn()
27
+ };
28
+
29
+ const mockContentClient = {
30
+ getContent: jest.fn()
31
+ };
32
+
33
+ return {
34
+ getSearchClient: jest.fn().mockResolvedValue(mockSearchClient),
35
+ getContentClient: jest.fn().mockResolvedValue(mockContentClient)
36
+ };
37
+ });
38
+
39
+ // Import the mocked module to access the mock functions
40
+ const octokitClient = require('./octokitClient');
41
+
42
+ // Import the function to test
43
+ const { fetchTermsFromGitHubRepository } = require('./fetchTermsFromGitHubRepository');
44
+ const { getPath } = require('../config/paths');
45
+
46
+ describe('fetchTermsFromGitHubRepository', () => {
47
+ // Setup common variables
48
+ const mockToken = 'mock-github-token';
49
+ const mockSearchString = 'test-search';
50
+ const mockOwner = 'test-owner';
51
+ const mockRepo = 'test-repo';
52
+ const mockSubdirectory = 'test-dir';
53
+ const mockCachePath = '/mock/cache/path';
54
+
55
+ const mockSearchResponse = {
56
+ data: {
57
+ total_count: 1,
58
+ items: [{
59
+ path: 'test-file.md',
60
+ repository: {
61
+ owner: { login: 'test-owner' },
62
+ name: 'test-repo'
63
+ },
64
+ text_matches: [{
65
+ fragment: '[[def: test-term]]\nContent'
66
+ }]
67
+ }]
68
+ }
69
+ };
70
+
71
+ const mockFileContentResponse = {
72
+ data: {
73
+ content: Buffer.from('[[def: test-term]]\nTest file content').toString('base64')
74
+ }
75
+ };
76
+
77
+ beforeEach(() => {
78
+ // Reset all mocks
79
+ jest.clearAllMocks();
80
+
81
+ // Setup common mock returns
82
+ getPath.mockReturnValue(mockCachePath);
83
+ path.join.mockImplementation((...args) => args.join('/'));
84
+
85
+ // Mock crypto hash
86
+ const mockHash = {
87
+ update: jest.fn().mockReturnThis(),
88
+ digest: jest.fn().mockReturnValue('mock-hash')
89
+ };
90
+ require('crypto').createHash.mockReturnValue(mockHash);
91
+
92
+ // Default file system mocks
93
+ fs.existsSync.mockReturnValue(false);
94
+ fs.mkdirSync.mockReturnValue(undefined);
95
+ fs.writeFileSync.mockReturnValue(undefined);
96
+ fs.readFileSync.mockReturnValue('');
97
+
98
+ // Default isLineWithDefinition behavior
99
+ const { isLineWithDefinition } = require('../utils/isLineWithDefinition');
100
+ isLineWithDefinition.mockImplementation(line => {
101
+ return line.includes('[[def:') && line.includes(']]');
102
+ });
103
+ });
104
+
105
+ test('should return cached search results if available', async () => {
106
+ // Setup cache hit
107
+ fs.existsSync.mockReturnValue(true);
108
+ fs.readFileSync.mockReturnValue(JSON.stringify(mockSearchResponse));
109
+
110
+ // Also set up file content cache
111
+ fs.existsSync.mockImplementation((path) => {
112
+ return true; // Assume all files exist in cache
113
+ });
114
+
115
+ // Mock content response
116
+ fs.readFileSync.mockImplementation((path) => {
117
+ if (path.includes('.json')) {
118
+ return JSON.stringify(mockSearchResponse);
119
+ } else {
120
+ return '[[def: test-term]]\nContent';
121
+ }
122
+ });
123
+
124
+ const result = await fetchTermsFromGitHubRepository(
125
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
126
+ );
127
+
128
+ expect(fs.existsSync).toHaveBeenCalled();
129
+ expect(fs.readFileSync).toHaveBeenCalled();
130
+ expect(result).not.toBeNull();
131
+ expect(result.path).toBe('test-file.md');
132
+ });
133
+
134
+ test('should fetch and cache search results if not cached', async () => {
135
+ // Set up mocks for API calls
136
+ const mockSearchClient = await octokitClient.getSearchClient();
137
+ mockSearchClient.search.mockResolvedValueOnce(mockSearchResponse);
138
+
139
+ const mockContentClient = await octokitClient.getContentClient();
140
+ mockContentClient.getContent.mockResolvedValueOnce({
141
+ data: {
142
+ content: Buffer.from('[[def: test-term]]\nContent').toString('base64')
143
+ }
144
+ });
145
+
146
+ // Mock file system for cache miss
147
+ fs.existsSync.mockReturnValue(false);
148
+
149
+ const result = await fetchTermsFromGitHubRepository(
150
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
151
+ );
152
+
153
+ expect(fs.writeFileSync).toHaveBeenCalled();
154
+ expect(result).not.toBeNull();
155
+ expect(result.path).toBe('test-file.md');
156
+ expect(result.content).toBeDefined();
157
+ });
158
+
159
+ test('should handle API errors gracefully', async () => {
160
+ // Set up mock to throw an error
161
+ const mockSearchClient = await octokitClient.getSearchClient();
162
+ mockSearchClient.search.mockRejectedValueOnce(new Error('API error'));
163
+
164
+ const result = await fetchTermsFromGitHubRepository(
165
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
166
+ );
167
+
168
+ expect(result).toBeNull();
169
+ });
170
+
171
+ test('should handle empty search results', async () => {
172
+ // Set up mock for empty results
173
+ const mockSearchClient = await octokitClient.getSearchClient();
174
+ mockSearchClient.search.mockResolvedValueOnce({
175
+ data: {
176
+ total_count: 0,
177
+ items: []
178
+ }
179
+ });
180
+
181
+ const result = await fetchTermsFromGitHubRepository(
182
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
183
+ );
184
+
185
+ expect(result).toBeNull();
186
+ });
187
+
188
+ test('should handle invalid GitHub tokens', async () => {
189
+ // Set up mock for authentication error
190
+ const mockSearchClient = await octokitClient.getSearchClient();
191
+ mockSearchClient.search.mockRejectedValueOnce({
192
+ status: 401,
193
+ message: 'Bad credentials'
194
+ });
195
+
196
+ const result = await fetchTermsFromGitHubRepository(
197
+ 'invalid-token', mockSearchString, mockOwner, mockRepo, mockSubdirectory
198
+ );
199
+
200
+ expect(result).toBeNull();
201
+ });
202
+
203
+ test('should handle rate limiting errors', async () => {
204
+ // Set up mock for rate limit error
205
+ const mockSearchClient = await octokitClient.getSearchClient();
206
+ mockSearchClient.search.mockRejectedValueOnce({
207
+ status: 403,
208
+ message: 'API rate limit exceeded',
209
+ headers: {
210
+ 'x-ratelimit-reset': (Date.now() / 1000 + 60).toString()
211
+ }
212
+ });
213
+
214
+ const result = await fetchTermsFromGitHubRepository(
215
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
216
+ );
217
+
218
+ expect(result).toBeNull();
219
+ });
220
+
221
+ test('should handle large files that provide download URLs instead of content', async () => {
222
+ // Set up search response with large file
223
+ const mockSearchClient = await octokitClient.getSearchClient();
224
+ mockSearchClient.search.mockResolvedValueOnce({
225
+ data: {
226
+ total_count: 1,
227
+ items: [{
228
+ path: 'large-file.md',
229
+ repository: {
230
+ owner: { login: mockOwner },
231
+ name: mockRepo
232
+ },
233
+ text_matches: [{
234
+ fragment: '[[def: test-term]]\nContent'
235
+ }]
236
+ }]
237
+ }
238
+ });
239
+
240
+ // Set up content client for large file (no content)
241
+ const mockContentClient = await octokitClient.getContentClient();
242
+ mockContentClient.getContent.mockResolvedValueOnce({
243
+ data: {
244
+ content: null,
245
+ download_url: 'https://raw.githubusercontent.com/test-owner/test-repo/main/large-file.md'
246
+ }
247
+ });
248
+
249
+ const result = await fetchTermsFromGitHubRepository(
250
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
251
+ );
252
+
253
+ expect(result).not.toBeNull();
254
+ expect(result.path).toBe('large-file.md');
255
+ expect(result.content).toBe("");
256
+ });
257
+
258
+ test('should handle files with multiple definition lines', async () => {
259
+ // Set up search response with file containing multiple definitions
260
+ const mockSearchClient = await octokitClient.getSearchClient();
261
+ mockSearchClient.search.mockResolvedValueOnce({
262
+ data: {
263
+ total_count: 1,
264
+ items: [{
265
+ path: 'multi-def.md',
266
+ repository: {
267
+ owner: { login: mockOwner },
268
+ name: mockRepo
269
+ },
270
+ text_matches: [{
271
+ fragment: '[[def: term1]]\nContent'
272
+ }]
273
+ }]
274
+ }
275
+ });
276
+
277
+ // File content with multiple definitions
278
+ const fileContent = `
279
+ [[def: term1, alias1]]\n
280
+ This is definition 1\n
281
+ \n
282
+ [[def: term2, alias2]]\n
283
+ This is definition 2
284
+ `;
285
+
286
+ // Set up content client for multi-def file
287
+ const mockContentClient = await octokitClient.getContentClient();
288
+ mockContentClient.getContent.mockResolvedValueOnce({
289
+ data: {
290
+ content: Buffer.from(fileContent).toString('base64')
291
+ }
292
+ });
293
+
294
+ const result = await fetchTermsFromGitHubRepository(
295
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
296
+ );
297
+
298
+ expect(result).not.toBeNull();
299
+ expect(result.path).toBe('multi-def.md');
300
+ expect(result.content).toContain('term1');
301
+ expect(result.content).toContain('term2');
302
+ });
303
+
304
+ test('should handle file content fetch errors', async () => {
305
+ // Set up search that succeeds
306
+ const mockSearchClient = await octokitClient.getSearchClient();
307
+ mockSearchClient.search.mockResolvedValueOnce({
308
+ data: {
309
+ total_count: 1,
310
+ items: [{
311
+ path: 'error-file.md',
312
+ repository: {
313
+ owner: { login: mockOwner },
314
+ name: mockRepo
315
+ },
316
+ text_matches: [{
317
+ fragment: '[[def: test-term]]\nContent'
318
+ }]
319
+ }]
320
+ }
321
+ });
322
+
323
+ // Set up content fetch that fails
324
+ const mockContentClient = await octokitClient.getContentClient();
325
+ mockContentClient.getContent.mockRejectedValueOnce(new Error('File not found'));
326
+
327
+ const result = await fetchTermsFromGitHubRepository(
328
+ mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
329
+ );
330
+
331
+ // This might be null or might return empty content based on implementation
332
+ if (result) {
333
+ expect(result.content).toBe("");
334
+ } else {
335
+ expect(result).toBeNull();
336
+ }
337
+ });
338
+
339
+ test('should correctly filter by subdirectory', async () => {
340
+ // Set up search with multiple results in different directories
341
+ const mockSearchClient = await octokitClient.getSearchClient();
342
+ mockSearchClient.search.mockResolvedValueOnce({
343
+ data: {
344
+ total_count: 2,
345
+ items: [
346
+ {
347
+ path: 'test-dir/file1.md',
348
+ repository: {
349
+ owner: { login: mockOwner },
350
+ name: mockRepo
351
+ },
352
+ text_matches: [{
353
+ fragment: '[[def: term1]]\nContent'
354
+ }]
355
+ },
356
+ {
357
+ path: 'other-dir/file2.md',
358
+ repository: {
359
+ owner: { login: mockOwner },
360
+ name: mockRepo
361
+ },
362
+ text_matches: [{
363
+ fragment: '[[def: term2]]\nContent'
364
+ }]
365
+ }
366
+ ]
367
+ }
368
+ });
369
+
370
+ // Set up content client
371
+ const mockContentClient = await octokitClient.getContentClient();
372
+ mockContentClient.getContent.mockResolvedValueOnce({
373
+ data: {
374
+ content: Buffer.from('[[def: term1]]\nContent').toString('base64')
375
+ }
376
+ });
377
+
378
+ const result = await fetchTermsFromGitHubRepository(
379
+ mockToken, mockSearchString, mockOwner, mockRepo, 'test-dir'
380
+ );
381
+
382
+ expect(result).not.toBeNull();
383
+ expect(result.path).toBe('test-dir/file1.md');
384
+ });
385
+ });
@@ -2,20 +2,25 @@ const isLineWithDefinition = require('../utils/isLineWithDefinition').isLineWith
2
2
 
3
3
  function matchTerm(text, term) {
4
4
  if (!text || typeof text !== 'string') {
5
- // console.error('Invalid text:', text);
6
- console.log('Nothing to match');
5
+ console.log('Nothing to match for term:', term);
7
6
  return false;
8
7
  }
9
8
 
10
9
  const firstLine = text.split('\n')[0].trim();
11
10
 
12
- if (isLineWithDefinition(firstLine) === false) {
11
+ if (isLineWithDefinition(firstLine) === false) {
13
12
  console.log('String does not start with `[[def:` or end with `]]`');
14
13
  return false;
15
14
  };
16
15
 
17
- // Remove `[[def:` from the beginning and `]]` from the end
18
- let relevantPart = firstLine.slice(7, -2);
16
+ // Find the closing bracket position instead of assuming it's at the end
17
+ const startPos = firstLine.indexOf('[[def:') + 6;
18
+ const endPos = firstLine.indexOf(']]');
19
+
20
+ if (startPos === -1 || endPos === -1) return false;
21
+
22
+ // Extract text between [[def: and ]]
23
+ let relevantPart = firstLine.substring(startPos, endPos);
19
24
 
20
25
  // Split the string on `,` and trim the array elements
21
26
  let termsArray = relevantPart.split(',').map(term => term.trim());
@@ -0,0 +1,30 @@
1
+ const { matchTerm } = require('./matchTerm');
2
+
3
+ describe('matchTerm', () => {
4
+ test('returns true when the term is found in a correctly formatted definition', () => {
5
+ const text = '[[def: term1, term2]]\nSome additional text';
6
+ expect(matchTerm(text, 'term1')).toBe(true);
7
+ expect(matchTerm(text, 'term2')).toBe(true);
8
+ });
9
+
10
+ test('returns false when the term is not found in the definition', () => {
11
+ const text = '[[def: term1, term2]]\nSome additional text';
12
+ expect(matchTerm(text, 'term3')).toBe(false);
13
+ });
14
+
15
+ test('returns false when the text is null or not a string', () => {
16
+ expect(matchTerm(null, 'term1')).toBe(false);
17
+ expect(matchTerm(123, 'term1')).toBe(false);
18
+ });
19
+
20
+ test('returns false when the first line is not a valid definition', () => {
21
+ const text = 'Invalid definition line\n[[def: term1, term2]]';
22
+ expect(matchTerm(text, 'term1')).toBe(false);
23
+ });
24
+
25
+ test('handles extra spaces correctly', () => {
26
+ const text = '[[def: term1 , term2 ]]';
27
+ expect(matchTerm(text, 'term1')).toBe(true);
28
+ expect(matchTerm(text, 'term2')).toBe(true);
29
+ });
30
+ });
@@ -0,0 +1,96 @@
1
+ // Simple wrapper for Octokit functionality
2
+ let octokitClient = null;
3
+ let initPromise = null;
4
+
5
+ /**
6
+ * Initialize the Octokit client (only happens once)
7
+ */
8
+ async function initializeClient(token) {
9
+ if (initPromise) return initPromise;
10
+
11
+ initPromise = (async () => {
12
+ try {
13
+ const { Octokit } = await import('octokit');
14
+ const { throttling } = await import('@octokit/plugin-throttling');
15
+
16
+ const ThrottledOctokit = Octokit.plugin(throttling);
17
+ octokitClient = new ThrottledOctokit({
18
+ auth: token,
19
+ throttle: {
20
+ onRateLimit: (retryAfter, options) => {
21
+ console.warn(`Request quota exhausted for ${options.method} ${options.url}`);
22
+ return options.request.retryCount <= 1; // retry once
23
+ },
24
+ onSecondaryRateLimit: (retryAfter, options) => {
25
+ console.warn(`Secondary rate limit hit for ${options.method} ${options.url}`);
26
+ return options.request.retryCount <= 1; // retry once
27
+ },
28
+ }
29
+ });
30
+
31
+ console.log("Octokit client initialized successfully");
32
+ return octokitClient;
33
+ } catch (error) {
34
+ console.error("Failed to initialize Octokit:", error);
35
+ initPromise = null; // Reset so we can try again later
36
+ throw error;
37
+ }
38
+ })();
39
+
40
+ return initPromise;
41
+ }
42
+
43
+ /**
44
+ * Get a GitHub search client
45
+ */
46
+ async function getSearchClient(token) {
47
+ const client = await initializeClient(token);
48
+ return {
49
+ search: async (query, owner, repo, path) => {
50
+ // First check if the repo is a fork
51
+ let isForked = false;
52
+ try {
53
+ const repoInfo = await client.rest.repos.get({
54
+ owner,
55
+ repo
56
+ });
57
+ isForked = repoInfo.data.fork;
58
+ } catch (error) {
59
+ console.warn(`Could not determine fork status: ${error.message}`);
60
+ }
61
+
62
+ // Use appropriate search parameter based on fork status
63
+ const forkParam = isForked ? ' fork:true' : '';
64
+ const searchQuery = `${query} repo:${owner}/${repo} path:${path}${forkParam}`;
65
+
66
+ console.log(`Executing GitHub search: ${searchQuery}`);
67
+ return client.rest.search.code({
68
+ q: searchQuery,
69
+ headers: {
70
+ Accept: "application/vnd.github.v3.text-match+json",
71
+ },
72
+ });
73
+ }
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Get a GitHub content client
79
+ */
80
+ async function getContentClient(token) {
81
+ const client = await initializeClient(token);
82
+ return {
83
+ getContent: async (owner, repo, path) => {
84
+ return client.rest.repos.getContent({
85
+ owner,
86
+ repo,
87
+ path
88
+ });
89
+ }
90
+ };
91
+ }
92
+
93
+ module.exports = {
94
+ getSearchClient,
95
+ getContentClient
96
+ };
@@ -31,10 +31,12 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
31
31
  xtref.content = item.content;
32
32
  xtref.avatarUrl = item.repository.owner.avatar_url;
33
33
  console.log(`✅ Match found for term: ${xtref.term} in ${xtref.externalSpec};`);
34
+ console.log("============================================\n\n");
34
35
  } else {
35
36
  xtref.commitHash = "not found";
36
37
  xtref.content = "This term was not found in the external repository.";
37
38
  console.log(`ℹ️ No match found for term: ${xtref.term} in ${xtref.externalSpec};`);
39
+ console.log("============================================\n\n");
38
40
  }
39
41
  }
40
42