spec-up-t 1.0.88 → 1.0.89
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/index.js +361 -341
- package/package.json +4 -1
- package/src/config/paths.js +46 -0
- package/src/create-versions-index.js +1 -1
- package/src/fix-markdown-files.js +1 -2
- package/src/get-xtrefs-data/checkRateLimit.js +17 -0
- package/src/get-xtrefs-data/matchTerm.1.js +23 -0
- package/src/get-xtrefs-data/matchTerm.js +26 -0
- package/src/get-xtrefs-data/searchGitHubCode.1.js +69 -0
- package/src/get-xtrefs-data/searchGitHubCode.2.js +77 -0
- package/src/get-xtrefs-data/searchGitHubCode.3.js +85 -0
- package/src/get-xtrefs-data/searchGitHubCode.4.js +92 -0
- package/src/get-xtrefs-data/searchGitHubCode.5.js +97 -0
- package/src/get-xtrefs-data/searchGitHubCode.js +97 -0
- package/src/get-xtrefs-data/setupFetchHeaders.js +14 -0
- package/src/get-xtrefs-data.js +95 -145
- package/src/init.js +36 -0
- package/src/json-key-validator.js +1 -1
- package/src/prepare-tref.js +16 -3
- package/src/utils/doesUrlExist.js +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-up-t",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.89",
|
|
4
4
|
"description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
|
|
5
5
|
"main": "./index",
|
|
6
6
|
"repository": {
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://github.com/trustoverip/spec-up-t#readme",
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"@octokit/plugin-throttling": "^9.4.0",
|
|
26
27
|
"@traptitech/markdown-it-katex": "^3.3.0",
|
|
27
28
|
"axios": "^1.7.7",
|
|
28
29
|
"diff": "^7.0.0",
|
|
30
|
+
"dotenv": "^16.4.7",
|
|
29
31
|
"find-pkg-dir": "^2.0.0",
|
|
30
32
|
"fs-extra": "^11.2.0",
|
|
31
33
|
"gulp": "4.0.2",
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
"markdown-it-textual-uml": "^0.1.3",
|
|
53
55
|
"markdown-it-toc-and-anchor": "^4.2.0",
|
|
54
56
|
"merge-stream": "^2.0.0",
|
|
57
|
+
"octokit": "^4.1.0",
|
|
55
58
|
"pdf-lib": "^1.17.1",
|
|
56
59
|
"pkg-dir": "^8.0.0",
|
|
57
60
|
"prismjs": "^1.24.0",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const paths = {};
|
|
3
|
+
|
|
4
|
+
// A path can be a directory or a file
|
|
5
|
+
//
|
|
6
|
+
// How to require:
|
|
7
|
+
// const { addPath, getPath, getAllPaths } = require('./config/paths');
|
|
8
|
+
//
|
|
9
|
+
// // Example usage
|
|
10
|
+
// addPath('exampleDir', '/path/to/examplePath');
|
|
11
|
+
// console.log(getPath('examplePath'));
|
|
12
|
+
// console.log(getAllPaths());
|
|
13
|
+
|
|
14
|
+
// Add paths
|
|
15
|
+
paths['output-local-terms'] = path.resolve('output', 'local-terms-dir');
|
|
16
|
+
paths['specsjson'] = path.resolve('specs.json');
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
/**
|
|
20
|
+
* Adds a directory or file to the directories object.
|
|
21
|
+
* @param {string} name - The name of the directory.
|
|
22
|
+
* @param {string} dir - The path of the directory.
|
|
23
|
+
*/
|
|
24
|
+
addPath: (name, dir) => {
|
|
25
|
+
paths[name] = path.resolve(dir);
|
|
26
|
+
},
|
|
27
|
+
/**
|
|
28
|
+
* Gets the resolved path of a directory or file by its name.
|
|
29
|
+
* @param {string} name - The name of the directory.
|
|
30
|
+
* @returns {string|null} The resolved path of the directory or null if not found.
|
|
31
|
+
*/
|
|
32
|
+
getPath: (name) => {
|
|
33
|
+
return paths[name] ? path.resolve(paths[name]) : null;
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* Gets all directories or files with their resolved paths.
|
|
37
|
+
* @returns {Object} An object containing all directories with their resolved paths.
|
|
38
|
+
*/
|
|
39
|
+
getAllPaths: () => {
|
|
40
|
+
const resolvedPaths = {};
|
|
41
|
+
for (const [name, dir] of Object.entries(paths)) {
|
|
42
|
+
resolvedPaths[name] = path.resolve(dir);
|
|
43
|
+
}
|
|
44
|
+
return resolvedPaths;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -19,7 +19,7 @@ function createVersionsIndex(outputPath) {
|
|
|
19
19
|
// Directory containing the version files
|
|
20
20
|
const versionsDir = path.join(outputPath, 'versions');
|
|
21
21
|
|
|
22
|
-
// Check if the directory exists, if not create it
|
|
22
|
+
// Check if the directory that holds the versions / snapshots exists, if not create it
|
|
23
23
|
if (!fs.existsSync(versionsDir)) {
|
|
24
24
|
fs.mkdirSync(versionsDir, { recursive: true });
|
|
25
25
|
console.log('Directory created:', versionsDir);
|
|
@@ -75,10 +75,9 @@ function fixMarkdownFiles(directory) {
|
|
|
75
75
|
// Write the modified content back to the file synchronously if there were any changes
|
|
76
76
|
if (modified) {
|
|
77
77
|
fs.writeFileSync(itemPath, data, 'utf8');
|
|
78
|
-
console.log(`\n SPEC-UP-T: Modified ${item.name}` + "\n");
|
|
79
78
|
}
|
|
80
79
|
} catch (err) {
|
|
81
|
-
console.error(`\n SPEC-UP-T: Error
|
|
80
|
+
console.error(`\n SPEC-UP-T: Error while trying to fix the markdown in file ${item.name}: ${err}` + "\n");
|
|
82
81
|
}
|
|
83
82
|
}
|
|
84
83
|
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Function to check the rate limit of the GitHub API
|
|
2
|
+
function checkRateLimit(response) {
|
|
3
|
+
const remaining = response.headers.get('X-RateLimit-Remaining');
|
|
4
|
+
const reset = response.headers.get('X-RateLimit-Reset');
|
|
5
|
+
|
|
6
|
+
if (response.status === 403 && remaining === '0') {
|
|
7
|
+
const resetTime = new Date(reset * 1000);
|
|
8
|
+
console.error(`\n SPEC-UP-T: Github API rate limit exceeded. Try again after ${resetTime}. See https://trustoverip.github.io/spec-up-t-website/docs/github-token/ for more info.\n`);
|
|
9
|
+
return true;
|
|
10
|
+
} else if (remaining !== null) {
|
|
11
|
+
console.log(`\n SPEC-UP-T: Github API rate limit: ${remaining} requests remaining. See https://trustoverip.github.io/spec-up-t-website/docs/github-token/ for more info.\n`);
|
|
12
|
+
} else {
|
|
13
|
+
console.warn(`\n SPEC-UP-T: Unable to determine rate limit status. Check your GitHub API token and network connection.\n`);
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
exports.checkRateLimit = checkRateLimit;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function matchTerm(text, term) {
|
|
2
|
+
if (text && typeof text === 'string') {
|
|
3
|
+
const firstLine = text.split('\n')[0].trim();
|
|
4
|
+
|
|
5
|
+
// Check if the string starts with `[[def:` and ends with `]]`
|
|
6
|
+
if (!firstLine.startsWith('[[def:') || !firstLine.endsWith(']]')) {
|
|
7
|
+
console.log('String does not start with `[[def:` or end with `]]`');
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Remove `[[def:` from the beginning and `]]` from the end
|
|
12
|
+
let relevantPart = firstLine.slice(7, -2);
|
|
13
|
+
|
|
14
|
+
// Split the string on `,` and trim the array elements
|
|
15
|
+
let termsArray = relevantPart.split(',').map(term => term.trim());
|
|
16
|
+
|
|
17
|
+
// Check if the term is in the array
|
|
18
|
+
return termsArray.includes(term);
|
|
19
|
+
} else {
|
|
20
|
+
console.error('Invalid text:', text);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.matchTerm = matchTerm;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function matchTerm(text, term) {
|
|
2
|
+
if (!text || typeof text !== 'string') {
|
|
3
|
+
// console.error('Invalid text:', text);
|
|
4
|
+
console.log('Nothing to match');
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const firstLine = text.split('\n')[0].trim();
|
|
9
|
+
|
|
10
|
+
// Check if the string starts with `[[def:` and ends with `]]`
|
|
11
|
+
if (!firstLine.startsWith('[[def:') || !firstLine.endsWith(']]')) {
|
|
12
|
+
console.log('String does not start with `[[def:` or end with `]]`');
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Remove `[[def:` from the beginning and `]]` from the end
|
|
17
|
+
let relevantPart = firstLine.slice(7, -2);
|
|
18
|
+
|
|
19
|
+
// Split the string on `,` and trim the array elements
|
|
20
|
+
let termsArray = relevantPart.split(',').map(term => term.trim());
|
|
21
|
+
|
|
22
|
+
// Check if the term is in the array
|
|
23
|
+
return termsArray.includes(term);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
exports.matchTerm = matchTerm;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
async function searchGitHubCode(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
|
|
2
|
+
const { Octokit } = await import("octokit");
|
|
3
|
+
const { throttling } = await import("@octokit/plugin-throttling");
|
|
4
|
+
|
|
5
|
+
// Create a throttled Octokit instance
|
|
6
|
+
const ThrottledOctokit = Octokit.plugin(throttling);
|
|
7
|
+
const octokit = new ThrottledOctokit({
|
|
8
|
+
auth: GITHUB_API_TOKEN,
|
|
9
|
+
throttle: {
|
|
10
|
+
onRateLimit: (retryAfter, options) => {
|
|
11
|
+
console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
|
|
12
|
+
if (options.request.retryCount <= 1) {
|
|
13
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
onAbuseLimit: (retryAfter, options) => {
|
|
18
|
+
console.warn(`Abuse detected for request ${options.method} ${options.url}`);
|
|
19
|
+
},
|
|
20
|
+
onSecondaryRateLimit: (retryAfter, options) => {
|
|
21
|
+
console.warn(`Secondary rate limit hit for request ${options.method} ${options.url}`);
|
|
22
|
+
if (options.request.retryCount <= 1) {
|
|
23
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Perform the search using Octokit with exact match
|
|
32
|
+
const searchResponse = await octokit.rest.search.code({
|
|
33
|
+
// q: `${searchString} repo:${owner}/${repo}`, // Fuzzy search
|
|
34
|
+
q: `"${searchString}" repo:${owner}/${repo} path:${subdirectory}`, // Use quotation marks for exact match
|
|
35
|
+
// q: `"${searchString}" repo:${owner}/${repo} case:true`, // DOES NOT WORK Use quotation marks for exact match. Case sensitive search
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Log the search results
|
|
39
|
+
console.log("Total results:", searchResponse.data.total_count);
|
|
40
|
+
|
|
41
|
+
// const rateLimitResponse = await octokit.rest.rateLimit.get();
|
|
42
|
+
// console.log("Rate limit:", rateLimitResponse.data);
|
|
43
|
+
|
|
44
|
+
// Fetch the content of each file
|
|
45
|
+
for (const item of searchResponse.data.items) {
|
|
46
|
+
let content = "";
|
|
47
|
+
const fileContentResponse = await octokit.rest.repos.getContent({
|
|
48
|
+
owner: item.repository.owner.login, // Repository owner
|
|
49
|
+
repo: item.repository.name, // Repository name
|
|
50
|
+
path: item.path, // File path
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Decode the file content (it's base64-encoded)
|
|
54
|
+
if (fileContentResponse.data.content) {
|
|
55
|
+
content = Buffer.from(fileContentResponse.data.content, "base64").toString("utf-8");
|
|
56
|
+
} else {
|
|
57
|
+
// If the file is larger than 1 MB, GitHub's API will return a download URL instead of the content.
|
|
58
|
+
console.log("File is too large. Download URL:", fileContentResponse.data.download_url);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
item.content = content;
|
|
62
|
+
}
|
|
63
|
+
return searchResponse;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("Error searching GitHub or fetching file content:", error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
exports.searchGitHubCode = searchGitHubCode;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
async function searchGitHubCode(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
|
|
2
|
+
const { Octokit } = await import("octokit");
|
|
3
|
+
const { throttling } = await import("@octokit/plugin-throttling");
|
|
4
|
+
|
|
5
|
+
// Create a throttled Octokit instance
|
|
6
|
+
const ThrottledOctokit = Octokit.plugin(throttling);
|
|
7
|
+
const octokit = new ThrottledOctokit({
|
|
8
|
+
auth: GITHUB_API_TOKEN,
|
|
9
|
+
throttle: {
|
|
10
|
+
onRateLimit: (retryAfter, options) => {
|
|
11
|
+
console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
|
|
12
|
+
if (options.request.retryCount <= 1) {
|
|
13
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
onAbuseLimit: (retryAfter, options) => {
|
|
18
|
+
console.warn(`Abuse detected for request ${options.method} ${options.url}`);
|
|
19
|
+
},
|
|
20
|
+
onSecondaryRateLimit: (retryAfter, options) => {
|
|
21
|
+
console.warn(`Secondary rate limit hit for request ${options.method} ${options.url}`);
|
|
22
|
+
if (options.request.retryCount <= 1) {
|
|
23
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Perform the search using Octokit with exact match
|
|
32
|
+
const searchResponse = await octokit.rest.search.code({
|
|
33
|
+
q: `"${searchString}" repo:${owner}/${repo} path:${subdirectory}`, // Exact match in subdirectory
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// Log the search results
|
|
37
|
+
console.log("Total results:", searchResponse.data.total_count);
|
|
38
|
+
|
|
39
|
+
// Fetch the content of each file
|
|
40
|
+
for (const item of searchResponse.data.items) {
|
|
41
|
+
// Check if the match is in the first line using text_matches
|
|
42
|
+
const isFirstLineMatch = item.text_matches.some(match =>
|
|
43
|
+
match.fragment.split("\n")[0].includes(searchString)
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (!isFirstLineMatch) {
|
|
47
|
+
console.log(`Skipping ${item.path}: Match not in the first line.`);
|
|
48
|
+
continue; // Skip this file
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fetch file content
|
|
52
|
+
let content = "";
|
|
53
|
+
const fileContentResponse = await octokit.rest.repos.getContent({
|
|
54
|
+
owner: item.repository.owner.login, // Repository owner
|
|
55
|
+
repo: item.repository.name, // Repository name
|
|
56
|
+
path: item.path, // File path
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Decode the file content (it's base64-encoded)
|
|
60
|
+
if (fileContentResponse.data.content) {
|
|
61
|
+
content = Buffer.from(fileContentResponse.data.content, "base64").toString("utf-8");
|
|
62
|
+
} else {
|
|
63
|
+
// If the file is larger than 1 MB, GitHub's API will return a download URL instead of the content.
|
|
64
|
+
console.log("File is too large. Download URL:", fileContentResponse.data.download_url);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Attach the content to the item
|
|
68
|
+
item.content = content;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return searchResponse;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("Error searching GitHub or fetching file content:", error);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.searchGitHubCode = searchGitHubCode;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
async function searchGitHubCode(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
|
|
2
|
+
const { Octokit } = await import("octokit");
|
|
3
|
+
const { throttling } = await import("@octokit/plugin-throttling");
|
|
4
|
+
|
|
5
|
+
// Create a throttled Octokit instance
|
|
6
|
+
const ThrottledOctokit = Octokit.plugin(throttling);
|
|
7
|
+
const octokit = new ThrottledOctokit({
|
|
8
|
+
auth: GITHUB_API_TOKEN,
|
|
9
|
+
throttle: {
|
|
10
|
+
onRateLimit: (retryAfter, options) => {
|
|
11
|
+
console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
|
|
12
|
+
if (options.request.retryCount <= 1) {
|
|
13
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
onAbuseLimit: (retryAfter, options) => {
|
|
18
|
+
console.warn(`Abuse detected for request ${options.method} ${options.url}`);
|
|
19
|
+
},
|
|
20
|
+
onSecondaryRateLimit: (retryAfter, options) => {
|
|
21
|
+
console.warn(`Secondary rate limit hit for request ${options.method} ${options.url}`);
|
|
22
|
+
if (options.request.retryCount <= 1) {
|
|
23
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Perform the search using Octokit with exact match
|
|
32
|
+
const searchResponse = await octokit.rest.search.code({
|
|
33
|
+
q: `"${searchString}" repo:${owner}/${repo} path:${subdirectory}`, // Exact match in subdirectory
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: "application/vnd.github.v3.text-match+json", // Include text-match media type
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Log the search results
|
|
40
|
+
console.log("Total results:", searchResponse.data.total_count);
|
|
41
|
+
|
|
42
|
+
// Fetch the content of each file
|
|
43
|
+
for (const item of searchResponse.data.items) {
|
|
44
|
+
// Check if the match is in the first line using text_matches
|
|
45
|
+
if (!item.text_matches) {
|
|
46
|
+
console.log(`Skipping ${item.path}: No text matches found.`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const isFirstLineMatch = item.text_matches.some(match =>
|
|
51
|
+
match.fragment.split("\n")[0].includes(searchString)
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (!isFirstLineMatch) {
|
|
55
|
+
console.log(`Skipping ${item.path}: Match not in the first line.`);
|
|
56
|
+
continue; // Skip this file
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Fetch file content
|
|
60
|
+
let content = "";
|
|
61
|
+
const fileContentResponse = await octokit.rest.repos.getContent({
|
|
62
|
+
owner: item.repository.owner.login, // Repository owner
|
|
63
|
+
repo: item.repository.name, // Repository name
|
|
64
|
+
path: item.path, // File path
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Decode the file content (it's base64-encoded)
|
|
68
|
+
if (fileContentResponse.data.content) {
|
|
69
|
+
content = Buffer.from(fileContentResponse.data.content, "base64").toString("utf-8");
|
|
70
|
+
} else {
|
|
71
|
+
// If the file is larger than 1 MB, GitHub's API will return a download URL instead of the content.
|
|
72
|
+
console.log("File is too large. Download URL:", fileContentResponse.data.download_url);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Attach the content to the item
|
|
76
|
+
item.content = content;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return searchResponse;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("Error searching GitHub or fetching file content:", error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
exports.searchGitHubCode = searchGitHubCode;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
async function searchGitHubCode(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
|
|
2
|
+
const { Octokit } = await import("octokit");
|
|
3
|
+
const { throttling } = await import("@octokit/plugin-throttling");
|
|
4
|
+
|
|
5
|
+
// Create a throttled Octokit instance
|
|
6
|
+
const ThrottledOctokit = Octokit.plugin(throttling);
|
|
7
|
+
const octokit = new ThrottledOctokit({
|
|
8
|
+
auth: GITHUB_API_TOKEN,
|
|
9
|
+
throttle: {
|
|
10
|
+
onRateLimit: (retryAfter, options) => {
|
|
11
|
+
console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
|
|
12
|
+
if (options.request.retryCount <= 1) {
|
|
13
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
onAbuseLimit: (retryAfter, options) => {
|
|
18
|
+
console.warn(`Abuse detected for request ${options.method} ${options.url}`);
|
|
19
|
+
},
|
|
20
|
+
onSecondaryRateLimit: (retryAfter, options) => {
|
|
21
|
+
console.warn(`Secondary rate limit hit for request ${options.method} ${options.url}`);
|
|
22
|
+
if (options.request.retryCount <= 1) {
|
|
23
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Perform the search using Octokit with exact match
|
|
32
|
+
const searchResponse = await octokit.rest.search.code({
|
|
33
|
+
q: `"${searchString}" repo:${owner}/${repo} path:${subdirectory}`, // Exact match in subdirectory
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: "application/vnd.github.v3.text-match+json", // Include text-match media type
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Log the search results
|
|
40
|
+
console.log("Total results:", searchResponse.data.total_count);
|
|
41
|
+
|
|
42
|
+
// Fetch the content of each file
|
|
43
|
+
for (const item of searchResponse.data.items) {
|
|
44
|
+
// Check if text_matches exists and is not empty
|
|
45
|
+
if (!item.text_matches || item.text_matches.length === 0) {
|
|
46
|
+
console.log(`Skipping ${item.path}: No text matches found.`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if the match is in the first line using text_matches
|
|
51
|
+
const isFirstLineMatch = item.text_matches.some(match => {
|
|
52
|
+
if (!match.fragment) {
|
|
53
|
+
console.log(`Skipping ${item.path}: No fragment found in text match.`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const firstLine = match.fragment.split("\n")[0];
|
|
58
|
+
return firstLine.includes(searchString);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!isFirstLineMatch) {
|
|
62
|
+
console.log(`Skipping ${item.path}: Match not in the first line.`);
|
|
63
|
+
continue; // Skip this file
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Fetch file content
|
|
67
|
+
let content = "";
|
|
68
|
+
const fileContentResponse = await octokit.rest.repos.getContent({
|
|
69
|
+
owner: item.repository.owner.login, // Repository owner
|
|
70
|
+
repo: item.repository.name, // Repository name
|
|
71
|
+
path: item.path, // File path
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Decode the file content (it's base64-encoded)
|
|
75
|
+
if (fileContentResponse.data.content) {
|
|
76
|
+
content = Buffer.from(fileContentResponse.data.content, "base64").toString("utf-8");
|
|
77
|
+
} else {
|
|
78
|
+
// If the file is larger than 1 MB, GitHub's API will return a download URL instead of the content.
|
|
79
|
+
console.log("File is too large. Download URL:", fileContentResponse.data.download_url);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Attach the content to the item
|
|
83
|
+
item.content = content;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return searchResponse;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Error searching GitHub or fetching file content:", error);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
exports.searchGitHubCode = searchGitHubCode;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
async function searchGitHubCode(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
|
|
2
|
+
const { Octokit } = await import("octokit");
|
|
3
|
+
const { throttling } = await import("@octokit/plugin-throttling");
|
|
4
|
+
|
|
5
|
+
// Create a throttled Octokit instance
|
|
6
|
+
const ThrottledOctokit = Octokit.plugin(throttling);
|
|
7
|
+
const octokit = new ThrottledOctokit({
|
|
8
|
+
auth: GITHUB_API_TOKEN,
|
|
9
|
+
throttle: {
|
|
10
|
+
onRateLimit: (retryAfter, options) => {
|
|
11
|
+
console.warn(`Request quota exhausted for request ${options.method} ${options.url}`);
|
|
12
|
+
if (options.request.retryCount <= 1) {
|
|
13
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
onAbuseLimit: (retryAfter, options) => {
|
|
18
|
+
console.warn(`Abuse detected for request ${options.method} ${options.url}`);
|
|
19
|
+
},
|
|
20
|
+
onSecondaryRateLimit: (retryAfter, options) => {
|
|
21
|
+
console.warn(`Secondary rate limit hit for request ${options.method} ${options.url}`);
|
|
22
|
+
if (options.request.retryCount <= 1) {
|
|
23
|
+
console.log(`Retrying after ${retryAfter} seconds...`);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Perform the search using Octokit with exact match
|
|
32
|
+
const searchResponse = await octokit.rest.search.code({
|
|
33
|
+
q: `"${searchString}" repo:${owner}/${repo} path:${subdirectory}`, // Exact match in subdirectory
|
|
34
|
+
headers: {
|
|
35
|
+
Accept: "application/vnd.github.v3.text-match+json", // Include text-match media type
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Log the search results
|
|
40
|
+
console.log("Total results:", searchResponse.data.total_count);
|
|
41
|
+
|
|
42
|
+
// Fetch the content of each file
|
|
43
|
+
for (const item of searchResponse.data.items) {
|
|
44
|
+
// Check if text_matches exists and is not empty
|
|
45
|
+
if (!item.text_matches || item.text_matches.length === 0) {
|
|
46
|
+
console.log(`Skipping ${item.path}: No text matches found.`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check if the match is in the first line using text_matches
|
|
51
|
+
const isFirstLineMatch = item.text_matches.some(match => {
|
|
52
|
+
if (!match.fragment) {
|
|
53
|
+
console.log(`Skipping ${item.path}: No fragment found in text match.`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const firstLine = match.fragment.split("\n")[0];
|
|
58
|
+
return firstLine.includes(searchString);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!isFirstLineMatch) {
|
|
62
|
+
console.log(`Skipping ${item.path}: Match not in the first line.`);
|
|
63
|
+
continue; // Skip this file
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Fetch file content
|
|
67
|
+
let content = "";
|
|
68
|
+
try {
|
|
69
|
+
const fileContentResponse = await octokit.rest.repos.getContent({
|
|
70
|
+
owner: item.repository.owner.login, // Repository owner
|
|
71
|
+
repo: item.repository.name, // Repository name
|
|
72
|
+
path: item.path, // File path
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Decode the file content (it's base64-encoded)
|
|
76
|
+
if (fileContentResponse.data.content) {
|
|
77
|
+
content = Buffer.from(fileContentResponse.data.content, "base64").toString("utf-8");
|
|
78
|
+
} else {
|
|
79
|
+
// If the file is larger than 1 MB, GitHub's API will return a download URL instead of the content.
|
|
80
|
+
console.log("File is too large. Download URL:", fileContentResponse.data.download_url);
|
|
81
|
+
}
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error(`Error fetching content for ${item.path}:`, error);
|
|
84
|
+
content = ""; // Set content to an empty string if there's an error
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Attach the content to the item
|
|
88
|
+
item.content = content;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return searchResponse;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error("Error searching GitHub or fetching file content:", error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
exports.searchGitHubCode = searchGitHubCode;
|