github-portfolio-analyzer 1.4.0 → 1.4.2
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/CHANGELOG.md +14 -0
- package/analyzer.manifest.json +1 -1
- package/package.json +1 -1
- package/schemas/portfolio-report.schema.json +6 -1
- package/src/core/report.js +4 -1
- package/src/github/repos.js +29 -9
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.4.2] - 2026-04-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- `normalizeRepository` now explicitly coerces `private` to boolean, preventing `undefined` from propagating through the pipeline and causing private repos to be treated as public.
|
|
11
|
+
- `buildReportModel` always includes `private` in output (was omitted when falsy), ensuring the worker always receives an explicit value.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Language breakdown via GitHub `/languages` API endpoint. Each repo now has a `languages` field (`Record<string, number>`) with byte counts per language, enabling multi-language badge display in the portfolio frontend.
|
|
15
|
+
|
|
16
|
+
## [1.4.1] - 2026-04-03
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- `classifyFork`: removed `pushed_at` heuristic from fallback logic. Forks without a successful upstream comparison now always return `passive`. Previously, recently-cloned forks with no own commits were incorrectly classified as `active`.
|
|
20
|
+
|
|
7
21
|
## [1.4.0] — 2026-04-03
|
|
8
22
|
|
|
9
23
|
### Added
|
package/analyzer.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -203,7 +203,8 @@
|
|
|
203
203
|
"priorityBand",
|
|
204
204
|
"priorityOverrides",
|
|
205
205
|
"priorityWhy",
|
|
206
|
-
"nextAction"
|
|
206
|
+
"nextAction",
|
|
207
|
+
"private"
|
|
207
208
|
],
|
|
208
209
|
"properties": {
|
|
209
210
|
"slug": { "type": "string" },
|
|
@@ -243,6 +244,10 @@
|
|
|
243
244
|
},
|
|
244
245
|
"nextAction": { "type": "string" },
|
|
245
246
|
"language": { "type": "string" },
|
|
247
|
+
"languages": {
|
|
248
|
+
"type": "object",
|
|
249
|
+
"additionalProperties": { "type": "number" }
|
|
250
|
+
},
|
|
246
251
|
"topics": {
|
|
247
252
|
"type": "array",
|
|
248
253
|
"items": { "type": "string" }
|
package/src/core/report.js
CHANGED
|
@@ -205,13 +205,16 @@ export function buildReportModel(portfolioData, inventoryData = null, options =
|
|
|
205
205
|
nextAction: String(item.nextAction ?? '').trim(),
|
|
206
206
|
// presentation fields — passed directly from portfolio item
|
|
207
207
|
...(item.language != null ? { language: item.language } : {}),
|
|
208
|
+
...(typeof item.languages === 'object' && item.languages !== null && !Array.isArray(item.languages) && Object.keys(item.languages).length > 0
|
|
209
|
+
? { languages: item.languages }
|
|
210
|
+
: {}),
|
|
208
211
|
...(Array.isArray(item.topics) && item.topics.length > 0 ? { topics: item.topics } : {}),
|
|
209
212
|
...(!isPrivate && item.htmlUrl != null ? { htmlUrl: item.htmlUrl } : {}),
|
|
210
213
|
...(!isPrivate && item.homepage != null ? { homepage: item.homepage } : {}),
|
|
211
214
|
...(item.category != null ? { category: item.category } : {}),
|
|
212
215
|
...(item.fork != null ? { fork: Boolean(item.fork) } : {}),
|
|
213
216
|
...(item.forkType != null ? { forkType: item.forkType } : {}),
|
|
214
|
-
|
|
217
|
+
private: Boolean(item.private ?? false),
|
|
215
218
|
...(item.publicAlias != null ? { publicAlias: item.publicAlias } : {}),
|
|
216
219
|
...(!isPrivate && item.description != null ? { description: item.description } : {}),
|
|
217
220
|
...(isPrivate && item.description != null ? { _description: item.description } : {})
|
package/src/github/repos.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { daysSince } from '../core/classification.js';
|
|
2
|
-
|
|
3
1
|
const PAGE_SIZE = 100;
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
4
|
* Classifies a fork as active or passive.
|
|
7
|
-
* Active forks are
|
|
8
|
-
* when upstream comparison metadata is unavailable.
|
|
5
|
+
* Active forks are ahead of the upstream default branch.
|
|
9
6
|
*/
|
|
10
7
|
export async function classifyFork(client, repo, asOfDate = new Date().toISOString().slice(0, 10)) {
|
|
11
8
|
if (!repo?.fork) {
|
|
@@ -13,9 +10,7 @@ export async function classifyFork(client, repo, asOfDate = new Date().toISOStri
|
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
const parent = repo.parent;
|
|
16
|
-
const
|
|
17
|
-
const isRecentlyActive = pushedAt ? daysSince(pushedAt, asOfDate) <= 90 : false;
|
|
18
|
-
const fallbackForkType = isRecentlyActive ? 'active' : 'passive';
|
|
13
|
+
const fallbackForkType = 'passive';
|
|
19
14
|
|
|
20
15
|
if (!parent) {
|
|
21
16
|
return fallbackForkType;
|
|
@@ -78,6 +73,27 @@ export async function fetchAllRepositories(client, asOfDate = new Date().toISOSt
|
|
|
78
73
|
);
|
|
79
74
|
}
|
|
80
75
|
|
|
76
|
+
for (let index = 0; index < repositories.length; index += 5) {
|
|
77
|
+
const batch = repositories.slice(index, index + 5);
|
|
78
|
+
await Promise.all(
|
|
79
|
+
batch.map(async (repository) => {
|
|
80
|
+
const ownerLogin = repository.owner?.login ?? repository.ownerLogin ?? '';
|
|
81
|
+
if (!ownerLogin || !repository.name) {
|
|
82
|
+
repository.languages = {};
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
repository.languages = await client.request(
|
|
88
|
+
`/repos/${encodeURIComponent(ownerLogin)}/${encodeURIComponent(repository.name)}/languages`
|
|
89
|
+
);
|
|
90
|
+
} catch {
|
|
91
|
+
repository.languages = {};
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
81
97
|
return repositories;
|
|
82
98
|
}
|
|
83
99
|
|
|
@@ -86,9 +102,9 @@ export function normalizeRepository(repo) {
|
|
|
86
102
|
id: repo.id,
|
|
87
103
|
nodeId: repo.node_id,
|
|
88
104
|
name: repo.name,
|
|
89
|
-
ownerLogin: repo.owner?.login ?? '',
|
|
105
|
+
ownerLogin: repo.owner?.login ?? repo.ownerLogin ?? '',
|
|
90
106
|
fullName: repo.full_name,
|
|
91
|
-
private: repo.private,
|
|
107
|
+
private: Boolean(repo.private),
|
|
92
108
|
archived: repo.archived,
|
|
93
109
|
fork: repo.fork,
|
|
94
110
|
forkType: repo.forkType ?? null,
|
|
@@ -96,6 +112,10 @@ export function normalizeRepository(repo) {
|
|
|
96
112
|
htmlUrl: repo.html_url,
|
|
97
113
|
description: repo.description,
|
|
98
114
|
language: repo.language,
|
|
115
|
+
languages:
|
|
116
|
+
typeof repo.languages === 'object' && repo.languages !== null && !Array.isArray(repo.languages)
|
|
117
|
+
? repo.languages
|
|
118
|
+
: {},
|
|
99
119
|
homepage: typeof repo.homepage === 'string' && repo.homepage.trim().length > 0
|
|
100
120
|
? repo.homepage.trim()
|
|
101
121
|
: null,
|