← back to build-pages
api: /api/projects/:slug/commits — paginated raw JSON (limit + offset) + 4 smoke
254d1d066ce39da5f45496dc48a9f0c3d8b590b2 · 2026-05-13 13:34:44 -0700 · SteveStudio2
Files touched
M server.jsM test/smoke.js
Diff
commit 254d1d066ce39da5f45496dc48a9f0c3d8b590b2
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 13 13:34:44 2026 -0700
api: /api/projects/:slug/commits — paginated raw JSON (limit + offset) + 4 smoke
---
server.js | 21 +++++++++++++++++++++
test/smoke.js | 7 +++++++
2 files changed, 28 insertions(+)
diff --git a/server.js b/server.js
index 9b68cf1..412d46a 100644
--- a/server.js
+++ b/server.js
@@ -434,6 +434,27 @@ app.get('/p/:slug/c/:hash', async (req, res, next) => {
} catch (e) { next(e); }
});
+// Raw commits JSON — paginated. Supports ?limit=N (default 100, max 500) and
+// ?offset=N. The bundle has the full list cached; this just slices it.
+app.get('/api/projects/:slug/commits', async (req, res, next) => {
+ const p = PROJECTS[req.params.slug];
+ if (!p) return res.status(404).json({ error: 'no-such-project' });
+ try {
+ const { commits } = await getProjectBundle(req.params.slug, p);
+ const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 100, 1), 500);
+ const offset = Math.max(parseInt(req.query.offset, 10) || 0, 0);
+ res.setHeader('Cache-Control', 'public, max-age=30, stale-while-revalidate=60');
+ res.json({
+ slug: req.params.slug,
+ total: commits.length,
+ limit, offset,
+ commits: commits.slice(offset, offset + limit).map(c => ({
+ hash: c.shortHash, date: c.dateISO, author: c.author, subject: c.subject, body: c.body,
+ })),
+ });
+ } catch (e) { next(e); }
+});
+
// Atom feed per project — recent N commits in standards-compliant Atom 1.0.
// Lets external tools subscribe to a project's build journal (e.g. Feedly,
// NetNewsWire). Pulls from the same bundle cache as the HTML page.
diff --git a/test/smoke.js b/test/smoke.js
index 11a5787..708d657 100644
--- a/test/smoke.js
+++ b/test/smoke.js
@@ -133,6 +133,13 @@ function check(name, cond, hint = '') {
r = await fetch('/p/butlr');
check('butlr: feed autodiscover',/rel="alternate" type="application\/atom\+xml"/.test(r.body));
+ // 12. /api/projects/:slug/commits — paginated raw JSON
+ r = await fetch('/api/projects/butlr/commits?limit=3');
+ check('commits api: 200', r.status === 200);
+ check('commits api: total', /"total":\d+/.test(r.body));
+ check('commits api: 3 returned', /"limit":3/.test(r.body));
+ check('commits api: hash field', /"hash":"[a-f0-9]{4,}/.test(r.body));
+
// Report
if (process.argv.includes('--json')) {
console.log(JSON.stringify({ base: BASE, pass, fail, total: pass + fail, checks, as_of: new Date().toISOString() }));
← c196cfd feed: Atom 1.0 per project (/p/:slug/feed.atom) + autodiscov
·
back to build-pages
·
404: branded page on unknown routes (fleet parity) + 3 smoke 0158ff5 →