@oss-scout/core 0.1.1 → 0.2.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.
- package/dist/cli.bundle.cjs +48 -48
- package/dist/cli.js +110 -86
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/config.js +77 -71
- package/dist/commands/results.d.ts +1 -1
- package/dist/commands/results.js +1 -1
- package/dist/commands/search.d.ts +2 -2
- package/dist/commands/search.js +16 -6
- package/dist/commands/setup.d.ts +1 -1
- package/dist/commands/setup.js +27 -21
- package/dist/commands/validation.d.ts +1 -1
- package/dist/commands/validation.js +1 -1
- package/dist/commands/vet-list.d.ts +2 -2
- package/dist/commands/vet-list.js +12 -5
- package/dist/commands/vet.d.ts +3 -3
- package/dist/commands/vet.js +9 -5
- package/dist/core/bootstrap.d.ts +1 -1
- package/dist/core/bootstrap.js +20 -16
- package/dist/core/category-mapping.d.ts +1 -1
- package/dist/core/category-mapping.js +104 -13
- package/dist/core/errors.d.ts +8 -1
- package/dist/core/errors.js +31 -19
- package/dist/core/gist-state-store.d.ts +1 -1
- package/dist/core/gist-state-store.js +36 -27
- package/dist/core/github.d.ts +1 -1
- package/dist/core/github.js +5 -5
- package/dist/core/http-cache.js +26 -22
- package/dist/core/issue-discovery.d.ts +3 -3
- package/dist/core/issue-discovery.js +325 -270
- package/dist/core/issue-eligibility.d.ts +2 -2
- package/dist/core/issue-eligibility.js +26 -21
- package/dist/core/issue-filtering.js +23 -15
- package/dist/core/issue-scoring.js +1 -1
- package/dist/core/issue-vetting.d.ts +2 -2
- package/dist/core/issue-vetting.js +66 -53
- package/dist/core/local-state.d.ts +1 -1
- package/dist/core/local-state.js +16 -14
- package/dist/core/repo-health.d.ts +2 -2
- package/dist/core/repo-health.js +46 -35
- package/dist/core/schemas.d.ts +3 -1
- package/dist/core/schemas.js +41 -18
- package/dist/core/search-budget.js +3 -3
- package/dist/core/search-phases.d.ts +6 -6
- package/dist/core/search-phases.js +23 -19
- package/dist/core/types.d.ts +9 -9
- package/dist/core/types.js +15 -3
- package/dist/core/utils.d.ts +10 -1
- package/dist/core/utils.js +44 -25
- package/dist/formatters/json.d.ts +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +5 -5
- package/dist/scout.d.ts +4 -5
- package/dist/scout.js +72 -31
- package/package.json +1 -1
package/dist/core/utils.js
CHANGED
|
@@ -1,39 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utility functions for oss-scout.
|
|
3
3
|
*/
|
|
4
|
-
import * as fs from
|
|
5
|
-
import * as path from
|
|
6
|
-
import * as os from
|
|
7
|
-
import { execFileSync } from
|
|
8
|
-
import { ConfigurationError, errorMessage } from
|
|
9
|
-
import { debug } from
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as os from "os";
|
|
7
|
+
import { execFileSync } from "child_process";
|
|
8
|
+
import { ConfigurationError, errorMessage } from "./errors.js";
|
|
9
|
+
import { debug } from "./logger.js";
|
|
10
10
|
export function sleep(ms) {
|
|
11
11
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
12
|
}
|
|
13
|
-
const MODULE =
|
|
13
|
+
const MODULE = "utils";
|
|
14
14
|
let cachedGitHubToken = null;
|
|
15
15
|
let tokenFetchAttempted = false;
|
|
16
16
|
export function getDataDir() {
|
|
17
|
-
const dir = path.join(os.homedir(),
|
|
17
|
+
const dir = path.join(os.homedir(), ".oss-scout");
|
|
18
18
|
if (!fs.existsSync(dir)) {
|
|
19
19
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
20
20
|
}
|
|
21
21
|
return dir;
|
|
22
22
|
}
|
|
23
23
|
export function getCacheDir() {
|
|
24
|
-
const dir = path.join(getDataDir(),
|
|
24
|
+
const dir = path.join(getDataDir(), "cache");
|
|
25
25
|
if (!fs.existsSync(dir)) {
|
|
26
26
|
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
27
27
|
}
|
|
28
28
|
return dir;
|
|
29
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Extract "owner/repo" from any GitHub URL format:
|
|
32
|
+
* - https://github.com/owner/repo
|
|
33
|
+
* - https://github.com/owner/repo/pull/123
|
|
34
|
+
* - https://github.com/owner/repo/issues/123
|
|
35
|
+
* - https://api.github.com/repos/owner/repo
|
|
36
|
+
* - https://api.github.com/repos/owner/repo/...
|
|
37
|
+
*/
|
|
38
|
+
export function extractRepoFromUrl(url) {
|
|
39
|
+
// API URLs: https://api.github.com/repos/owner/repo[/...]
|
|
40
|
+
const apiMatch = url.match(/api\.github\.com\/repos\/([^/]+\/[^/]+)/);
|
|
41
|
+
if (apiMatch)
|
|
42
|
+
return apiMatch[1];
|
|
43
|
+
// Web URLs: https://github.com/owner/repo[/...]
|
|
44
|
+
const webMatch = url.match(/github\.com\/([^/]+\/[^/]+)/);
|
|
45
|
+
if (webMatch)
|
|
46
|
+
return webMatch[1];
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
30
49
|
const OWNER_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
31
50
|
const REPO_PATTERN = /^[a-zA-Z0-9_.-]+$/;
|
|
32
51
|
function isValidOwnerRepo(owner, repo) {
|
|
33
52
|
return OWNER_PATTERN.test(owner) && REPO_PATTERN.test(repo);
|
|
34
53
|
}
|
|
35
54
|
export function parseGitHubUrl(url) {
|
|
36
|
-
if (!url.startsWith(
|
|
55
|
+
if (!url.startsWith("https://github.com/"))
|
|
37
56
|
return null;
|
|
38
57
|
const prMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
39
58
|
if (prMatch) {
|
|
@@ -41,7 +60,7 @@ export function parseGitHubUrl(url) {
|
|
|
41
60
|
const repo = prMatch[2];
|
|
42
61
|
if (!isValidOwnerRepo(owner, repo))
|
|
43
62
|
return null;
|
|
44
|
-
return { owner, repo, number: parseInt(prMatch[3], 10), type:
|
|
63
|
+
return { owner, repo, number: parseInt(prMatch[3], 10), type: "pull" };
|
|
45
64
|
}
|
|
46
65
|
const issueMatch = url.match(/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/);
|
|
47
66
|
if (issueMatch) {
|
|
@@ -49,7 +68,7 @@ export function parseGitHubUrl(url) {
|
|
|
49
68
|
const repo = issueMatch[2];
|
|
50
69
|
if (!isValidOwnerRepo(owner, repo))
|
|
51
70
|
return null;
|
|
52
|
-
return { owner, repo, number: parseInt(issueMatch[3], 10), type:
|
|
71
|
+
return { owner, repo, number: parseInt(issueMatch[3], 10), type: "issues" };
|
|
53
72
|
}
|
|
54
73
|
return null;
|
|
55
74
|
}
|
|
@@ -58,12 +77,12 @@ export function daysBetween(from, to = new Date()) {
|
|
|
58
77
|
}
|
|
59
78
|
export function getCLIVersion() {
|
|
60
79
|
try {
|
|
61
|
-
const pkgPath = path.join(path.dirname(process.argv[1]),
|
|
62
|
-
return JSON.parse(fs.readFileSync(pkgPath,
|
|
80
|
+
const pkgPath = path.join(path.dirname(process.argv[1]), "..", "package.json");
|
|
81
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
|
|
63
82
|
}
|
|
64
83
|
catch (err) {
|
|
65
84
|
debug(MODULE, `Could not read CLI version: ${errorMessage(err)}`);
|
|
66
|
-
return
|
|
85
|
+
return "unknown";
|
|
67
86
|
}
|
|
68
87
|
}
|
|
69
88
|
export function getGitHubToken() {
|
|
@@ -77,30 +96,30 @@ export function getGitHubToken() {
|
|
|
77
96
|
return cachedGitHubToken;
|
|
78
97
|
}
|
|
79
98
|
try {
|
|
80
|
-
const token = execFileSync(
|
|
81
|
-
encoding:
|
|
82
|
-
stdio: [
|
|
99
|
+
const token = execFileSync("gh", ["auth", "token"], {
|
|
100
|
+
encoding: "utf-8",
|
|
101
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
83
102
|
timeout: 2000,
|
|
84
103
|
}).trim();
|
|
85
104
|
if (token && token.length > 0) {
|
|
86
105
|
cachedGitHubToken = token;
|
|
87
|
-
debug(MODULE,
|
|
106
|
+
debug(MODULE, "Using GitHub token from gh CLI");
|
|
88
107
|
return cachedGitHubToken;
|
|
89
108
|
}
|
|
90
109
|
}
|
|
91
110
|
catch (err) {
|
|
92
|
-
debug(MODULE,
|
|
111
|
+
debug(MODULE, "gh auth token failed", err);
|
|
93
112
|
}
|
|
94
113
|
return null;
|
|
95
114
|
}
|
|
96
115
|
export function requireGitHubToken() {
|
|
97
116
|
const token = getGitHubToken();
|
|
98
117
|
if (!token) {
|
|
99
|
-
throw new ConfigurationError(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
throw new ConfigurationError("GitHub authentication required.\n\n" +
|
|
119
|
+
"Options:\n" +
|
|
120
|
+
" 1. Use gh CLI: gh auth login\n" +
|
|
121
|
+
" 2. Set GITHUB_TOKEN environment variable\n\n" +
|
|
122
|
+
"The gh CLI is recommended - install from https://cli.github.com");
|
|
104
123
|
}
|
|
105
124
|
return token;
|
|
106
125
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JSON output formatter for oss-scout CLI.
|
|
3
3
|
*/
|
|
4
|
-
import type { ErrorCode } from
|
|
4
|
+
import type { ErrorCode } from "../core/errors.js";
|
|
5
5
|
export declare function formatJsonSuccess<T>(data: T): string;
|
|
6
6
|
export declare function formatJsonError(error: string, errorCode?: ErrorCode): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @packageDocumentation
|
|
16
16
|
*/
|
|
17
|
-
export { createScout, OssScout } from
|
|
18
|
-
export type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, RepoScoreUpdate, ProjectHealth, SearchPriority, CheckResult, VetListOptions, VetListResult, VetListEntry, VetListSummary, } from
|
|
19
|
-
export type { ScoutState, ScoutPreferences, RepoScore, RepoSignals, IssueVettingResult, ContributionGuidelines, TrackedIssue, IssueScope, ProjectCategory, StoredMergedPR, StoredClosedPR, SearchStrategy, } from
|
|
20
|
-
export { ScoutStateSchema, ScoutPreferencesSchema, RepoScoreSchema, IssueScopeSchema, ProjectCategorySchema, SearchStrategySchema, } from
|
|
21
|
-
export { requireGitHubToken, getGitHubToken } from
|
|
22
|
-
export { IssueDiscovery } from
|
|
23
|
-
export { IssueVetter, type ScoutStateReader } from
|
|
17
|
+
export { createScout, OssScout } from "./scout.js";
|
|
18
|
+
export type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, RepoScoreUpdate, ProjectHealth, SearchPriority, CheckResult, VetListOptions, VetListResult, VetListEntry, VetListSummary, } from "./core/types.js";
|
|
19
|
+
export type { ScoutState, ScoutPreferences, RepoScore, RepoSignals, IssueVettingResult, ContributionGuidelines, TrackedIssue, IssueScope, ProjectCategory, StoredMergedPR, StoredClosedPR, SearchStrategy, } from "./core/schemas.js";
|
|
20
|
+
export { ScoutStateSchema, ScoutPreferencesSchema, RepoScoreSchema, IssueScopeSchema, ProjectCategorySchema, SearchStrategySchema, } from "./core/schemas.js";
|
|
21
|
+
export { requireGitHubToken, getGitHubToken } from "./core/utils.js";
|
|
22
|
+
export { IssueDiscovery } from "./core/issue-discovery.js";
|
|
23
|
+
export { IssueVetter, type ScoutStateReader } from "./core/issue-vetting.js";
|
package/dist/index.js
CHANGED
|
@@ -15,11 +15,11 @@
|
|
|
15
15
|
* @packageDocumentation
|
|
16
16
|
*/
|
|
17
17
|
// Main API
|
|
18
|
-
export { createScout, OssScout } from
|
|
18
|
+
export { createScout, OssScout } from "./scout.js";
|
|
19
19
|
// Schemas (for consumers who need runtime validation)
|
|
20
|
-
export { ScoutStateSchema, ScoutPreferencesSchema, RepoScoreSchema, IssueScopeSchema, ProjectCategorySchema, SearchStrategySchema, } from
|
|
20
|
+
export { ScoutStateSchema, ScoutPreferencesSchema, RepoScoreSchema, IssueScopeSchema, ProjectCategorySchema, SearchStrategySchema, } from "./core/schemas.js";
|
|
21
21
|
// Utilities
|
|
22
|
-
export { requireGitHubToken, getGitHubToken } from
|
|
22
|
+
export { requireGitHubToken, getGitHubToken } from "./core/utils.js";
|
|
23
23
|
// Internal classes (for advanced use)
|
|
24
|
-
export { IssueDiscovery } from
|
|
25
|
-
export { IssueVetter } from
|
|
24
|
+
export { IssueDiscovery } from "./core/issue-discovery.js";
|
|
25
|
+
export { IssueVetter } from "./core/issue-vetting.js";
|
package/dist/scout.d.ts
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* Provides personalized issue discovery, vetting, and scoring.
|
|
5
5
|
* Implements ScoutStateReader to bridge state with the search engine.
|
|
6
6
|
*/
|
|
7
|
-
import type { ScoutStateReader } from
|
|
8
|
-
import type { ScoutState, ScoutPreferences, RepoScore, SavedCandidate } from
|
|
9
|
-
import type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, RepoScoreUpdate, ProjectCategory, VetListOptions, VetListResult } from
|
|
10
|
-
import { GistStateStore } from
|
|
7
|
+
import type { ScoutStateReader } from "./core/issue-vetting.js";
|
|
8
|
+
import type { ScoutState, ScoutPreferences, RepoScore, SavedCandidate } from "./core/schemas.js";
|
|
9
|
+
import type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, RepoScoreUpdate, ProjectCategory, VetListOptions, VetListResult } from "./core/types.js";
|
|
10
|
+
import { GistStateStore } from "./core/gist-state-store.js";
|
|
11
11
|
/**
|
|
12
12
|
* Create an OssScout instance.
|
|
13
13
|
*
|
|
@@ -114,7 +114,6 @@ export declare class OssScout implements ScoutStateReader {
|
|
|
114
114
|
* Get the full state snapshot for serialization or external consumption.
|
|
115
115
|
*/
|
|
116
116
|
getState(): Readonly<ScoutState>;
|
|
117
|
-
private extractRepoFromUrl;
|
|
118
117
|
private updateRepoScoreFromPRs;
|
|
119
118
|
/**
|
|
120
119
|
* Calculate repo score (1-10) from observed data.
|
package/dist/scout.js
CHANGED
|
@@ -4,12 +4,55 @@
|
|
|
4
4
|
* Provides personalized issue discovery, vetting, and scoring.
|
|
5
5
|
* Implements ScoutStateReader to bridge state with the search engine.
|
|
6
6
|
*/
|
|
7
|
-
import { IssueDiscovery } from
|
|
8
|
-
import { ScoutStateSchema } from
|
|
9
|
-
import { GistStateStore, mergeStates } from
|
|
10
|
-
import { getOctokit } from
|
|
11
|
-
import { loadLocalState } from
|
|
12
|
-
import { warn } from
|
|
7
|
+
import { IssueDiscovery } from "./core/issue-discovery.js";
|
|
8
|
+
import { ScoutStateSchema } from "./core/schemas.js";
|
|
9
|
+
import { GistStateStore, mergeStates } from "./core/gist-state-store.js";
|
|
10
|
+
import { getOctokit } from "./core/github.js";
|
|
11
|
+
import { loadLocalState } from "./core/local-state.js";
|
|
12
|
+
import { warn } from "./core/logger.js";
|
|
13
|
+
import { extractRepoFromUrl } from "./core/utils.js";
|
|
14
|
+
/** Wrap a real Octokit instance as GistOctokitLike without unsafe double casts. */
|
|
15
|
+
function toGistOctokit(octokit) {
|
|
16
|
+
return {
|
|
17
|
+
gists: {
|
|
18
|
+
async get(params) {
|
|
19
|
+
const { data } = await octokit.gists.get(params);
|
|
20
|
+
if (!data.id)
|
|
21
|
+
throw new Error("Gist get returned no id");
|
|
22
|
+
const files = data.files
|
|
23
|
+
? Object.fromEntries(Object.entries(data.files).map(([k, v]) => [
|
|
24
|
+
k,
|
|
25
|
+
v ? { content: v.content } : undefined,
|
|
26
|
+
]))
|
|
27
|
+
: null;
|
|
28
|
+
return { data: { id: data.id, files } };
|
|
29
|
+
},
|
|
30
|
+
async create(params) {
|
|
31
|
+
const { data } = await octokit.gists.create(params);
|
|
32
|
+
if (!data.id)
|
|
33
|
+
throw new Error("Gist create returned no id");
|
|
34
|
+
return { data: { id: data.id } };
|
|
35
|
+
},
|
|
36
|
+
async update(params) {
|
|
37
|
+
const { data } = await octokit.gists.update(params);
|
|
38
|
+
if (!data.id)
|
|
39
|
+
throw new Error("Gist update returned no id");
|
|
40
|
+
return { data: { id: data.id } };
|
|
41
|
+
},
|
|
42
|
+
async list(params) {
|
|
43
|
+
const { data } = await octokit.gists.list(params);
|
|
44
|
+
return {
|
|
45
|
+
data: data
|
|
46
|
+
.filter((g) => g.id)
|
|
47
|
+
.map((g) => ({
|
|
48
|
+
id: g.id,
|
|
49
|
+
description: g.description ?? null,
|
|
50
|
+
})),
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
13
56
|
/**
|
|
14
57
|
* Create an OssScout instance.
|
|
15
58
|
*
|
|
@@ -34,14 +77,14 @@ import { warn } from './core/logger.js';
|
|
|
34
77
|
export async function createScout(config) {
|
|
35
78
|
let state;
|
|
36
79
|
let gistStore = null;
|
|
37
|
-
if (config.persistence ===
|
|
80
|
+
if (config.persistence === "provided") {
|
|
38
81
|
state = config.initialState;
|
|
39
82
|
}
|
|
40
|
-
else if (config.persistence ===
|
|
41
|
-
gistStore = new GistStateStore(getOctokit(config.githubToken));
|
|
83
|
+
else if (config.persistence === "gist") {
|
|
84
|
+
gistStore = new GistStateStore(toGistOctokit(getOctokit(config.githubToken)));
|
|
42
85
|
const result = await gistStore.bootstrap();
|
|
43
86
|
if (result.degraded) {
|
|
44
|
-
warn(
|
|
87
|
+
warn("scout", "Gist sync unavailable — running in offline mode. Changes will only be saved locally.");
|
|
45
88
|
}
|
|
46
89
|
const localState = loadLocalState();
|
|
47
90
|
state = mergeStates(localState, result.state);
|
|
@@ -126,13 +169,13 @@ export class OssScout {
|
|
|
126
169
|
})
|
|
127
170
|
.catch((error) => {
|
|
128
171
|
const msg = error instanceof Error ? error.message : String(error);
|
|
129
|
-
const isGone = msg.includes(
|
|
172
|
+
const isGone = msg.includes("Not Found") || msg.includes("410");
|
|
130
173
|
results.push({
|
|
131
174
|
issueUrl: item.issueUrl,
|
|
132
175
|
repo: item.repo,
|
|
133
176
|
number: item.number,
|
|
134
177
|
title: item.title,
|
|
135
|
-
status: isGone ?
|
|
178
|
+
status: isGone ? "closed" : "error",
|
|
136
179
|
errorMessage: msg,
|
|
137
180
|
});
|
|
138
181
|
})
|
|
@@ -147,15 +190,18 @@ export class OssScout {
|
|
|
147
190
|
await Promise.allSettled(pending.values());
|
|
148
191
|
const summary = {
|
|
149
192
|
total: results.length,
|
|
150
|
-
stillAvailable: results.filter((r) => r.status ===
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
193
|
+
stillAvailable: results.filter((r) => r.status === "still_available")
|
|
194
|
+
.length,
|
|
195
|
+
claimed: results.filter((r) => r.status === "claimed").length,
|
|
196
|
+
closed: results.filter((r) => r.status === "closed").length,
|
|
197
|
+
hasPR: results.filter((r) => r.status === "has_pr").length,
|
|
198
|
+
errors: results.filter((r) => r.status === "error").length,
|
|
155
199
|
};
|
|
156
200
|
let prunedCount;
|
|
157
201
|
if (options?.prune) {
|
|
158
|
-
const unavailableUrls = new Set(results
|
|
202
|
+
const unavailableUrls = new Set(results
|
|
203
|
+
.filter((r) => r.status !== "still_available")
|
|
204
|
+
.map((r) => r.issueUrl));
|
|
159
205
|
const before = (this.state.savedResults ?? []).length;
|
|
160
206
|
this.state.savedResults = (this.state.savedResults ?? []).filter((r) => !unavailableUrls.has(r.issueUrl));
|
|
161
207
|
prunedCount = before - (this.state.savedResults?.length ?? 0);
|
|
@@ -166,16 +212,16 @@ export class OssScout {
|
|
|
166
212
|
classifyVetResult(candidate) {
|
|
167
213
|
const checks = candidate.vettingResult.checks;
|
|
168
214
|
if (!checks.noExistingPR)
|
|
169
|
-
return
|
|
215
|
+
return "has_pr";
|
|
170
216
|
if (!checks.notClaimed)
|
|
171
|
-
return
|
|
172
|
-
return
|
|
217
|
+
return "claimed";
|
|
218
|
+
return "still_available";
|
|
173
219
|
}
|
|
174
220
|
// ── State Reads (ScoutStateReader implementation) ───────────────────
|
|
175
221
|
getReposWithMergedPRs() {
|
|
176
222
|
const repoCounts = new Map();
|
|
177
223
|
for (const pr of this.state.mergedPRs ?? []) {
|
|
178
|
-
const repo =
|
|
224
|
+
const repo = extractRepoFromUrl(pr.url);
|
|
179
225
|
if (repo) {
|
|
180
226
|
repoCounts.set(repo, (repoCounts.get(repo) ?? 0) + 1);
|
|
181
227
|
}
|
|
@@ -353,21 +399,16 @@ export class OssScout {
|
|
|
353
399
|
return this.state;
|
|
354
400
|
}
|
|
355
401
|
// ── Private helpers ─────────────────────────────────────────────────
|
|
356
|
-
extractRepoFromUrl(url) {
|
|
357
|
-
const match = url.match(/github\.com\/([^/]+\/[^/]+)\//);
|
|
358
|
-
return match ? match[1] : null;
|
|
359
|
-
}
|
|
360
402
|
updateRepoScoreFromPRs(repo) {
|
|
361
|
-
const mergedCount = (this.state.mergedPRs ?? []).filter((p) =>
|
|
362
|
-
const closedCount = (this.state.closedPRs ?? []).filter((p) =>
|
|
403
|
+
const mergedCount = (this.state.mergedPRs ?? []).filter((p) => extractRepoFromUrl(p.url) === repo).length;
|
|
404
|
+
const closedCount = (this.state.closedPRs ?? []).filter((p) => extractRepoFromUrl(p.url) === repo).length;
|
|
363
405
|
this.updateRepoScore(repo, {
|
|
364
406
|
mergedPRCount: mergedCount,
|
|
365
407
|
closedWithoutMergeCount: closedCount,
|
|
366
408
|
lastMergedAt: mergedCount > 0
|
|
367
409
|
? (this.state.mergedPRs ?? [])
|
|
368
|
-
.filter((p) =>
|
|
369
|
-
.sort((a, b) => b.mergedAt.localeCompare(a.mergedAt))[0]
|
|
370
|
-
?.mergedAt
|
|
410
|
+
.filter((p) => extractRepoFromUrl(p.url) === repo)
|
|
411
|
+
.sort((a, b) => b.mergedAt.localeCompare(a.mergedAt))[0]?.mergedAt
|
|
371
412
|
: undefined,
|
|
372
413
|
});
|
|
373
414
|
}
|