megit-app 0.11.0 → 0.11.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/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <title>megit</title>
8
- <script type="module" crossorigin src="/assets/index-JAmzg-th.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-_ttj49Ya.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-BXzSxW-2.css">
10
10
  </head>
11
11
  <body>
@@ -1,7 +1,18 @@
1
- import { execFile } from 'node:child_process';
2
1
  import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
3
2
  import { homedir } from 'node:os';
4
3
  import { join } from 'node:path';
4
+ // This file holds every outbound network call the server makes, and every
5
+ // external host it knows about:
6
+ //
7
+ // api.github.com — commit -> GitHub account lookup (resolveAvatar)
8
+ // avatars.githubusercontent.com — the photo itself, fetched by the browser
9
+ // github.com/<user>.png — same, for legacy noreply emails
10
+ //
11
+ // All three are literals, never assembled from remote input, and nothing here
12
+ // runs unless author avatars are on in Settings. Requests are anonymous: no
13
+ // token is read, sent or stored, so GitHub's 60 req/h unauthenticated limit
14
+ // applies — the on-disk cache below makes that one request per author, ever.
15
+ // See SECURITY.md, "Threat model".
5
16
  // GitHub noreply emails encode the account: "12345+user@users.noreply.github.com"
6
17
  // (current form) or legacy "user@users.noreply.github.com".
7
18
  export function noreplyAvatar(email) {
@@ -12,7 +23,8 @@ export function noreplyAvatar(email) {
12
23
  ? `https://avatars.githubusercontent.com/u/${m[1]}?s=48`
13
24
  : `https://github.com/${m[2]}.png?size=48`;
14
25
  }
15
- // git@github.com:owner/repo.git | https://github.com/owner/repo(.git)
26
+ // owner/repo out of either remote spelling — ssh (git@host:owner/repo.git) or
27
+ // https (host/owner/repo, with or without the .git suffix)
16
28
  export function parseGithubRemote(url) {
17
29
  const m = url.trim().match(/github\.com[:/]([^/]+)\/([^/\s]+?)(?:\.git)?$/);
18
30
  return m ? { owner: m[1], repo: m[2] } : null;
@@ -34,9 +46,6 @@ function saveCache() {
34
46
  mkdirSync(join(homedir(), '.config', 'megit'), { recursive: true });
35
47
  writeFileSync(cacheFile, JSON.stringify(cache, null, 2));
36
48
  }
37
- // gh CLI token when the user is logged in — lifts the API limit from 60/h to 5000/h
38
- let tokenP = null;
39
- const ghToken = () => (tokenP ??= new Promise(r => execFile('gh', ['auth', 'token'], (e, out) => r(e ? null : out.trim() || null))));
40
49
  // email -> GitHub profile avatar. Strategy: noreply emails resolve locally; anything
41
50
  // else looks up one commit by that author and asks the GitHub commits API which
42
51
  // account it belongs to. Definitive answers (incl. "no account") persist to disk;
@@ -56,17 +65,16 @@ export async function resolveAvatar(repo, email, git) {
56
65
  const rx = email.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
57
66
  const sha = (await git(repo, ['log', '--all', '-1', `--author=${rx}`, '--format=%H'])).trim();
58
67
  if (sha) {
59
- const headers = { 'User-Agent': 'megit', Accept: 'application/vnd.github+json' };
60
- const token = await ghToken();
61
- if (token)
62
- headers.Authorization = `Bearer ${token}`;
63
- const resp = await fetch(`https://api.github.com/repos/${remote.owner}/${remote.repo}/commits/${sha}`, { headers });
68
+ const resp = await fetch(`https://api.github.com/repos/${remote.owner}/${remote.repo}/commits/${sha}`, {
69
+ headers: { 'User-Agent': 'megit', Accept: 'application/vnd.github+json' },
70
+ });
64
71
  if (resp.ok) {
65
72
  const data = (await resp.json());
66
73
  const raw = data.author?.avatar_url;
67
74
  url = raw ? raw + (raw.includes('?') ? '&' : '?') + 's=48' : null;
68
75
  }
69
76
  else if (resp.status !== 404 && resp.status !== 422) {
77
+ // 403/429 = rate limited: leave it uncached so a later run retries
70
78
  definitive = false;
71
79
  }
72
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "megit-app",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Git repository viewer in the browser: commit graph with branch lanes, diffs, stashes and WIP",
5
5
  "license": "MIT",
6
6
  "author": "Hoang Vuong Vu",
@@ -25,7 +25,8 @@
25
25
  "bin",
26
26
  "dist",
27
27
  "dist-server",
28
- "CHANGELOG.md"
28
+ "CHANGELOG.md",
29
+ "SECURITY.md"
29
30
  ],
30
31
  "engines": {
31
32
  "node": ">=22"