github-reporadar 0.1.0 → 0.1.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/README.md +1 -0
- package/dist/assets/index-Bb1rtvKk.js +73 -0
- package/dist/index.html +1 -1
- package/dist-cli/cli.js +108 -64
- package/package.json +1 -1
- package/dist/assets/index-7rO9STC2.js +0 -73
package/dist/index.html
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap"
|
|
17
17
|
rel="stylesheet"
|
|
18
18
|
/>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-Bb1rtvKk.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-B5UAXuco.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
package/dist-cli/cli.js
CHANGED
|
@@ -195,24 +195,41 @@ async function fetchLogin(token) {
|
|
|
195
195
|
const { json } = await rest(token, "/user");
|
|
196
196
|
return json.login;
|
|
197
197
|
}
|
|
198
|
-
async function listRecentRepos(token, since, includePrivate) {
|
|
199
|
-
const repos = [];
|
|
198
|
+
async function listRecentRepos(token, since, includePrivate, orgs = []) {
|
|
200
199
|
const visibility = "";
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
200
|
+
const urls = [
|
|
201
|
+
`/user/repos?sort=pushed&per_page=100&affiliation=owner${visibility}`,
|
|
202
|
+
// --orgで足した組織。トークンが見える範囲(type=all)をpushed降順で
|
|
203
|
+
...orgs.map((o) => `/orgs/${encodeURIComponent(o)}/repos?sort=pushed&per_page=100&type=all`)
|
|
204
|
+
];
|
|
205
|
+
const seen = /* @__PURE__ */ new Set();
|
|
206
|
+
const repos = [];
|
|
207
|
+
for (const start of urls) {
|
|
208
|
+
let url = start;
|
|
209
|
+
while (url) {
|
|
210
|
+
const { json, next } = await rest(token, url);
|
|
211
|
+
const page = json;
|
|
212
|
+
for (const r of page) {
|
|
213
|
+
if (!seen.has(r.full_name)) {
|
|
214
|
+
seen.add(r.full_name);
|
|
215
|
+
repos.push(r);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (page.length === 0 || new Date(page[page.length - 1].pushed_at) < since) break;
|
|
219
|
+
url = next;
|
|
220
|
+
}
|
|
208
221
|
}
|
|
209
222
|
return repos.filter((r) => {
|
|
210
223
|
return new Date(r.pushed_at) >= since;
|
|
211
224
|
});
|
|
212
225
|
}
|
|
213
|
-
|
|
226
|
+
function commitsUrl(fullName, since, author) {
|
|
227
|
+
const base = `/repos/${fullName}/commits?since=${since.toISOString()}&per_page=100`;
|
|
228
|
+
return author ? `${base}&author=${encodeURIComponent(author)}` : base;
|
|
229
|
+
}
|
|
230
|
+
async function listCommits(token, repo, since, author) {
|
|
214
231
|
const commits = [];
|
|
215
|
-
let url =
|
|
232
|
+
let url = commitsUrl(repo.full_name, since, author);
|
|
216
233
|
let pages = 0;
|
|
217
234
|
while (url && pages < MAX_COMMIT_PAGES) {
|
|
218
235
|
let result;
|
|
@@ -257,33 +274,44 @@ ${fields}
|
|
|
257
274
|
})));
|
|
258
275
|
return result;
|
|
259
276
|
}
|
|
260
|
-
|
|
277
|
+
function issuesUrls(fullName, login) {
|
|
278
|
+
const base = `/repos/${fullName}/issues?state=open&per_page=100`;
|
|
279
|
+
if (!login) return [base];
|
|
280
|
+
const me = encodeURIComponent(login);
|
|
281
|
+
return [`${base}&creator=${me}`, `${base}&assignee=${me}`];
|
|
282
|
+
}
|
|
283
|
+
async function listOpenIssues(token, repo, login) {
|
|
261
284
|
const items = [];
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
let
|
|
266
|
-
|
|
267
|
-
result
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
285
|
+
const seen = /* @__PURE__ */ new Set();
|
|
286
|
+
for (const start of issuesUrls(repo.full_name, login)) {
|
|
287
|
+
let url = start;
|
|
288
|
+
let pages = 0;
|
|
289
|
+
while (url && pages < 5) {
|
|
290
|
+
let result;
|
|
291
|
+
try {
|
|
292
|
+
result = await rest(token, url);
|
|
293
|
+
} catch (err) {
|
|
294
|
+
if (err instanceof Error && err.message.includes("HTTP 409")) return [];
|
|
295
|
+
throw err;
|
|
296
|
+
}
|
|
297
|
+
for (const it of result.json) {
|
|
298
|
+
if (seen.has(it.number)) continue;
|
|
299
|
+
seen.add(it.number);
|
|
300
|
+
items.push({
|
|
301
|
+
repo: repo.full_name,
|
|
302
|
+
type: it.pull_request ? "pr" : "issue",
|
|
303
|
+
number: it.number,
|
|
304
|
+
title: it.title,
|
|
305
|
+
state: it.state,
|
|
306
|
+
labels: it.labels.map((l) => l.name),
|
|
307
|
+
updatedAt: it.updated_at,
|
|
308
|
+
url: it.html_url,
|
|
309
|
+
comments: it.comments
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
url = result.next;
|
|
313
|
+
pages++;
|
|
284
314
|
}
|
|
285
|
-
url = result.next;
|
|
286
|
-
pages++;
|
|
287
315
|
}
|
|
288
316
|
return items;
|
|
289
317
|
}
|
|
@@ -312,8 +340,9 @@ async function fetchActivity(token, from, opts) {
|
|
|
312
340
|
const limit = createLimiter(CONCURRENCY);
|
|
313
341
|
const [login, recentRepos] = await Promise.all([
|
|
314
342
|
fetchLogin(token),
|
|
315
|
-
listRecentRepos(token, from)
|
|
343
|
+
listRecentRepos(token, from, opts.includePrivate, opts.orgs ?? [])
|
|
316
344
|
]);
|
|
345
|
+
const mineOnly = (r) => r.owner.login.toLowerCase() === login.toLowerCase() ? null : login;
|
|
317
346
|
const openCounts = await fetchOpenCounts(token, recentRepos.map((r) => r.full_name), limit);
|
|
318
347
|
const issueRepos = recentRepos.filter((r) => {
|
|
319
348
|
const c = openCounts.get(r.full_name);
|
|
@@ -322,7 +351,7 @@ async function fetchActivity(token, from, opts) {
|
|
|
322
351
|
const progress = createProgress(recentRepos.length, issueRepos.length);
|
|
323
352
|
const commitsPromise = Promise.all(
|
|
324
353
|
recentRepos.map(
|
|
325
|
-
(r) => limit(() => listCommits(token, r, from)).then((commits) => {
|
|
354
|
+
(r) => limit(() => listCommits(token, r, from, mineOnly(r))).then((commits) => {
|
|
326
355
|
progress.repoDone();
|
|
327
356
|
return commits;
|
|
328
357
|
})
|
|
@@ -330,7 +359,7 @@ async function fetchActivity(token, from, opts) {
|
|
|
330
359
|
);
|
|
331
360
|
const issuesPromise = Promise.all(
|
|
332
361
|
issueRepos.map(
|
|
333
|
-
(r) => limit(() => listOpenIssues(token, r)).then((items) => {
|
|
362
|
+
(r) => limit(() => listOpenIssues(token, r, mineOnly(r))).then((items) => {
|
|
334
363
|
progress.issueDone();
|
|
335
364
|
return items;
|
|
336
365
|
})
|
|
@@ -382,9 +411,11 @@ async function runSync(opts) {
|
|
|
382
411
|
const from = new Date(to.getTime() - windowDays * 24 * 60 * 60 * 1e3);
|
|
383
412
|
log(`GitHub RepoRadar sync: ${from.toISOString().slice(0, 10)} 〜 ${to.toISOString().slice(0, 10)} (${windowDays}日)`);
|
|
384
413
|
const token = getToken();
|
|
385
|
-
const
|
|
414
|
+
const orgs = opts.orgs ?? [];
|
|
415
|
+
if (orgs.length > 0) log(`組織も取得: ${orgs.join(", ")}(自分がauthorのコミットだけ数える)`);
|
|
416
|
+
const { login, repos, activity, issues } = await fetchActivity(token, from, { includePrivate: true, orgs });
|
|
386
417
|
mkdirSync(opts.dataDir, { recursive: true });
|
|
387
|
-
const meta = { lastSyncAt: to.toISOString(), login, windowDays, includesPrivate: true };
|
|
418
|
+
const meta = { lastSyncAt: to.toISOString(), login, windowDays, includesPrivate: true, ...orgs.length > 0 ? { orgs } : {} };
|
|
388
419
|
writeFileSync(join(opts.dataDir, "repos.json"), JSON.stringify(repos, null, 2));
|
|
389
420
|
writeFileSync(join(opts.dataDir, "activity.json"), JSON.stringify(activity, null, 2));
|
|
390
421
|
writeFileSync(join(opts.dataDir, "issues.json"), JSON.stringify(issues, null, 2));
|
|
@@ -414,7 +445,8 @@ const DEFAULTS = {
|
|
|
414
445
|
dataDir: null,
|
|
415
446
|
includePrivate: false,
|
|
416
447
|
sort: "active",
|
|
417
|
-
limit: 15
|
|
448
|
+
limit: 15,
|
|
449
|
+
orgs: []
|
|
418
450
|
};
|
|
419
451
|
function intIn(flag, raw, min, max) {
|
|
420
452
|
if (raw === void 0 || raw.startsWith("--")) throw new RangeError(`${flag} には値が要ります`);
|
|
@@ -425,7 +457,7 @@ function intIn(flag, raw, min, max) {
|
|
|
425
457
|
return n;
|
|
426
458
|
}
|
|
427
459
|
function parseArgs(argv) {
|
|
428
|
-
const out = { ...DEFAULTS };
|
|
460
|
+
const out = { ...DEFAULTS, orgs: [] };
|
|
429
461
|
let command = null;
|
|
430
462
|
for (let i = 0; i < argv.length; i++) {
|
|
431
463
|
const a = argv[i];
|
|
@@ -458,6 +490,14 @@ function parseArgs(argv) {
|
|
|
458
490
|
out.dataDir = v;
|
|
459
491
|
break;
|
|
460
492
|
}
|
|
493
|
+
case "--org": {
|
|
494
|
+
const v = argv[++i];
|
|
495
|
+
if (v === void 0 || v.startsWith("--")) throw new RangeError(`${a}には値が要ります`);
|
|
496
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9-]*$/.test(v)) throw new RangeError(`--orgはGitHubの組織名(英数字とハイフン)で指定してください(受け取った値: ${v})`);
|
|
497
|
+
if (out.orgs.includes(v)) throw new RangeError(`--org ${v}が重複しています`);
|
|
498
|
+
out.orgs.push(v);
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
461
501
|
case "--sort": {
|
|
462
502
|
const v = argv[++i];
|
|
463
503
|
if (!SORTS.includes(v)) throw new RangeError(`--sortは${SORTS.join(" | ")} のどれか(受け取った値: ${v})`);
|
|
@@ -477,26 +517,30 @@ function parseArgs(argv) {
|
|
|
477
517
|
}
|
|
478
518
|
return out;
|
|
479
519
|
}
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
npx github-reporadar
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
--
|
|
491
|
-
--
|
|
492
|
-
--
|
|
493
|
-
--
|
|
494
|
-
--
|
|
495
|
-
--
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
520
|
+
const row = (left, right) => ` ${left.padEnd(34)}${right}`;
|
|
521
|
+
const USAGE = [
|
|
522
|
+
"github-reporadar — 自分のGitHub活動を一晩ぶん眺める計器盤",
|
|
523
|
+
"",
|
|
524
|
+
"使い方:",
|
|
525
|
+
row("npx github-reporadar", "同期してからローカルで開く(既定)"),
|
|
526
|
+
row("npx github-reporadar sync", "同期だけ"),
|
|
527
|
+
row("npx github-reporadar brief", "端末に要約を出す"),
|
|
528
|
+
"",
|
|
529
|
+
"フラグ:",
|
|
530
|
+
row("--days N", "取得する期間(1〜365、既定90)。brief は 7 | 30 | 90"),
|
|
531
|
+
row("--port N", "待ち受けポート(既定5177。使用中なら次を探す)"),
|
|
532
|
+
row("--no-open", "ブラウザを開かない"),
|
|
533
|
+
row("--no-sync", "同期せずに手元のデータで開く"),
|
|
534
|
+
row("--org NAME", "組織のrepoも取得する(繰り返し可)。組織では自分がauthorのコミットと、自分が作ったか担当のissue/PRだけ数える"),
|
|
535
|
+
row("--data-dir DIR", "データの置き場(既定: $XDG_CACHE_HOME か ~/.cache の github-reporadar/data)"),
|
|
536
|
+
row("--sort KEY", "brief の並び: active | recent | stale"),
|
|
537
|
+
row("--limit N", "brief の件数(既定15)"),
|
|
538
|
+
row("--private", "brief に private repo も出す"),
|
|
539
|
+
"",
|
|
540
|
+
"トークンは gh CLI(gh auth token)から実行時に取り、どこにも保存しません。",
|
|
541
|
+
"待ち受けは 127.0.0.1 だけです。データには private repo 名とコミット文が含まれます。",
|
|
542
|
+
""
|
|
543
|
+
].join("\n");
|
|
500
544
|
function defaultDataDir(env, home) {
|
|
501
545
|
const base = env.XDG_CACHE_HOME && env.XDG_CACHE_HOME !== "" ? env.XDG_CACHE_HOME : join(home, ".cache");
|
|
502
546
|
return join(base, "github-reporadar", "data");
|
|
@@ -622,7 +666,7 @@ async function main() {
|
|
|
622
666
|
}
|
|
623
667
|
const dataDir = args.dataDir ?? defaultDataDir(process.env, homedir());
|
|
624
668
|
if (args.command === "sync" || args.command === "serve" && args.sync) {
|
|
625
|
-
await runSync({ dataDir, days: args.days });
|
|
669
|
+
await runSync({ dataDir, days: args.days, orgs: args.orgs });
|
|
626
670
|
}
|
|
627
671
|
if (args.command === "sync") return;
|
|
628
672
|
if (args.command === "brief") {
|