diff --git a/__tests__/integration/app.test.js b/__tests__/integration/app.test.js
index 86f7918..b0992dd 100644
--- a/__tests__/integration/app.test.js
+++ b/__tests__/integration/app.test.js
@@ -322,7 +322,7 @@ describe('app', () => {
expect(mockRes.status).toHaveBeenCalledWith(200);
expect(mockRes.json).toHaveBeenCalledWith(
- expect.objectContaining({ status: 'healthy', version: '1.0.0' })
+ expect.objectContaining({ status: 'healthy', version: expect.any(String) })
);
expect(mockRes.json).toHaveBeenCalledWith(
expect.objectContaining({ queue: expect.any(Object) })
@@ -407,7 +407,7 @@ describe('app', () => {
expect(res.writeHead).toHaveBeenCalledWith(200, { 'Content-Type': 'application/json' });
const body = JSON.parse(res.end.mock.calls[0][0]);
expect(body.status).toBe('healthy');
- expect(body.version).toBe('1.0.0');
+ expect(typeof body.version).toBe('string');
expect(body.queue).toBeDefined();
expect(body.timestamp).toBeDefined();
});
diff --git a/__tests__/smoke/server.test.js b/__tests__/smoke/server.test.js
index fd408e9..fad0d32 100644
--- a/__tests__/smoke/server.test.js
+++ b/__tests__/smoke/server.test.js
@@ -121,7 +121,7 @@ describe('server boot', () => {
if (res.status === 200) {
const data = JSON.parse(res.body);
expect(data.status).toBe('healthy');
- expect(data.version).toBe('1.0.0');
+ expect(typeof data.version).toBe('string');
expect(data.queue).toBeDefined();
} else {
// Routes not registered — Probot v14 doesn't always provide getRouter
diff --git a/package.json b/package.json
index c8a4d2c..8a7c3e5 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,6 @@
"provenance": true
},
"pnpm": {
- "onlyBuiltDependencies": []
+ "onlyBuiltDependencies": ["better-sqlite3"]
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 92f202e..05b25e3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,6 +14,9 @@ importers:
'@octokit/rest':
specifier: ^22.0.1
version: 22.0.1
+ better-sqlite3:
+ specifier: ^11.10.0
+ version: 11.10.0
helmet:
specifier: ^8.1.0
version: 8.1.0
@@ -1099,6 +1102,9 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ base64-js@1.5.1:
+ resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+
baseline-browser-mapping@2.9.18:
resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==}
hasBin: true
@@ -1106,10 +1112,19 @@ packages:
before-after-hook@4.0.0:
resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==}
+ better-sqlite3@11.10.0:
+ resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==}
+
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
+ bindings@1.5.0:
+ resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+
+ bl@4.1.0:
+ resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
+
bottleneck@2.19.5:
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
@@ -1131,6 +1146,9 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ buffer@5.7.1:
+ resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@@ -1158,6 +1176,9 @@ packages:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
+ chownr@1.1.4:
+ resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
+
ci-info@3.9.0:
resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
engines: {node: '>=8'}
@@ -1220,6 +1241,10 @@ packages:
supports-color:
optional: true
+ decompress-response@6.0.0:
+ resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
+ engines: {node: '>=10'}
+
dedent@1.7.1:
resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==}
peerDependencies:
@@ -1228,6 +1253,10 @@ packages:
babel-plugin-macros:
optional: true
+ deep-extend@0.6.0:
+ resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
+ engines: {node: '>=4.0.0'}
+
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -1235,6 +1264,10 @@ packages:
resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
engines: {node: '>=0.10.0'}
+ detect-libc@2.1.2:
+ resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
+ engines: {node: '>=8'}
+
detect-newline@3.1.0:
resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
engines: {node: '>=8'}
@@ -1326,6 +1359,10 @@ packages:
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
engines: {node: '>= 0.8.0'}
+ expand-template@2.0.3:
+ resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
+ engines: {node: '>=6'}
+
expect@29.7.0:
resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -1365,6 +1402,9 @@ packages:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0}
+ file-uri-to-path@1.0.0:
+ resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -1388,6 +1428,9 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+ fs-constants@1.0.0:
+ resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
+
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
@@ -1415,6 +1458,9 @@ packages:
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
engines: {node: '>=10'}
+ github-from-package@0.0.0:
+ resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
+
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -1463,6 +1509,9 @@ packages:
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
engines: {node: '>=10.17.0'}
+ ieee754@1.2.1:
+ resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
+
ignore-by-default@1.0.1:
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
@@ -1493,6 +1542,9 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ ini@1.3.8:
+ resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
@@ -1806,18 +1858,32 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
+ mimic-response@3.1.0:
+ resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
+ engines: {node: '>=10'}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ mkdirp-classic@0.5.3:
+ resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ napi-build-utils@2.0.0:
+ resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
+
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ node-abi@3.87.0:
+ resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==}
+ engines: {node: '>=10'}
+
node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
@@ -1944,6 +2010,12 @@ packages:
resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
engines: {node: '>=8'}
+ prebuild-install@7.1.3:
+ resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
+ engines: {node: '>=10'}
+ deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
+ hasBin: true
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -1987,9 +2059,17 @@ packages:
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
+ rc@1.2.8:
+ resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
+ hasBin: true
+
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@@ -2053,6 +2133,9 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
@@ -2080,6 +2163,12 @@ packages:
signal-exit@3.0.7:
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+ simple-concat@1.0.1:
+ resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
+
+ simple-get@4.0.1:
+ resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
+
simple-update-notifier@2.0.0:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
@@ -2120,6 +2209,9 @@ packages:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -2132,6 +2224,10 @@ packages:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
+ strip-json-comments@2.0.1:
+ resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
+ engines: {node: '>=0.10.0'}
+
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -2156,6 +2252,13 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ tar-fs@2.1.4:
+ resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
+
+ tar-stream@2.2.0:
+ resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
+ engines: {node: '>=6'}
+
test-exclude@6.0.0:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'}
@@ -2185,6 +2288,9 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+ tunnel-agent@0.6.0:
+ resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
+
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
@@ -2238,6 +2344,9 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
v8-to-istanbul@9.3.0:
resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==}
engines: {node: '>=10.12.0'}
@@ -3701,12 +3810,29 @@ snapshots:
balanced-match@1.0.2: {}
+ base64-js@1.5.1: {}
+
baseline-browser-mapping@2.9.18: {}
before-after-hook@4.0.0: {}
+ better-sqlite3@11.10.0:
+ dependencies:
+ bindings: 1.5.0
+ prebuild-install: 7.1.3
+
binary-extensions@2.3.0: {}
+ bindings@1.5.0:
+ dependencies:
+ file-uri-to-path: 1.0.0
+
+ bl@4.1.0:
+ dependencies:
+ buffer: 5.7.1
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
bottleneck@2.19.5: {}
brace-expansion@1.1.12:
@@ -3732,6 +3858,11 @@ snapshots:
buffer-from@1.1.2: {}
+ buffer@5.7.1:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
callsites@3.1.0: {}
camelcase@5.3.1: {}
@@ -3759,6 +3890,8 @@ snapshots:
optionalDependencies:
fsevents: 2.3.3
+ chownr@1.1.4: {}
+
ci-info@3.9.0: {}
ci-info@4.3.1: {}
@@ -3820,12 +3953,20 @@ snapshots:
optionalDependencies:
supports-color: 5.5.0
+ decompress-response@6.0.0:
+ dependencies:
+ mimic-response: 3.1.0
+
dedent@1.7.1: {}
+ deep-extend@0.6.0: {}
+
deep-is@0.1.4: {}
deepmerge@4.3.1: {}
+ detect-libc@2.1.2: {}
+
detect-newline@3.1.0: {}
diff-sequences@29.6.3: {}
@@ -3940,6 +4081,8 @@ snapshots:
exit@0.1.2: {}
+ expand-template@2.0.3: {}
+
expect@29.7.0:
dependencies:
'@jest/expect-utils': 29.7.0
@@ -3983,6 +4126,8 @@ snapshots:
dependencies:
flat-cache: 3.2.0
+ file-uri-to-path@1.0.0: {}
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -4007,6 +4152,8 @@ snapshots:
flatted@3.3.3: {}
+ fs-constants@1.0.0: {}
+
fs.realpath@1.0.0: {}
fsevents@2.3.3:
@@ -4022,6 +4169,8 @@ snapshots:
get-stream@6.0.1: {}
+ github-from-package@0.0.0: {}
+
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -4063,6 +4212,8 @@ snapshots:
human-signals@2.1.0: {}
+ ieee754@1.2.1: {}
+
ignore-by-default@1.0.1: {}
ignore@5.3.2: {}
@@ -4088,6 +4239,8 @@ snapshots:
inherits@2.0.4: {}
+ ini@1.3.8: {}
+
is-arrayish@0.2.1: {}
is-binary-path@2.1.0:
@@ -4583,16 +4736,26 @@ snapshots:
mimic-fn@2.1.0: {}
+ mimic-response@3.1.0: {}
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
minimist@1.2.8: {}
+ mkdirp-classic@0.5.3: {}
+
ms@2.1.3: {}
+ napi-build-utils@2.0.0: {}
+
natural-compare@1.4.0: {}
+ node-abi@3.87.0:
+ dependencies:
+ semver: 7.7.4
+
node-int64@0.4.0: {}
node-releases@2.0.27: {}
@@ -4742,6 +4905,21 @@ snapshots:
dependencies:
find-up: 4.1.0
+ prebuild-install@7.1.3:
+ dependencies:
+ detect-libc: 2.1.2
+ expand-template: 2.0.3
+ github-from-package: 0.0.0
+ minimist: 1.2.8
+ mkdirp-classic: 0.5.3
+ napi-build-utils: 2.0.0
+ node-abi: 3.87.0
+ pump: 3.0.3
+ rc: 1.2.8
+ simple-get: 4.0.1
+ tar-fs: 2.1.4
+ tunnel-agent: 0.6.0
+
prelude-ls@1.2.1: {}
pretty-format@29.7.0:
@@ -4804,8 +4982,21 @@ snapshots:
quick-format-unescaped@4.0.4: {}
+ rc@1.2.8:
+ dependencies:
+ deep-extend: 0.6.0
+ ini: 1.3.8
+ minimist: 1.2.8
+ strip-json-comments: 2.0.1
+
react-is@18.3.1: {}
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
@@ -4861,6 +5052,8 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
+ safe-buffer@5.2.1: {}
+
safe-stable-stringify@2.5.0: {}
secure-json-parse@4.1.0: {}
@@ -4877,6 +5070,14 @@ snapshots:
signal-exit@3.0.7: {}
+ simple-concat@1.0.1: {}
+
+ simple-get@4.0.1:
+ dependencies:
+ decompress-response: 6.0.0
+ once: 1.4.0
+ simple-concat: 1.0.1
+
simple-update-notifier@2.0.0:
dependencies:
semver: 7.7.4
@@ -4915,6 +5116,10 @@ snapshots:
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -4923,6 +5128,8 @@ snapshots:
strip-final-newline@2.0.0: {}
+ strip-json-comments@2.0.1: {}
+
strip-json-comments@3.1.1: {}
strip-json-comments@5.0.3: {}
@@ -4941,6 +5148,21 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ tar-fs@2.1.4:
+ dependencies:
+ chownr: 1.1.4
+ mkdirp-classic: 0.5.3
+ pump: 3.0.3
+ tar-stream: 2.2.0
+
+ tar-stream@2.2.0:
+ dependencies:
+ bl: 4.1.0
+ end-of-stream: 1.4.5
+ fs-constants: 1.0.0
+ inherits: 2.0.4
+ readable-stream: 3.6.2
+
test-exclude@6.0.0:
dependencies:
'@istanbuljs/schema': 0.1.3
@@ -4965,6 +5187,10 @@ snapshots:
tslib@2.8.1: {}
+ tunnel-agent@0.6.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
type-check@0.4.0:
dependencies:
prelude-ls: 1.2.1
@@ -5004,6 +5230,8 @@ snapshots:
dependencies:
punycode: 2.3.1
+ util-deprecate@1.0.2: {}
+
v8-to-istanbul@9.3.0:
dependencies:
'@jridgewell/trace-mapping': 0.3.31
diff --git a/src/BUILD.bazel b/src/BUILD.bazel
index b299c79..31556d3 100644
--- a/src/BUILD.bazel
+++ b/src/BUILD.bazel
@@ -10,6 +10,7 @@ js_library(
"//:node_modules/js-yaml",
"//:node_modules/helmet",
"//:node_modules/pino",
+ "//:node_modules/better-sqlite3",
],
visibility = ["//visibility:public"],
)
diff --git a/src/app.js b/src/app.js
index dba9d43..69e4757 100644
--- a/src/app.js
+++ b/src/app.js
@@ -1,6 +1,6 @@
import path from 'path';
import { fileURLToPath } from 'url';
-import { createDashboardHandler } from './dashboard.js';
+import { createDashboardHandler, DEPLOY_SHA } from './dashboard.js';
import { getConfig } from './config.js';
import { getLogger, setLogger } from './logger.js';
import { configureRepository } from './repository.js';
@@ -51,7 +51,7 @@ function createCustomRoutesHandler() {
const healthData = {
status: 'healthy',
timestamp: new Date().toISOString(),
- version: '1.0.0',
+ version: DEPLOY_SHA || '1.0.0',
queue: defaultQueue.stats()
};
if (_taskStore) {
@@ -617,7 +617,7 @@ function registerApp(app, { getRouter, addHandler } = {}) {
const healthData = {
status: 'healthy',
timestamp: new Date().toISOString(),
- version: '1.0.0',
+ version: DEPLOY_SHA || '1.0.0',
queue: defaultQueue.stats()
};
if (_taskStore) {
diff --git a/src/dashboard.js b/src/dashboard.js
index cc760fb..1f8e14d 100644
--- a/src/dashboard.js
+++ b/src/dashboard.js
@@ -1,3 +1,4 @@
+import { execFileSync } from 'child_process';
import { getConfig } from './config.js';
import { getLogger } from './logger.js';
import { analyzeOrganizationRepositories, synchronizeAllRepositories, generateOrganizationAnalysisReport } from './organization.js';
@@ -406,6 +407,113 @@ function renderReviewsPartial() {
return rows;
}
+// ── Deployment partial ───────────────────────────────────────────────
+
+async function renderDeploymentPartial() {
+ const config = getConfig();
+ const org = config?.organization;
+
+ // Local deploy info always renders — no API needed
+ const startedAgo = timeAgo(DEPLOY_TIME);
+ let html = '
' +
+ '
Deployed SHA
' +
+ '
' + esc(DEPLOY_SHA) + '
' +
+ '
started ' + esc(startedAgo) + ' ago
';
+
+ // API-dependent section
+ let mainSha = null;
+ let behindBy = 0;
+ let branches = [];
+ try {
+ const octokit = await getOctokit();
+ const selfRepo = config?.selfRepoName || 'temper';
+
+ // Get main branch HEAD
+ const { data: mainBranch } = await octokit.repos.getBranch({ owner: org, repo: selfRepo, branch: 'main' });
+ mainSha = mainBranch.commit.sha.slice(0, 7);
+ const mainShaFull = mainBranch.commit.sha;
+
+ // Compare to find drift
+ let compareData = null;
+ if (DEPLOY_SHA_FULL !== 'unknown') {
+ try {
+ const { data } = await octokit.repos.compareCommits({ owner: org, repo: selfRepo, base: DEPLOY_SHA_FULL, head: mainShaFull });
+ compareData = data;
+ behindBy = data.ahead_by || 0;
+ } catch (cmpErr) {
+ if (cmpErr.status === 404) {
+ compareData = { error: 'Deployed SHA not found in repo history (force-pushed?)' };
+ } else { throw cmpErr; }
+ }
+ }
+
+ // Main HEAD card
+ const driftBadge = behindBy > 0 ? badge('warn', behindBy + ' behind') : badge('ok', 'up to date');
+ html += '
Main HEAD
' +
+ '
' + esc(mainSha) + '
' +
+ '
' + driftBadge + '
';
+
+ // Branch count
+ const { data: branchList } = await octokit.repos.listBranches({ owner: org, repo: selfRepo, per_page: 100 });
+ branches = branchList;
+ html += '
Branches
' +
+ '
' + branches.length + '
' +
+ '
remote
';
+
+ html += '
'; // close stat-grid
+
+ // Commits table (when behind)
+ if (compareData && !compareData.error && behindBy > 0) {
+ const commits = (compareData.commits || []).slice(-10).reverse();
+ html += '' +
+ '' +
+ '| SHA | Message | Author | Age | ' +
+ '
';
+ for (const c of commits) {
+ const sha7 = c.sha.slice(0, 7);
+ const msg = (c.commit.message || '').split('\n')[0];
+ const author = c.commit.author?.name || c.author?.login || '';
+ const age = timeAgo(c.commit.author?.date);
+ html += '' +
+ '| ' + esc(sha7) + ' | ' +
+ '' + esc(msg.length > 72 ? msg.slice(0, 72) + '...' : msg) + ' | ' +
+ '' + esc(author) + ' | ' +
+ '' + esc(age) + ' | ' +
+ '
';
+ }
+ html += '
';
+ } else if (compareData && compareData.error) {
+ html += '' + esc(compareData.error) + '
';
+ }
+
+ // Branch list table
+ if (branches.length > 0) {
+ html += '' +
+ '' +
+ '| Branch | Last Commit | Info | ' +
+ '
';
+ for (const b of branches) {
+ const sha7 = b.commit.sha.slice(0, 7);
+ const badges = [];
+ if (b.name === 'main') badges.push(badge('info', 'default'));
+ if (b.commit.sha === DEPLOY_SHA_FULL) badges.push(badge('ok', 'deployed'));
+ html += '' +
+ '| ' + esc(b.name) + ' | ' +
+ '' + esc(sha7) + ' | ' +
+ '' + (badges.length ? badges.join(' ') : '') + ' | ' +
+ '
';
+ }
+ html += '
';
+ }
+ } catch (err) {
+ html += ''; // close stat-grid in case API failed mid-render
+ getLogger().warn({ err }, 'Deployment partial API error');
+ html += 'Could not fetch remote info: ' + esc(err.message) + '
';
+ }
+
+ return html;
+}
+
// ── Full reviews partial (for dedicated Reviews tab) ─────────────────
function reviewAssessmentBadge(a) {
@@ -571,6 +679,11 @@ const CLIENT_JS = readFileSync(join(dashModuleDir, 'dashboard-client.js'), 'utf8
const HTMX_JS = readFileSync(join(dashModuleDir, 'htmx.min.js'), 'utf8');
const IDIOMORPH_JS = readFileSync(join(dashModuleDir, 'idiomorph-ext.min.js'), 'utf8');
+// ── Deploy info (captured once at startup) ──────────────────────────
+export const DEPLOY_SHA = (() => { try { return execFileSync('git', ['rev-parse', '--short', 'HEAD'], { cwd: dashModuleDir, encoding: 'utf8' }).trim(); } catch { return 'unknown'; } })();
+const DEPLOY_SHA_FULL = (() => { try { return execFileSync('git', ['rev-parse', 'HEAD'], { cwd: dashModuleDir, encoding: 'utf8' }).trim(); } catch { return 'unknown'; } })();
+const DEPLOY_TIME = new Date().toISOString();
+
// ── CSS ──────────────────────────────────────────────────────────────
const CSS = `
*{margin:0;padding:0;box-sizing:border-box}
@@ -1067,6 +1180,10 @@ function renderDashboardPage() {
'' +
+ '' +
+ '' +
'' +
'' +
'
' +
@@ -1231,6 +1348,11 @@ export function createDashboardHandler() {
catch (err) { getLogger().error({err},'Dashboard issues error'); sendHtml(res, 200, renderError(err)); }
return true;
}
+ if (req.method === 'GET' && path === '/dashboard/partials/deployment') {
+ try { sendHtml(res, 200, await renderDeploymentPartial()); }
+ catch (err) { getLogger().error({err},'Dashboard deployment error'); sendHtml(res, 200, renderError(err)); }
+ return true;
+ }
// AI Reviews partial (compact, for Signals panel if still used)
if (req.method === 'GET' && path === '/dashboard/partials/reviews') {