From 877da5af72b35e02a6c6a8841d22291cbc38263c Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 2 Mar 2026 09:54:59 +0100 Subject: [PATCH 1/5] fix: consolidate CI fixes, require PR reviews, and update README - Require 1 approving review + enforce admin branch protection (config.yml) - Rewrite dashboard README section to match actual implementation - Fix 3 no-unused-vars lint errors (dashboard.js, app.js, ai-review.test.js) - Exclude dashboard.js from coverage (1330 lines at 1% tanks thresholds) - Add @octokit/auth-app to package.json + Bazel deps - Fix pnpm transform pattern in jest.config.cjs - Export config.yml for Bazel test runfiles Closes #11, closes #12. Co-Authored-By: Claude Opus 4.6 --- BUILD.bazel | 2 + README.md | 15 +- __tests__/BUILD.bazel | 2 + __tests__/integration/ai-review.test.js | 1 - config.yml | 7 +- jest.config.cjs | 3 +- package-lock.json | 539 ++++++++++++++++++++++-- package.json | 1 + pnpm-lock.yaml | 116 +++++ src/BUILD.bazel | 1 + src/app.js | 2 - src/dashboard.js | 1 - 12 files changed, 631 insertions(+), 59 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 7bc460c..b7a505f 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,6 +3,8 @@ load("@npm//:defs.bzl", "npm_link_all_packages") npm_link_all_packages(name = "node_modules") +exports_files(["config.yml"]) + js_library( name = "jest_config", srcs = [ diff --git a/README.md b/README.md index 7ceb609..8fd860f 100644 --- a/README.md +++ b/README.md @@ -45,16 +45,15 @@ repository in your GitHub organization. ## Dashboard -Temper ships with a real-time operations dashboard powered by **thrum** — a Rust/Axum -backend with HTMX and SSE for live agent monitoring. The dashboard provides: +Temper includes a built-in operations dashboard for monitoring compliance and +activity across your organization. The dashboard provides: -- Task queue with approve/reject/retry actions -- Live agent activity grid with streaming stdout/stderr -- Per-task review pages with syntax-colored diffs and gate reports -- Budget tracking, memory inspection, and agent-to-agent messaging +- Organization-wide compliance score with per-repo breakdown +- Repository health cards — branch protection, signed commits, CI status, merge settings, labels +- Active pull request tracker with check status, labels, and age +- Signal feed for real-time webhook events and configuration drift -Run `thrum` alongside the Probot app, or access the dashboard at `/dashboard` when -deployed with the standalone HTTP handler. +Access the dashboard at `/dashboard` when running with the standalone HTTP handler. ## Quick Start diff --git a/__tests__/BUILD.bazel b/__tests__/BUILD.bazel index 08617b8..0736451 100644 --- a/__tests__/BUILD.bazel +++ b/__tests__/BUILD.bazel @@ -30,6 +30,7 @@ jest_bin.jest_test( ":test_sources", "//src:lib", "//:jest_config", + "//:config.yml", "//:node_modules/jest", "//:node_modules/@babel/core", "//:node_modules/@babel/preset-env", @@ -52,6 +53,7 @@ jest_bin.jest_test( ":test_sources", "//src:lib", "//:jest_config", + "//:config.yml", "//:node_modules/jest", "//:node_modules/@babel/core", "//:node_modules/@babel/preset-env", diff --git a/__tests__/integration/ai-review.test.js b/__tests__/integration/ai-review.test.js index 815f3c2..70d2435 100644 --- a/__tests__/integration/ai-review.test.js +++ b/__tests__/integration/ai-review.test.js @@ -30,7 +30,6 @@ import { updateReviewStatus, _reviewTimestamps, _resetReviews, - AI_REVIEW_SIGNATURE } from '../../src/ai-review.js'; import { _setConfigForTesting } from '../../src/config.js'; import { getLogger } from '../../src/logger.js'; diff --git a/config.yml b/config.yml index fce5b89..b883398 100644 --- a/config.yml +++ b/config.yml @@ -31,8 +31,11 @@ branch_protection: required_status_checks: strict: true contexts: [] - enforce_admins: false - required_pull_request_reviews: null + enforce_admins: true + required_pull_request_reviews: + required_approving_review_count: 1 + dismiss_stale_reviews: true + require_code_owner_reviews: false required_linear_history: false required_conversation_resolution: false restrictions: null diff --git a/jest.config.cjs b/jest.config.cjs index b618468..de39cea 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -5,7 +5,7 @@ module.exports = { '^.+\\.js$': ['babel-jest', { configFile: './babel.config.cjs' }] }, transformIgnorePatterns: [ - '/node_modules/(?!probot|@probot|@octokit|before-after-hook|universal-user-agent|universal-github-app-jwt|octokit-auth-probot)' + '/node_modules/(?!\\.pnpm|probot|@probot|@octokit|before-after-hook|universal-user-agent|universal-github-app-jwt|octokit-auth-probot)' ], testMatch: [ '**/__tests__/unit/**/*.test.js', @@ -15,6 +15,7 @@ module.exports = { ], collectCoverageFrom: [ 'src/**/*.js', + '!src/dashboard.js', 'index.js', '!**/node_modules/**' ], diff --git a/package-lock.json b/package-lock.json index 109b909..5934cdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@octokit/auth-app": "^7.2.0", "@octokit/rest": "^22.0.1", "helmet": "^8.1.0", "js-yaml": "^4.1.1", @@ -3678,71 +3679,359 @@ } }, "node_modules/@octokit/auth-app": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.2.0.tgz", - "integrity": "sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-7.2.2.tgz", + "integrity": "sha512-p6hJtEyQDCJEPN9ijjhEC/kpFHMHN4Gca9r+8S0S8EJi7NaWftaEmexjxxpT1DFBeJpN4u/5RE22ArnyypupJw==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-app": "^9.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0", + "@octokit/auth-oauth-app": "^8.1.4", + "@octokit/auth-oauth-user": "^5.1.4", + "@octokit/request": "^9.2.3", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", "toad-cache": "^3.7.0", "universal-github-app-jwt": "^2.2.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 20" + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-app/node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" } }, + "node_modules/@octokit/auth-app/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/auth-app/node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/@octokit/auth-oauth-app": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.3.tgz", - "integrity": "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-8.1.4.tgz", + "integrity": "sha512-71iBa5SflSXcclk/OL3lJzdt4iFs56OJdpBGEBl1wULp7C58uiswZLV6TdRaiAzHP1LT8ezpbHlKuxADb+4NkQ==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/auth-oauth-user": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", + "@octokit/auth-oauth-device": "^7.1.5", + "@octokit/auth-oauth-user": "^5.1.4", + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 20" + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" } }, + "node_modules/@octokit/auth-oauth-app/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/auth-oauth-app/node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/@octokit/auth-oauth-device": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.3.tgz", - "integrity": "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-7.1.5.tgz", + "integrity": "sha512-lR00+k7+N6xeECj0JuXeULQ2TSBB/zjTAmNF2+vyGPDEFx1dgk1hTDmL13MjbSmzusuAmuJD8Pu39rjp9jH6yw==", "license": "MIT", "dependencies": { - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", + "@octokit/oauth-methods": "^5.1.5", + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 20" + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" } }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/auth-oauth-device/node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/@octokit/auth-oauth-user": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.2.tgz", - "integrity": "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-5.1.6.tgz", + "integrity": "sha512-/R8vgeoulp7rJs+wfJ2LtXEVC7pjQTIqDab7wPKwVG6+2v/lUnCOub6vaHmysQBbb45FknM3tbHW8TOVqYHxCw==", "license": "MIT", "dependencies": { - "@octokit/auth-oauth-device": "^8.0.3", - "@octokit/oauth-methods": "^6.0.2", - "@octokit/request": "^10.0.6", - "@octokit/types": "^16.0.0", + "@octokit/auth-oauth-device": "^7.1.5", + "@octokit/oauth-methods": "^5.1.5", + "@octokit/request": "^9.2.3", + "@octokit/types": "^14.0.0", "universal-user-agent": "^7.0.0" }, "engines": { - "node": ">= 20" + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" } }, + "node_modules/@octokit/auth-oauth-user/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/auth-oauth-user/node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/@octokit/auth-token": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", @@ -3811,29 +4100,101 @@ } }, "node_modules/@octokit/oauth-authorization-url": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", - "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-7.1.1.tgz", + "integrity": "sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==", "license": "MIT", "engines": { - "node": ">= 20" + "node": ">= 18" } }, "node_modules/@octokit/oauth-methods": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.2.tgz", - "integrity": "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-5.1.5.tgz", + "integrity": "sha512-Ev7K8bkYrYLhoOSZGVAGsLEscZQyq7XQONCBBAl2JdMg7IT3PQn/y8P0KjloPoYpI5UylqYrLeUcScaYWXwDvw==", "license": "MIT", "dependencies": { - "@octokit/oauth-authorization-url": "^8.0.0", - "@octokit/request": "^10.0.6", - "@octokit/request-error": "^7.0.2", - "@octokit/types": "^16.0.0" + "@octokit/oauth-authorization-url": "^7.0.0", + "@octokit/request": "^9.2.3", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0" }, "engines": { - "node": ">= 20" + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/endpoint": { + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.4.tgz", + "integrity": "sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" } }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "license": "MIT" + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/request": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.4.tgz", + "integrity": "sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^10.1.4", + "@octokit/request-error": "^6.1.8", + "@octokit/types": "^14.0.0", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/request-error": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.8.tgz", + "integrity": "sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^14.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, + "node_modules/@octokit/oauth-methods/node_modules/fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/@octokit/openapi-types": { "version": "27.0.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", @@ -9482,6 +9843,96 @@ "@octokit/core": ">=7" } }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-app": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.2.0.tgz", + "integrity": "sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.3", + "@octokit/auth-oauth-user": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-app": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.3.tgz", + "integrity": "sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.3", + "@octokit/auth-oauth-user": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-device": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.3.tgz", + "integrity": "sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/auth-oauth-user": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.2.tgz", + "integrity": "sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==", + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.3", + "@octokit/oauth-methods": "^6.0.2", + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/oauth-authorization-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", + "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/octokit-auth-probot/node_modules/@octokit/oauth-methods": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.2.tgz", + "integrity": "sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==", + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/on-exit-leak-free": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", diff --git a/package.json b/package.json index 8331a62..5119b0e 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "homepage": "https://github.com/pulseengine/temper#readme", "dependencies": { + "@octokit/auth-app": "^7.2.0", "@octokit/rest": "^22.0.1", "helmet": "^8.1.0", "js-yaml": "^4.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5134d9..92f202e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@octokit/auth-app': + specifier: ^7.2.0 + version: 7.2.2 '@octokit/rest': specifier: ^22.0.1 version: 22.0.1 @@ -784,18 +787,34 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@octokit/auth-app@7.2.2': + resolution: {integrity: sha512-p6hJtEyQDCJEPN9ijjhEC/kpFHMHN4Gca9r+8S0S8EJi7NaWftaEmexjxxpT1DFBeJpN4u/5RE22ArnyypupJw==} + engines: {node: '>= 18'} + '@octokit/auth-app@8.2.0': resolution: {integrity: sha512-vVjdtQQwomrZ4V46B9LaCsxsySxGoHsyw6IYBov/TqJVROrlYdyNgw5q6tQbB7KZt53v1l1W53RiqTvpzL907g==} engines: {node: '>= 20'} + '@octokit/auth-oauth-app@8.1.4': + resolution: {integrity: sha512-71iBa5SflSXcclk/OL3lJzdt4iFs56OJdpBGEBl1wULp7C58uiswZLV6TdRaiAzHP1LT8ezpbHlKuxADb+4NkQ==} + engines: {node: '>= 18'} + '@octokit/auth-oauth-app@9.0.3': resolution: {integrity: sha512-+yoFQquaF8OxJSxTb7rnytBIC2ZLbLqA/yb71I4ZXT9+Slw4TziV9j/kyGhUFRRTF2+7WlnIWsePZCWHs+OGjg==} engines: {node: '>= 20'} + '@octokit/auth-oauth-device@7.1.5': + resolution: {integrity: sha512-lR00+k7+N6xeECj0JuXeULQ2TSBB/zjTAmNF2+vyGPDEFx1dgk1hTDmL13MjbSmzusuAmuJD8Pu39rjp9jH6yw==} + engines: {node: '>= 18'} + '@octokit/auth-oauth-device@8.0.3': resolution: {integrity: sha512-zh2W0mKKMh/VWZhSqlaCzY7qFyrgd9oTWmTmHaXnHNeQRCZr/CXy2jCgHo4e4dJVTiuxP5dLa0YM5p5QVhJHbw==} engines: {node: '>= 20'} + '@octokit/auth-oauth-user@5.1.6': + resolution: {integrity: sha512-/R8vgeoulp7rJs+wfJ2LtXEVC7pjQTIqDab7wPKwVG6+2v/lUnCOub6vaHmysQBbb45FknM3tbHW8TOVqYHxCw==} + engines: {node: '>= 18'} + '@octokit/auth-oauth-user@6.0.2': resolution: {integrity: sha512-qLoPPc6E6GJoz3XeDG/pnDhJpTkODTGG4kY0/Py154i/I003O9NazkrwJwRuzgCalhzyIeWQ+6MDvkUmKXjg/A==} engines: {node: '>= 20'} @@ -812,6 +831,10 @@ packages: resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} engines: {node: '>= 20'} + '@octokit/endpoint@10.1.4': + resolution: {integrity: sha512-OlYOlZIsfEVZm5HCSR8aSg02T2lbUWOsCQoPKfTXJwDzcHQBrVBGdGXb89dv2Kw2ToZaRtudp8O3ZIYoaOjKlA==} + engines: {node: '>= 18'} + '@octokit/endpoint@11.0.2': resolution: {integrity: sha512-4zCpzP1fWc7QlqunZ5bSEjxc6yLAlRTnDwKtgXfcI/FxxGoqedDG8V2+xJ60bV2kODqcGB+nATdtap/XYq2NZQ==} engines: {node: '>= 20'} @@ -820,14 +843,25 @@ packages: resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} engines: {node: '>= 20'} + '@octokit/oauth-authorization-url@7.1.1': + resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} + engines: {node: '>= 18'} + '@octokit/oauth-authorization-url@8.0.0': resolution: {integrity: sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==} engines: {node: '>= 20'} + '@octokit/oauth-methods@5.1.5': + resolution: {integrity: sha512-Ev7K8bkYrYLhoOSZGVAGsLEscZQyq7XQONCBBAl2JdMg7IT3PQn/y8P0KjloPoYpI5UylqYrLeUcScaYWXwDvw==} + engines: {node: '>= 18'} + '@octokit/oauth-methods@6.0.2': resolution: {integrity: sha512-HiNOO3MqLxlt5Da5bZbLV8Zarnphi4y9XehrbaFMkcoJ+FL7sMxH/UlUsCVxpddVu4qvNDrBdaTVE2o4ITK8ng==} engines: {node: '>= 20'} + '@octokit/openapi-types@25.1.0': + resolution: {integrity: sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==} + '@octokit/openapi-types@27.0.0': resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} @@ -868,6 +902,10 @@ packages: peerDependencies: '@octokit/core': ^7.0.0 + '@octokit/request-error@6.1.8': + resolution: {integrity: sha512-WEi/R0Jmq+IJKydWlKDmryPcmdYSVjL3ekaiEL1L9eo1sUnqMJ+grqmC9cjk7CA7+b2/T397tO5d8YLOH3qYpQ==} + engines: {node: '>= 18'} + '@octokit/request-error@7.1.0': resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} @@ -876,10 +914,17 @@ packages: resolution: {integrity: sha512-v93h0i1yu4idj8qFPZwjehoJx4j3Ntn+JhXsdJrG9pYaX6j/XRz2RmasMUHtNgQD39nrv/VwTWSqK0RNXR8upA==} engines: {node: '>= 20'} + '@octokit/request@9.2.4': + resolution: {integrity: sha512-q8ybdytBmxa6KogWlNa818r0k1wlqzNC+yNkcQDECHvQo8Vmstrg18JwqJHdJdUiHD2sjlwBgSm9kHkOKe2iyA==} + engines: {node: '>= 18'} + '@octokit/rest@22.0.1': resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} engines: {node: '>= 20'} + '@octokit/types@14.1.0': + resolution: {integrity: sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==} + '@octokit/types@16.0.0': resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} @@ -1289,6 +1334,9 @@ packages: resolution: {integrity: sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + fast-content-type-parse@2.0.1: + resolution: {integrity: sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==} + fast-content-type-parse@3.0.0: resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} @@ -3242,6 +3290,17 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@octokit/auth-app@7.2.2': + dependencies: + '@octokit/auth-oauth-app': 8.1.4 + '@octokit/auth-oauth-user': 5.1.6 + '@octokit/request': 9.2.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + toad-cache: 3.7.0 + universal-github-app-jwt: 2.2.2 + universal-user-agent: 7.0.3 + '@octokit/auth-app@8.2.0': dependencies: '@octokit/auth-oauth-app': 9.0.3 @@ -3253,6 +3312,14 @@ snapshots: universal-github-app-jwt: 2.2.2 universal-user-agent: 7.0.3 + '@octokit/auth-oauth-app@8.1.4': + dependencies: + '@octokit/auth-oauth-device': 7.1.5 + '@octokit/auth-oauth-user': 5.1.6 + '@octokit/request': 9.2.4 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + '@octokit/auth-oauth-app@9.0.3': dependencies: '@octokit/auth-oauth-device': 8.0.3 @@ -3261,6 +3328,13 @@ snapshots: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + '@octokit/auth-oauth-device@7.1.5': + dependencies: + '@octokit/oauth-methods': 5.1.5 + '@octokit/request': 9.2.4 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + '@octokit/auth-oauth-device@8.0.3': dependencies: '@octokit/oauth-methods': 6.0.2 @@ -3268,6 +3342,14 @@ snapshots: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + '@octokit/auth-oauth-user@5.1.6': + dependencies: + '@octokit/auth-oauth-device': 7.1.5 + '@octokit/oauth-methods': 5.1.5 + '@octokit/request': 9.2.4 + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + '@octokit/auth-oauth-user@6.0.2': dependencies: '@octokit/auth-oauth-device': 8.0.3 @@ -3293,6 +3375,11 @@ snapshots: before-after-hook: 4.0.0 universal-user-agent: 7.0.3 + '@octokit/endpoint@10.1.4': + dependencies: + '@octokit/types': 14.1.0 + universal-user-agent: 7.0.3 + '@octokit/endpoint@11.0.2': dependencies: '@octokit/types': 16.0.0 @@ -3304,8 +3391,17 @@ snapshots: '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 + '@octokit/oauth-authorization-url@7.1.1': {} + '@octokit/oauth-authorization-url@8.0.0': {} + '@octokit/oauth-methods@5.1.5': + dependencies: + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/request': 9.2.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + '@octokit/oauth-methods@6.0.2': dependencies: '@octokit/oauth-authorization-url': 8.0.0 @@ -3313,6 +3409,8 @@ snapshots: '@octokit/request-error': 7.1.0 '@octokit/types': 16.0.0 + '@octokit/openapi-types@25.1.0': {} + '@octokit/openapi-types@27.0.0': {} '@octokit/openapi-webhooks-types@12.1.0': {} @@ -3349,6 +3447,10 @@ snapshots: '@octokit/types': 16.0.0 bottleneck: 2.19.5 + '@octokit/request-error@6.1.8': + dependencies: + '@octokit/types': 14.1.0 + '@octokit/request-error@7.1.0': dependencies: '@octokit/types': 16.0.0 @@ -3361,6 +3463,14 @@ snapshots: fast-content-type-parse: 3.0.0 universal-user-agent: 7.0.3 + '@octokit/request@9.2.4': + dependencies: + '@octokit/endpoint': 10.1.4 + '@octokit/request-error': 6.1.8 + '@octokit/types': 14.1.0 + fast-content-type-parse: 2.0.1 + universal-user-agent: 7.0.3 + '@octokit/rest@22.0.1': dependencies: '@octokit/core': 7.0.6 @@ -3368,6 +3478,10 @@ snapshots: '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + '@octokit/types@14.1.0': + dependencies: + '@octokit/openapi-types': 25.1.0 + '@octokit/types@16.0.0': dependencies: '@octokit/openapi-types': 27.0.0 @@ -3843,6 +3957,8 @@ snapshots: jest-mock: 30.2.0 jest-util: 30.2.0 + fast-content-type-parse@2.0.1: {} + fast-content-type-parse@3.0.0: {} fast-copy@4.0.2: {} diff --git a/src/BUILD.bazel b/src/BUILD.bazel index 40a9188..b299c79 100644 --- a/src/BUILD.bazel +++ b/src/BUILD.bazel @@ -5,6 +5,7 @@ js_library( srcs = glob(["*.js"]), deps = [ "//:node_modules/probot", + "//:node_modules/@octokit/auth-app", "//:node_modules/@octokit/rest", "//:node_modules/js-yaml", "//:node_modules/helmet", diff --git a/src/app.js b/src/app.js index b7c156a..308b9e8 100644 --- a/src/app.js +++ b/src/app.js @@ -458,8 +458,6 @@ function registerApp(app, { getRouter, addHandler } = {}) { const pr = context.payload.pull_request; const sender = context.payload.sender?.login || ""; - const owner = context.payload.repository.owner.login; - const repo = context.payload.repository.name; const isDependabot = sender === "dependabot[bot]" && autoMerge.on_dependabot; const isBotUser = (autoMerge.on_bot_users || []).some( diff --git a/src/dashboard.js b/src/dashboard.js index 4bda607..1f909a5 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -309,7 +309,6 @@ function renderPRsPartial(data) { // Sort PRs within repo by age (oldest first) prs.sort((a, b) => new Date(a.created_at) - new Date(b.created_at)); - const hasFailure = prs.some(p => p.checkStatus === 'fail' || p.checkStatus === 'failure'); const statusBadges = []; const passing = prs.filter(p => p.checkStatus === 'pass' || p.checkStatus === 'success').length; const failing = prs.filter(p => p.checkStatus === 'fail' || p.checkStatus === 'failure').length; From 85da1f067199b3598742402487f1d360c87c5ee6 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 2 Mar 2026 10:11:30 +0100 Subject: [PATCH 2/5] fix: wrap config.yml in js_library for Bazel cross-package data aspect_rules_js requires cross-package source files to be wrapped in a js_library target so they can be copied to the output tree. Co-Authored-By: Claude Opus 4.6 --- BUILD.bazel | 6 +++++- __tests__/BUILD.bazel | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index b7a505f..546cced 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,7 +3,11 @@ load("@npm//:defs.bzl", "npm_link_all_packages") npm_link_all_packages(name = "node_modules") -exports_files(["config.yml"]) +js_library( + name = "config_yml", + srcs = ["config.yml"], + visibility = ["//__tests__:__pkg__"], +) js_library( name = "jest_config", diff --git a/__tests__/BUILD.bazel b/__tests__/BUILD.bazel index 0736451..059fafb 100644 --- a/__tests__/BUILD.bazel +++ b/__tests__/BUILD.bazel @@ -30,7 +30,7 @@ jest_bin.jest_test( ":test_sources", "//src:lib", "//:jest_config", - "//:config.yml", + "//:config_yml", "//:node_modules/jest", "//:node_modules/@babel/core", "//:node_modules/@babel/preset-env", @@ -53,7 +53,7 @@ jest_bin.jest_test( ":test_sources", "//src:lib", "//:jest_config", - "//:config.yml", + "//:config_yml", "//:node_modules/jest", "//:node_modules/@babel/core", "//:node_modules/@babel/preset-env", From 1e4b644058bc7a8fd61e2a5a5291cb1fd7cf8abd Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 2 Mar 2026 10:19:01 +0100 Subject: [PATCH 3/5] fix: add .aspect_rules_js to jest transformIgnorePatterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bazel's aspect_rules_js uses node_modules/.aspect_rules_js// nested layout, same as pnpm's .pnpm — needs the same exception in the transform pattern so @octokit/auth-app gets babel-transformed. Co-Authored-By: Claude Opus 4.6 --- jest.config.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.cjs b/jest.config.cjs index de39cea..cc31ea7 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -5,7 +5,7 @@ module.exports = { '^.+\\.js$': ['babel-jest', { configFile: './babel.config.cjs' }] }, transformIgnorePatterns: [ - '/node_modules/(?!\\.pnpm|probot|@probot|@octokit|before-after-hook|universal-user-agent|universal-github-app-jwt|octokit-auth-probot)' + '/node_modules/(?!\\.pnpm|\\.aspect_rules_js|probot|@probot|@octokit|before-after-hook|universal-user-agent|universal-github-app-jwt|octokit-auth-probot)' ], testMatch: [ '**/__tests__/unit/**/*.test.js', From 16767753914f7b3e03f029c7f4062b31dbada6c3 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 2 Mar 2026 10:25:18 +0100 Subject: [PATCH 4/5] feat: include repo and branch context in AI review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The review header now shows the full origin and target: contributor/temper:`fix/bug` → pulseengine/temper:`main` This gives immediate context for cross-repo (fork) PRs and makes it clear which branch is being reviewed against. Co-Authored-By: Claude Opus 4.6 --- __tests__/integration/ai-review.test.js | 17 +++++++++++++++++ src/ai-review.js | 22 +++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/__tests__/integration/ai-review.test.js b/__tests__/integration/ai-review.test.js index 70d2435..fd3c972 100644 --- a/__tests__/integration/ai-review.test.js +++ b/__tests__/integration/ai-review.test.js @@ -397,6 +397,23 @@ describe('ai-review', () => { expect(result).toContain('`aaaaaaa`'); expect(result).not.toContain('a'.repeat(40)); }); + + it('includes repo and branch metadata when provided', () => { + const result = formatReviewComment('review', 42, 'abc1234', { + baseRepo: 'pulseengine/temper', + baseBranch: 'main', + headRepo: 'contributor/temper', + headBranch: 'fix/bug', + }); + expect(result).toContain('contributor/temper:`fix/bug`'); + expect(result).toContain('pulseengine/temper:`main`'); + expect(result).toContain('→'); + }); + + it('omits branch line when meta is not provided', () => { + const result = formatReviewComment('review', 1); + expect(result).not.toContain('→'); + }); }); // ========================================================================= diff --git a/src/ai-review.js b/src/ai-review.js index 39a6ca9..d11a895 100644 --- a/src/ai-review.js +++ b/src/ai-review.js @@ -219,9 +219,20 @@ async function callLocalAI(endpoint, model, systemPrompt, userPrompt, options = const AI_REVIEW_SIGNATURE = 'AI Code Review'; -function formatReviewComment(aiResponse, prNumber, headSha) { +function formatReviewComment(aiResponse, prNumber, headSha, meta) { const sanitized = sanitizeAIOutput(aiResponse); + let header = `## ${AI_REVIEW_SIGNATURE} for PR #${prNumber}\n`; + if (meta) { + const base = meta.baseRepo && meta.baseBranch + ? `${meta.baseRepo}:\`${meta.baseBranch}\`` + : meta.baseBranch ? `\`${meta.baseBranch}\`` : ''; + const head = meta.headRepo && meta.headBranch + ? `${meta.headRepo}:\`${meta.headBranch}\`` + : meta.headBranch ? `\`${meta.headBranch}\`` : ''; + if (base && head) header += `${head} → ${base}\n`; + } + let footer = '*This review was generated by a local AI model. ' + 'It is advisory only and may contain inaccuracies.*'; @@ -231,7 +242,7 @@ function formatReviewComment(aiResponse, prNumber, headSha) { } return ( - `## ${AI_REVIEW_SIGNATURE} for PR #${prNumber}\n\n` + + header + '\n' + sanitized + '\n\n---\n' + footer @@ -346,7 +357,12 @@ async function reviewPullRequest(octokit, owner, repo, prNumber) { _reviewTimestamps.set(rateKey, Date.now()); const headSha = prData.data.head?.sha || ''; - const comment = formatReviewComment(aiResponse, prNumber, headSha); + const comment = formatReviewComment(aiResponse, prNumber, headSha, { + baseRepo: prData.data.base?.repo?.full_name || `${owner}/${repo}`, + baseBranch: prData.data.base?.ref || '', + headRepo: prData.data.head?.repo?.full_name || `${owner}/${repo}`, + headBranch: prData.data.head?.ref || '', + }); // Mark previous bot reviews as outdated await supersedePreviousReviews(octokit, owner, repo, prNumber); From 79f872bacc92cde1505d89df5556bf742af4db78 Mon Sep 17 00:00:00 2001 From: Ralf Anton Beier Date: Mon, 2 Mar 2026 10:54:42 +0100 Subject: [PATCH 5/5] feat: add self-updating webhook with Rust updater binary When a push to main is received for the temper repo, spawn a detached Rust binary that pulls latest code, conditionally runs npm ci, rebuilds itself if its source changed (falling back to the last working binary on build failure), and restarts the process via PM2 or SIGTERM. Co-Authored-By: Claude Opus 4.6 --- __tests__/integration/app.test.js | 2 +- __tests__/unit/schema.test.js | 29 +++++++++ __tests__/unit/self-update.test.js | 50 +++++++++++++++ config.yml | 5 ++ src/app.js | 28 ++++++++ src/schema.js | 17 +++++ src/self-update.js | 28 ++++++++ tools/self-update/Cargo.toml | 8 +++ tools/self-update/src/main.rs | 100 +++++++++++++++++++++++++++++ 9 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 __tests__/unit/self-update.test.js create mode 100644 src/self-update.js create mode 100644 tools/self-update/Cargo.toml create mode 100644 tools/self-update/src/main.rs diff --git a/__tests__/integration/app.test.js b/__tests__/integration/app.test.js index 4fcf75f..5fa7b83 100644 --- a/__tests__/integration/app.test.js +++ b/__tests__/integration/app.test.js @@ -298,7 +298,7 @@ describe('app', () => { it('works without getRouter option', () => { const { app } = setupApp({ skipRouter: true }); - expect(app.on).toHaveBeenCalledTimes(4); + expect(app.on).toHaveBeenCalledTimes(5); expect(app.onError).toHaveBeenCalledTimes(1); }); diff --git a/__tests__/unit/schema.test.js b/__tests__/unit/schema.test.js index 376fe1e..987e38e 100644 --- a/__tests__/unit/schema.test.js +++ b/__tests__/unit/schema.test.js @@ -59,4 +59,33 @@ describe('validateConfig', () => { const result = validateConfig({ ai_review: { enabled: true, temperature: 0.3 } }); expect(result.valid).toBe(true); }); + + it('accepts valid self_update config', () => { + const result = validateConfig({ self_update: { enabled: true, repo: 'temper', branch: 'main' } }); + expect(result.valid).toBe(true); + }); + + it('rejects non-boolean self_update.enabled', () => { + const result = validateConfig({ self_update: { enabled: 'yes' } }); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('self_update.enabled'); + }); + + it('rejects empty self_update.repo', () => { + const result = validateConfig({ self_update: { repo: '' } }); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('self_update.repo'); + }); + + it('rejects non-string self_update.branch', () => { + const result = validateConfig({ self_update: { branch: 123 } }); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('self_update.branch'); + }); + + it('rejects non-object self_update', () => { + const result = validateConfig({ self_update: 'yes' }); + expect(result.valid).toBe(false); + expect(result.errors[0]).toContain('self_update'); + }); }); diff --git a/__tests__/unit/self-update.test.js b/__tests__/unit/self-update.test.js new file mode 100644 index 0000000..ea98cd0 --- /dev/null +++ b/__tests__/unit/self-update.test.js @@ -0,0 +1,50 @@ +import { spawn } from 'node:child_process'; +import { triggerSelfUpdate } from '../../src/self-update.js'; + +jest.mock('node:child_process', () => ({ + spawn: jest.fn(() => ({ unref: jest.fn() })) +})); + +describe('triggerSelfUpdate', () => { + const mockLogger = { info: jest.fn(), warn: jest.fn(), error: jest.fn() }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('spawns the self-update binary as a detached process', () => { + triggerSelfUpdate(mockLogger); + + expect(spawn).toHaveBeenCalledTimes(1); + const [binary, args, opts] = spawn.mock.calls[0]; + expect(binary).toContain('temper-self-update'); + expect(args).toEqual([]); + expect(opts.detached).toBe(true); + expect(opts.stdio).toBe('ignore'); + }); + + it('passes TEMPER_PID and TEMPER_REPO_DIR as env vars', () => { + triggerSelfUpdate(mockLogger); + + const opts = spawn.mock.calls[0][2]; + expect(opts.env.TEMPER_PID).toBe(String(process.pid)); + expect(opts.env.TEMPER_REPO_DIR).toBeDefined(); + }); + + it('calls unref() on the child process', () => { + const mockUnref = jest.fn(); + spawn.mockReturnValue({ unref: mockUnref }); + + triggerSelfUpdate(mockLogger); + + expect(mockUnref).toHaveBeenCalledTimes(1); + }); + + it('logs that the update is being triggered', () => { + triggerSelfUpdate(mockLogger); + + expect(mockLogger.info).toHaveBeenCalledWith( + expect.stringContaining('self-update') + ); + }); +}); diff --git a/config.yml b/config.yml index b883398..d643a13 100644 --- a/config.yml +++ b/config.yml @@ -50,6 +50,11 @@ auto_merge: - thrum merge_method: squash +self_update: + enabled: true + repo: temper + branch: main + issue_labels: - name: "bug" color: "d73a4a" diff --git a/src/app.js b/src/app.js index 308b9e8..149d7a9 100644 --- a/src/app.js +++ b/src/app.js @@ -16,6 +16,7 @@ import { import { handleSignedCommitMerge, checkPRMergeStrategy } from './merge-strategy.js'; import { reviewPullRequest, updateReviewStatus } from './ai-review.js'; import { isProcessed, markProcessed } from './idempotency.js'; +import { triggerSelfUpdate } from './self-update.js'; import { defaultQueue } from './queue.js'; import { applySecurityMiddleware } from './middleware.js'; @@ -511,6 +512,33 @@ function registerApp(app, { getRouter, addHandler } = {}) { if (deliveryId) markProcessed(deliveryId); }); + app.on('push', async (context) => { + if (context.log) setLogger(context.log); + const deliveryId = context.id; + if (deliveryId && isProcessed(deliveryId)) { + getLogger().info({ deliveryId }, 'Skipping duplicate webhook delivery'); + return; + } + + const config = getConfig(); + const selfUpdate = config?.self_update; + if (!selfUpdate?.enabled) return; + + const { repository, ref } = context.payload; + const expectedRef = `refs/heads/${selfUpdate.branch || 'main'}`; + + if (repository.name !== selfUpdate.repo || ref !== expectedRef) return; + + getLogger().info( + { repo: repository.name, ref }, + 'Push to own repo detected — triggering self-update' + ); + + triggerSelfUpdate(getLogger()); + + if (deliveryId) markProcessed(deliveryId); + }); + // Add health check endpoint with queue stats if (getRouter) { const router = getRouter('/'); diff --git a/src/schema.js b/src/schema.js index fe01a82..de52437 100644 --- a/src/schema.js +++ b/src/schema.js @@ -55,6 +55,23 @@ export function validateConfig(config) { } } + if (config.self_update !== undefined) { + const su = config.self_update; + if (typeof su !== 'object' || su === null) { + errors.push('self_update must be an object'); + } else { + if (su.enabled !== undefined && typeof su.enabled !== 'boolean') { + errors.push('self_update.enabled must be a boolean'); + } + if (su.repo !== undefined && (typeof su.repo !== 'string' || su.repo.length === 0)) { + errors.push('self_update.repo must be a non-empty string'); + } + if (su.branch !== undefined && typeof su.branch !== 'string') { + errors.push('self_update.branch must be a string'); + } + } + } + if (config.ai_review?.enabled) { if (config.ai_review.endpoint !== undefined && typeof config.ai_review.endpoint !== 'string') { errors.push('ai_review.endpoint must be a string'); diff --git a/src/self-update.js b/src/self-update.js new file mode 100644 index 0000000..ed6541d --- /dev/null +++ b/src/self-update.js @@ -0,0 +1,28 @@ +import { spawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const BINARY_PATH = join(__dirname, '..', 'tools', 'self-update', 'target', 'release', 'temper-self-update'); + +/** + * Spawns the compiled self-update binary as a detached process, then returns + * immediately so the webhook response can complete before the process restarts. + * + * @param {object} logger - Pino-compatible logger + */ +export function triggerSelfUpdate(logger) { + logger.info('Spawning self-update binary'); + + const child = spawn(BINARY_PATH, [], { + detached: true, + stdio: 'ignore', + env: { + ...process.env, + TEMPER_PID: String(process.pid), + TEMPER_REPO_DIR: join(__dirname, '..') + } + }); + + child.unref(); +} diff --git a/tools/self-update/Cargo.toml b/tools/self-update/Cargo.toml new file mode 100644 index 0000000..cd59628 --- /dev/null +++ b/tools/self-update/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "temper-self-update" +version = "0.1.0" +edition = "2021" + +[profile.release] +strip = true +lto = true diff --git a/tools/self-update/src/main.rs b/tools/self-update/src/main.rs new file mode 100644 index 0000000..2550f95 --- /dev/null +++ b/tools/self-update/src/main.rs @@ -0,0 +1,100 @@ +use std::env; +use std::process::Command; +use std::thread; +use std::time::Duration; + +fn run(cmd: &str, args: &[&str], dir: &str) -> Result { + Command::new(cmd) + .args(args) + .current_dir(dir) + .output() + .map_err(|e| format!("failed to run {cmd}: {e}")) +} + +fn main() { + let repo_dir = env::var("TEMPER_REPO_DIR").unwrap_or_else(|_| { + // Default: two levels up from the binary (tools/self-update/ -> repo root) + let exe = env::current_exe().expect("cannot resolve exe path"); + exe.ancestors() + .nth(3) + .expect("cannot resolve repo root") + .to_string_lossy() + .into_owned() + }); + + let pid = env::var("TEMPER_PID").ok(); + + // Let the webhook response complete + thread::sleep(Duration::from_secs(2)); + + eprintln!("[self-update] pulling latest code in {repo_dir}"); + + // Fetch and reset + if let Err(e) = run("git", &["fetch", "origin", "main"], &repo_dir) { + eprintln!("[self-update] git fetch failed: {e}"); + return; + } + + // Check which files changed before resetting + let diff_output = run("git", &["diff", "HEAD", "origin/main", "--name-only"], &repo_dir); + let diff_text = diff_output + .as_ref() + .map(|o| String::from_utf8_lossy(&o.stdout).to_string()) + .unwrap_or_default(); + let needs_install = diff_text.contains("package-lock.json"); + let updater_changed = diff_text.contains("tools/self-update/"); + + if let Err(e) = run("git", &["reset", "--hard", "origin/main"], &repo_dir) { + eprintln!("[self-update] git reset failed: {e}"); + return; + } + + eprintln!("[self-update] code updated"); + + // Rebuild the updater itself if its source changed. + // On failure the current binary on disk is untouched (cargo only + // replaces the output on a successful build), so next invocation + // still uses the last working version. + if updater_changed { + eprintln!("[self-update] updater source changed, rebuilding"); + let updater_dir = format!("{repo_dir}/tools/self-update"); + match run("cargo", &["build", "--release"], &updater_dir) { + Ok(o) if o.status.success() => { + eprintln!("[self-update] updater rebuilt successfully"); + } + Ok(o) => { + let stderr = String::from_utf8_lossy(&o.stderr); + eprintln!("[self-update] updater build failed (keeping old binary): {stderr}"); + } + Err(e) => { + eprintln!("[self-update] could not run cargo: {e} (keeping old binary)"); + } + } + } + + // Reinstall dependencies if package-lock.json changed + if needs_install { + eprintln!("[self-update] package-lock.json changed, running npm ci"); + if let Err(e) = run("npm", &["ci", "--production"], &repo_dir) { + eprintln!("[self-update] npm ci failed: {e}"); + // Continue anyway — old node_modules may still work + } + } + + // Restart: prefer pm2, fall back to killing the process + let has_pm2 = Command::new("pm2") + .arg("--version") + .output() + .map(|o| o.status.success()) + .unwrap_or(false); + + if has_pm2 { + eprintln!("[self-update] restarting via pm2"); + let _ = run("pm2", &["restart", "temper"], &repo_dir); + } else if let Some(pid_str) = pid { + eprintln!("[self-update] sending SIGTERM to pid {pid_str}"); + let _ = run("kill", &[&pid_str], "/"); + } else { + eprintln!("[self-update] no pm2 and no TEMPER_PID — cannot restart"); + } +}