Files
trace/src/gitea.mjs
T
Fredrik JohanssonandClaude Sonnet 5 4b7b5a42d3 Reformat src/*.mjs with Prettier; add format/format:check scripts
Every route handler was written as one dense semicolon-chained line
(server.mjs's main handler was over 2000 characters wide) — real
width, not a VSCode wrapping artifact. Ran Prettier to break statement
structure into properly indented multi-line code; left template
literal string contents untouched since Prettier doesn't reformat
inside those, so the large HTML-generating return strings are still
single long lines by nature, just no longer compounded by unformatted
surrounding JS. Verified: tests pass, node --check passes, and the
app renders identically before/after (screenshotted the diff).

No devDependency added — npx prettier@3.9.5 pinned in the new scripts
keeps the zero-runtime-dependency pledge intact; formatting is a dev-
time concern only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 05:17:30 +02:00

72 lines
2.6 KiB
JavaScript

const FIX_WORDS =
/\b(fix(?:e[ds])?|bug|broken|race|crash|regression|rollback|restore[ds]?|duplicate[ds]?|auth|fail(?:ed|ure|ing)?)\b/i;
const validRepo = (value) => /^[a-z0-9_.-]+\/[a-z0-9_.-]+$/i.test(value);
export async function scanGiteaCandidates({
baseUrl,
repositories,
token = '',
limit = 100,
fetchImpl = fetch,
}) {
const candidates = [],
errors = [],
headers = { accept: 'application/json' };
if (token) headers.authorization = `token ${token}`;
for (const repo of repositories) {
if (!validRepo(repo)) {
errors.push({ repo, error: 'invalid owner/repository identifier' });
continue;
}
const [owner, name] = repo.split('/');
let seen = 0;
try {
for (let page = 1; seen < limit; page++) {
const count = Math.min(50, limit - seen),
url = `${baseUrl.replace(/\/$/, '')}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits?limit=${count}&page=${page}`;
const response = await fetchImpl(url, { headers, signal: AbortSignal.timeout(5000) });
if (!response.ok) throw new Error(`Gitea returned ${response.status}`);
const commits = await response.json();
if (!Array.isArray(commits)) throw new Error('unexpected Gitea response');
for (const commit of commits) {
seen++;
const subject = String(commit.commit?.message || '')
.split('\n')[0]
.trim(),
match = subject.match(FIX_WORDS);
if (!match) continue;
let paths = '';
try {
const detail = await fetchImpl(
`${baseUrl.replace(/\/$/, '')}/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/git/commits/${encodeURIComponent(commit.sha)}`,
{ headers, signal: AbortSignal.timeout(5000) },
);
if (detail.ok) {
const data = await detail.json();
paths = (data.files || [])
.map((file) => file.filename)
.filter(Boolean)
.join('\n');
}
} catch {}
candidates.push({
repo,
sha: commit.sha,
committedAt:
commit.commit?.committer?.date ||
commit.commit?.author?.date ||
commit.created ||
new Date().toISOString(),
subject,
paths,
reason: `Gitea subject keyword: ${match[0].toLowerCase()}`,
});
}
if (commits.length < count) break;
}
} catch (error) {
errors.push({ repo, error: error.message });
}
}
return { candidates, errors };
}