@r_masseater/ops-harbor 0.1.2 → 0.1.4
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/cli.js +642 -58
- package/dist/client/assets/index-C0_oEDrv.js +51 -0
- package/dist/client/assets/index-M1DvJW44.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/control-plane.js +392 -210
- package/dist/mcp-server.js +26 -27
- package/package.json +2 -1
- package/dist/client/assets/index-0Ytuj-W0.css +0 -1
- package/dist/client/assets/index-DyzvRtB8.js +0 -51
package/dist/cli.js
CHANGED
|
@@ -48,10 +48,317 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
|
48
48
|
var __require = import.meta.require;
|
|
49
49
|
|
|
50
50
|
// ../../packages/ops-harbor-core/src/alerts.ts
|
|
51
|
+
function inferCheckState(checks) {
|
|
52
|
+
if (checks.length === 0)
|
|
53
|
+
return "unknown";
|
|
54
|
+
let hasNeutral = false;
|
|
55
|
+
for (const check of checks) {
|
|
56
|
+
if (check.status !== "completed") {
|
|
57
|
+
return "pending";
|
|
58
|
+
}
|
|
59
|
+
const conclusion = (check.conclusion ?? "").toLowerCase();
|
|
60
|
+
if (["failure", "timed_out", "action_required", "cancelled"].includes(conclusion)) {
|
|
61
|
+
return "failure";
|
|
62
|
+
}
|
|
63
|
+
if (["neutral", "skipped"].includes(conclusion)) {
|
|
64
|
+
hasNeutral = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return hasNeutral ? "neutral" : "success";
|
|
68
|
+
}
|
|
69
|
+
function inferReviewState(reviews, reviewDecision) {
|
|
70
|
+
const normalizedDecision = (reviewDecision ?? "").toUpperCase();
|
|
71
|
+
if (normalizedDecision === "CHANGES_REQUESTED")
|
|
72
|
+
return "changes_requested";
|
|
73
|
+
if (normalizedDecision === "APPROVED")
|
|
74
|
+
return "approved";
|
|
75
|
+
const latest = reviews.toSorted((a, b) => b.submittedAt.localeCompare(a.submittedAt))[0];
|
|
76
|
+
return latest?.state ?? "none";
|
|
77
|
+
}
|
|
78
|
+
function inferMergeState(item) {
|
|
79
|
+
const state = item.mergeStateStatus.toUpperCase();
|
|
80
|
+
if (item.mergeable === "conflicting" || ["DIRTY", "CONFLICTING"].includes(state)) {
|
|
81
|
+
return "conflicting";
|
|
82
|
+
}
|
|
83
|
+
if (state === "BEHIND")
|
|
84
|
+
return "behind";
|
|
85
|
+
if (["BLOCKED", "DRAFT", "UNSTABLE"].includes(state))
|
|
86
|
+
return "blocked";
|
|
87
|
+
if (state === "CLEAN" || item.mergeable === "mergeable")
|
|
88
|
+
return "clean";
|
|
89
|
+
return "unknown";
|
|
90
|
+
}
|
|
91
|
+
function summarizeStatus(checks, reviews, item) {
|
|
92
|
+
const checkState = inferCheckState(checks);
|
|
93
|
+
const reviewState = inferReviewState(reviews, item.reviewDecision);
|
|
94
|
+
const mergeState = inferMergeState(item);
|
|
95
|
+
const overall = checkState === "unknown" && reviewState === "none" && mergeState === "unknown" ? "unknown" : checkState === "failure" || reviewState === "changes_requested" || mergeState !== "clean" && mergeState !== "unknown" ? "needs_attention" : checkState === "pending" ? "running" : "healthy";
|
|
96
|
+
return { overall, checkState, reviewState, mergeState };
|
|
97
|
+
}
|
|
98
|
+
function createAlert(type, severity, title, message, createdAt, source) {
|
|
99
|
+
return { type, severity, title, message, createdAt, source };
|
|
100
|
+
}
|
|
101
|
+
function deriveAlerts(item, recentEvents = []) {
|
|
102
|
+
const alerts = [];
|
|
103
|
+
const now = item.updatedAt;
|
|
104
|
+
if (item.statusSummary.checkState === "failure") {
|
|
105
|
+
alerts.push(createAlert("ci_failed", "critical", "CI failed", "One or more required checks failed on the latest head commit.", now, "state"));
|
|
106
|
+
}
|
|
107
|
+
if (item.statusSummary.mergeState === "conflicting") {
|
|
108
|
+
alerts.push(createAlert("conflicted", "critical", "Merge conflicts detected", `Branch ${item.headBranch} conflicts with ${item.baseBranch}.`, now, "state"));
|
|
109
|
+
}
|
|
110
|
+
if (item.statusSummary.mergeState === "behind") {
|
|
111
|
+
alerts.push(createAlert("base_behind", "warning", "Base branch moved ahead", `${item.headBranch} should be rebased or merged with ${item.baseBranch}.`, now, "state"));
|
|
112
|
+
}
|
|
113
|
+
if (item.statusSummary.reviewState === "changes_requested") {
|
|
114
|
+
alerts.push(createAlert("review_changes_requested", "critical", "Changes requested", "A reviewer requested follow-up changes on this work item.", now, "state"));
|
|
115
|
+
}
|
|
116
|
+
const reviewCommentEvent = recentEvents.filter((event) => event.type === "review_commented").toSorted((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
|
|
117
|
+
if (reviewCommentEvent) {
|
|
118
|
+
alerts.push(createAlert("review_commented", "info", "Review comment received", reviewCommentEvent.message, reviewCommentEvent.createdAt, "event"));
|
|
119
|
+
}
|
|
120
|
+
const blockers = alerts.filter((alert) => alert.severity !== "info");
|
|
121
|
+
return { alerts, blockers };
|
|
122
|
+
}
|
|
51
123
|
function hasBlockingAlerts(item) {
|
|
52
124
|
return item.alerts.some((alert) => alert.severity !== "info");
|
|
53
125
|
}
|
|
54
126
|
|
|
127
|
+
// ../../packages/ops-harbor-core/src/github.ts
|
|
128
|
+
import { createSign } from "crypto";
|
|
129
|
+
function toBase64Url(value) {
|
|
130
|
+
return Buffer.from(value).toString("base64").replaceAll("=", "").replaceAll("+", "-").replaceAll("/", "_");
|
|
131
|
+
}
|
|
132
|
+
function createAppJwt(appId, privateKeyPem) {
|
|
133
|
+
const now = Math.floor(Date.now() / 1000);
|
|
134
|
+
const header = { alg: "RS256", typ: "JWT" };
|
|
135
|
+
const payload = {
|
|
136
|
+
iat: now - 60,
|
|
137
|
+
exp: now + 9 * 60,
|
|
138
|
+
iss: appId
|
|
139
|
+
};
|
|
140
|
+
const encoded = `${toBase64Url(JSON.stringify(header))}.${toBase64Url(JSON.stringify(payload))}`;
|
|
141
|
+
const signer = createSign("RSA-SHA256");
|
|
142
|
+
signer.update(encoded);
|
|
143
|
+
const signature = signer.sign(privateKeyPem);
|
|
144
|
+
return `${encoded}.${toBase64Url(signature)}`;
|
|
145
|
+
}
|
|
146
|
+
async function createInstallationToken(config, installationId) {
|
|
147
|
+
const jwt = createAppJwt(config.githubAppId, config.githubPrivateKey);
|
|
148
|
+
const response = await fetch(`${config.githubApiUrl}/app/installations/${installationId}/access_tokens`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: {
|
|
151
|
+
Accept: "application/vnd.github+json",
|
|
152
|
+
Authorization: `Bearer ${jwt}`,
|
|
153
|
+
"User-Agent": "ops-harbor"
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
throw new Error(`Failed to create installation token: ${response.status} ${response.statusText}`);
|
|
158
|
+
}
|
|
159
|
+
const data = await response.json();
|
|
160
|
+
return data.token;
|
|
161
|
+
}
|
|
162
|
+
async function graphqlRequest(config, installationId, query, variables) {
|
|
163
|
+
const token = await createInstallationToken(config, installationId);
|
|
164
|
+
const response = await fetch(`${config.githubApiUrl}/graphql`, {
|
|
165
|
+
method: "POST",
|
|
166
|
+
headers: {
|
|
167
|
+
Accept: "application/vnd.github+json",
|
|
168
|
+
Authorization: `Bearer ${token}`,
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
"User-Agent": "ops-harbor"
|
|
171
|
+
},
|
|
172
|
+
body: JSON.stringify({ query, variables })
|
|
173
|
+
});
|
|
174
|
+
if (!response.ok) {
|
|
175
|
+
throw new Error(`GitHub GraphQL request failed: ${response.status} ${response.statusText}`);
|
|
176
|
+
}
|
|
177
|
+
const json = await response.json();
|
|
178
|
+
if (json.errors?.length) {
|
|
179
|
+
throw new Error(json.errors.map((error) => error.message).join("; "));
|
|
180
|
+
}
|
|
181
|
+
if (!json.data) {
|
|
182
|
+
throw new Error("GitHub GraphQL response did not contain data.");
|
|
183
|
+
}
|
|
184
|
+
return json.data;
|
|
185
|
+
}
|
|
186
|
+
function mapChecks(node) {
|
|
187
|
+
const contexts = node.commits.nodes[0]?.commit.statusCheckRollup?.contexts.nodes ?? [];
|
|
188
|
+
return contexts.map((context) => {
|
|
189
|
+
if (context.__typename === "CheckRun") {
|
|
190
|
+
return {
|
|
191
|
+
name: context.name,
|
|
192
|
+
status: context.status,
|
|
193
|
+
conclusion: context.conclusion,
|
|
194
|
+
url: context.detailsUrl ?? null,
|
|
195
|
+
startedAt: context.startedAt ?? null,
|
|
196
|
+
completedAt: context.completedAt ?? null
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
name: context.context,
|
|
201
|
+
status: context.state === "PENDING" ? "pending" : "completed",
|
|
202
|
+
conclusion: context.state.toLowerCase(),
|
|
203
|
+
url: context.targetUrl ?? null,
|
|
204
|
+
startedAt: context.createdAt ?? null,
|
|
205
|
+
completedAt: context.createdAt ?? null
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function mapReviews(node) {
|
|
210
|
+
return node.latestReviews.nodes.map((review) => ({
|
|
211
|
+
id: review.id,
|
|
212
|
+
state: review.state.toLowerCase(),
|
|
213
|
+
author: review.author?.login ?? "unknown",
|
|
214
|
+
submittedAt: review.submittedAt,
|
|
215
|
+
...review.body ? { bodySnippet: review.body.slice(0, 240) } : {},
|
|
216
|
+
...review.url ? { url: review.url } : {}
|
|
217
|
+
}));
|
|
218
|
+
}
|
|
219
|
+
function mapPullRequestNode(node, installationId) {
|
|
220
|
+
const checks = mapChecks(node);
|
|
221
|
+
const reviews = mapReviews(node);
|
|
222
|
+
const statusSummary = summarizeStatus(checks, reviews, {
|
|
223
|
+
mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
|
|
224
|
+
mergeStateStatus: node.mergeStateStatus,
|
|
225
|
+
reviewDecision: node.reviewDecision
|
|
226
|
+
});
|
|
227
|
+
return {
|
|
228
|
+
id: node.id,
|
|
229
|
+
provider: "github",
|
|
230
|
+
kind: "pull_request",
|
|
231
|
+
repository: node.repository.nameWithOwner,
|
|
232
|
+
number: node.number,
|
|
233
|
+
title: node.title,
|
|
234
|
+
url: node.url,
|
|
235
|
+
state: node.state.toLowerCase(),
|
|
236
|
+
author: node.author?.login ?? "unknown",
|
|
237
|
+
isDraft: node.isDraft,
|
|
238
|
+
headBranch: node.headRefName,
|
|
239
|
+
headSha: node.headRefOid,
|
|
240
|
+
baseBranch: node.baseRefName,
|
|
241
|
+
mergeable: node.mergeable === "CONFLICTING" ? "conflicting" : node.mergeable === "MERGEABLE" ? "mergeable" : "unknown",
|
|
242
|
+
mergeStateStatus: node.mergeStateStatus,
|
|
243
|
+
reviewDecision: node.reviewDecision,
|
|
244
|
+
statusSummary,
|
|
245
|
+
checks,
|
|
246
|
+
reviews,
|
|
247
|
+
alerts: [],
|
|
248
|
+
lastActivityAt: node.updatedAt,
|
|
249
|
+
updatedAt: node.updatedAt,
|
|
250
|
+
providerPayload: {
|
|
251
|
+
installationId,
|
|
252
|
+
repositoryUrl: node.repository.url
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
async function listAppInstallations(config) {
|
|
257
|
+
const jwt = createAppJwt(config.githubAppId, config.githubPrivateKey);
|
|
258
|
+
const installations = [];
|
|
259
|
+
for (let page = 1;; page += 1) {
|
|
260
|
+
const response = await fetch(`${config.githubApiUrl}/app/installations?per_page=100&page=${page}`, {
|
|
261
|
+
method: "GET",
|
|
262
|
+
headers: {
|
|
263
|
+
Accept: "application/vnd.github+json",
|
|
264
|
+
Authorization: `Bearer ${jwt}`,
|
|
265
|
+
"User-Agent": "ops-harbor"
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
throw new Error(`Failed to list app installations: ${response.status} ${response.statusText}`);
|
|
270
|
+
}
|
|
271
|
+
const data = await response.json();
|
|
272
|
+
const pageInstallations = Array.isArray(data) ? data : data.installations ?? [];
|
|
273
|
+
installations.push(...pageInstallations);
|
|
274
|
+
if (pageInstallations.length < 100) {
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return installations;
|
|
279
|
+
}
|
|
280
|
+
async function fetchOpenPullRequestsForAuthor(config, installationId, author, limit = 100) {
|
|
281
|
+
const items = [];
|
|
282
|
+
let cursor = null;
|
|
283
|
+
while (items.length < limit) {
|
|
284
|
+
const data = await graphqlRequest(config, installationId, SEARCH_QUERY, {
|
|
285
|
+
query: `is:pr is:open author:${author} archived:false`,
|
|
286
|
+
first: Math.min(50, limit - items.length),
|
|
287
|
+
after: cursor
|
|
288
|
+
});
|
|
289
|
+
items.push(...data.search.nodes.map((node) => mapPullRequestNode(node, installationId)));
|
|
290
|
+
if (!data.search.pageInfo.hasNextPage)
|
|
291
|
+
break;
|
|
292
|
+
cursor = data.search.pageInfo.endCursor;
|
|
293
|
+
}
|
|
294
|
+
return items;
|
|
295
|
+
}
|
|
296
|
+
var SEARCH_QUERY = `
|
|
297
|
+
query SearchPullRequests($query: String!, $first: Int!, $after: String) {
|
|
298
|
+
search(query: $query, type: ISSUE, first: $first, after: $after) {
|
|
299
|
+
pageInfo { hasNextPage endCursor }
|
|
300
|
+
nodes {
|
|
301
|
+
... on PullRequest {
|
|
302
|
+
id
|
|
303
|
+
number
|
|
304
|
+
title
|
|
305
|
+
url
|
|
306
|
+
state
|
|
307
|
+
isDraft
|
|
308
|
+
mergeable
|
|
309
|
+
mergeStateStatus
|
|
310
|
+
reviewDecision
|
|
311
|
+
updatedAt
|
|
312
|
+
repository { nameWithOwner url }
|
|
313
|
+
author { login }
|
|
314
|
+
baseRefName
|
|
315
|
+
headRefName
|
|
316
|
+
headRefOid
|
|
317
|
+
latestReviews(first: 20) {
|
|
318
|
+
nodes {
|
|
319
|
+
id
|
|
320
|
+
state
|
|
321
|
+
body
|
|
322
|
+
submittedAt
|
|
323
|
+
url
|
|
324
|
+
author { login }
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
commits(last: 1) {
|
|
328
|
+
nodes {
|
|
329
|
+
commit {
|
|
330
|
+
statusCheckRollup {
|
|
331
|
+
contexts(first: 50) {
|
|
332
|
+
nodes {
|
|
333
|
+
__typename
|
|
334
|
+
... on CheckRun {
|
|
335
|
+
name
|
|
336
|
+
status
|
|
337
|
+
conclusion
|
|
338
|
+
detailsUrl
|
|
339
|
+
startedAt
|
|
340
|
+
completedAt
|
|
341
|
+
}
|
|
342
|
+
... on StatusContext {
|
|
343
|
+
context
|
|
344
|
+
state
|
|
345
|
+
targetUrl
|
|
346
|
+
createdAt
|
|
347
|
+
description
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
`;
|
|
360
|
+
var init_github = () => {};
|
|
361
|
+
|
|
55
362
|
// ../../packages/ops-harbor-core/src/db-helpers.ts
|
|
56
363
|
function mapWorkItemsToAlertSummaries(workItems) {
|
|
57
364
|
return workItems.map((item) => ({
|
|
@@ -152,9 +459,9 @@ var init_port_finder = () => {};
|
|
|
152
459
|
|
|
153
460
|
// ../../packages/ops-harbor-core/src/sqlite.ts
|
|
154
461
|
import { createRequire } from "module";
|
|
155
|
-
import { resolve } from "path";
|
|
156
462
|
function openSqliteDatabase(filename) {
|
|
157
|
-
const
|
|
463
|
+
const Ctor = overriddenCtor ?? createRequire(import.meta.url)("bun:sqlite").Database;
|
|
464
|
+
const db = new Ctor(filename);
|
|
158
465
|
db.exec("PRAGMA journal_mode = WAL");
|
|
159
466
|
return db;
|
|
160
467
|
}
|
|
@@ -167,14 +474,12 @@ function parseJson(value, fallback) {
|
|
|
167
474
|
return fallback;
|
|
168
475
|
}
|
|
169
476
|
}
|
|
170
|
-
var
|
|
171
|
-
var init_sqlite =
|
|
172
|
-
requireFromModule = createRequire(import.meta.url);
|
|
173
|
-
requireFromWorkingDirectory = createRequire(resolve(process.cwd(), "package.json"));
|
|
174
|
-
});
|
|
477
|
+
var overriddenCtor;
|
|
478
|
+
var init_sqlite = () => {};
|
|
175
479
|
|
|
176
480
|
// ../../packages/ops-harbor-core/src/index.ts
|
|
177
481
|
var init_src = __esm(() => {
|
|
482
|
+
init_github();
|
|
178
483
|
init_filters();
|
|
179
484
|
init_prompt();
|
|
180
485
|
init_port_finder();
|
|
@@ -227,6 +532,15 @@ function ensureSchema(db) {
|
|
|
227
532
|
CREATE INDEX IF NOT EXISTS idx_local_runs_lookup ON automation_runs(work_item_id, finished_at DESC);
|
|
228
533
|
`);
|
|
229
534
|
}
|
|
535
|
+
function upsertWorkItems(db, items) {
|
|
536
|
+
const tx = db.transaction(() => {
|
|
537
|
+
const stmt = db.prepare(`INSERT OR REPLACE INTO work_items (id, repository, state, updated_at, payload_json) VALUES (?, ?, ?, ?, ?)`);
|
|
538
|
+
for (const item of items) {
|
|
539
|
+
stmt.run(item.id, item.repository, item.state, item.updatedAt, JSON.stringify(item));
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
tx();
|
|
543
|
+
}
|
|
230
544
|
function replaceSnapshot(db, workItems, events) {
|
|
231
545
|
const tx = db.transaction(() => {
|
|
232
546
|
db.exec(`DELETE FROM work_items; DELETE FROM activity_events;`);
|
|
@@ -277,10 +591,10 @@ function listActivity(db, options = {}) {
|
|
|
277
591
|
function listAlerts(db) {
|
|
278
592
|
return mapWorkItemsToAlertSummaries(listWorkItems(db, { hasBlocker: true }));
|
|
279
593
|
}
|
|
280
|
-
function recordAutomationRun(db,
|
|
594
|
+
function recordAutomationRun(db, run2) {
|
|
281
595
|
db.prepare(`INSERT OR REPLACE INTO automation_runs (
|
|
282
596
|
id, work_item_id, repository, trigger_type, status, started_at, finished_at, summary
|
|
283
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(
|
|
597
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(run2.id, run2.workItemId, run2.repository, run2.triggerType, run2.status, run2.startedAt, run2.finishedAt, run2.summary);
|
|
284
598
|
}
|
|
285
599
|
function listAutomationRuns(db, limit = 100) {
|
|
286
600
|
return db.prepare(`SELECT
|
|
@@ -832,7 +1146,7 @@ var require_serve_static2 = __commonJS((exports, module) => {
|
|
|
832
1146
|
});
|
|
833
1147
|
module.exports = __toCommonJS(serve_static_exports);
|
|
834
1148
|
var import_promises2 = __require("fs/promises");
|
|
835
|
-
var
|
|
1149
|
+
var import_node_path3 = __require("path");
|
|
836
1150
|
var import_serve_static = require_serve_static();
|
|
837
1151
|
var serveStatic2 = (options) => {
|
|
838
1152
|
return async function serveStatic22(c, next) {
|
|
@@ -851,7 +1165,7 @@ var require_serve_static2 = __commonJS((exports, module) => {
|
|
|
851
1165
|
return (0, import_serve_static.serveStatic)({
|
|
852
1166
|
...options,
|
|
853
1167
|
getContent,
|
|
854
|
-
join:
|
|
1168
|
+
join: import_node_path3.join,
|
|
855
1169
|
isDir
|
|
856
1170
|
})(c, next);
|
|
857
1171
|
};
|
|
@@ -1065,10 +1379,10 @@ var require_concurrent = __commonJS((exports, module) => {
|
|
|
1065
1379
|
};
|
|
1066
1380
|
}
|
|
1067
1381
|
const pool = /* @__PURE__ */ new Set;
|
|
1068
|
-
const
|
|
1382
|
+
const run2 = async (fn, promise, resolve) => {
|
|
1069
1383
|
if (pool.size >= concurrency) {
|
|
1070
|
-
promise ||= new Promise((r) =>
|
|
1071
|
-
setTimeout(() =>
|
|
1384
|
+
promise ||= new Promise((r) => resolve = r);
|
|
1385
|
+
setTimeout(() => run2(fn, promise, resolve));
|
|
1072
1386
|
return promise;
|
|
1073
1387
|
}
|
|
1074
1388
|
const marker = {};
|
|
@@ -1079,14 +1393,14 @@ var require_concurrent = __commonJS((exports, module) => {
|
|
|
1079
1393
|
} else {
|
|
1080
1394
|
pool.delete(marker);
|
|
1081
1395
|
}
|
|
1082
|
-
if (
|
|
1083
|
-
|
|
1396
|
+
if (resolve) {
|
|
1397
|
+
resolve(result);
|
|
1084
1398
|
return promise;
|
|
1085
1399
|
} else {
|
|
1086
1400
|
return result;
|
|
1087
1401
|
}
|
|
1088
1402
|
};
|
|
1089
|
-
return { run };
|
|
1403
|
+
return { run: run2 };
|
|
1090
1404
|
};
|
|
1091
1405
|
});
|
|
1092
1406
|
|
|
@@ -1297,7 +1611,7 @@ var require_middleware = __commonJS((exports, module) => {
|
|
|
1297
1611
|
ssgParams: () => ssgParams2
|
|
1298
1612
|
});
|
|
1299
1613
|
module.exports = __toCommonJS(middleware_exports);
|
|
1300
|
-
var
|
|
1614
|
+
var import_utils3 = require_utils2();
|
|
1301
1615
|
var SSG_CONTEXT = "HONO_SSG_CONTEXT";
|
|
1302
1616
|
var X_HONO_DISABLE_SSG_HEADER_KEY2 = "x-hono-disable-ssg";
|
|
1303
1617
|
var SSG_DISABLED_RESPONSE = (() => {
|
|
@@ -1311,7 +1625,7 @@ var require_middleware = __commonJS((exports, module) => {
|
|
|
1311
1625
|
}
|
|
1312
1626
|
})();
|
|
1313
1627
|
var ssgParams2 = (params) => async (c, next) => {
|
|
1314
|
-
if ((0,
|
|
1628
|
+
if ((0, import_utils3.isDynamicRoute)(c.req.path)) {
|
|
1315
1629
|
c.req.raw.ssgParams = Array.isArray(params) ? params : await params(c);
|
|
1316
1630
|
return c.notFound();
|
|
1317
1631
|
}
|
|
@@ -1496,10 +1810,10 @@ var require_html2 = __commonJS((exports, module) => {
|
|
|
1496
1810
|
var html_exports = {};
|
|
1497
1811
|
__export2(html_exports, {
|
|
1498
1812
|
html: () => html,
|
|
1499
|
-
raw: () =>
|
|
1813
|
+
raw: () => import_html3.raw
|
|
1500
1814
|
});
|
|
1501
1815
|
module.exports = __toCommonJS(html_exports);
|
|
1502
|
-
var
|
|
1816
|
+
var import_html3 = require_html();
|
|
1503
1817
|
var html = (strings, ...values) => {
|
|
1504
1818
|
const buffer = [""];
|
|
1505
1819
|
for (let i = 0, len = strings.length - 1;i < len; i++) {
|
|
@@ -1508,7 +1822,7 @@ var require_html2 = __commonJS((exports, module) => {
|
|
|
1508
1822
|
for (let i2 = 0, len2 = children.length;i2 < len2; i2++) {
|
|
1509
1823
|
const child = children[i2];
|
|
1510
1824
|
if (typeof child === "string") {
|
|
1511
|
-
(0,
|
|
1825
|
+
(0, import_html3.escapeToBuffer)(child, buffer);
|
|
1512
1826
|
} else if (typeof child === "number") {
|
|
1513
1827
|
buffer[0] += child;
|
|
1514
1828
|
} else if (typeof child === "boolean" || child === null || child === undefined) {
|
|
@@ -1527,12 +1841,12 @@ var require_html2 = __commonJS((exports, module) => {
|
|
|
1527
1841
|
} else if (child instanceof Promise) {
|
|
1528
1842
|
buffer.unshift("", child);
|
|
1529
1843
|
} else {
|
|
1530
|
-
(0,
|
|
1844
|
+
(0, import_html3.escapeToBuffer)(child.toString(), buffer);
|
|
1531
1845
|
}
|
|
1532
1846
|
}
|
|
1533
1847
|
}
|
|
1534
1848
|
buffer[0] += strings.at(-1);
|
|
1535
|
-
return buffer.length === 1 ? "callbacks" in buffer ? (0,
|
|
1849
|
+
return buffer.length === 1 ? "callbacks" in buffer ? (0, import_html3.raw)((0, import_html3.resolveCallbackSync)((0, import_html3.raw)(buffer[0], buffer.callbacks))) : (0, import_html3.raw)(buffer[0]) : (0, import_html3.stringBufferToString)(buffer, buffer.callbacks);
|
|
1536
1850
|
};
|
|
1537
1851
|
});
|
|
1538
1852
|
|
|
@@ -1561,7 +1875,7 @@ var require_plugins = __commonJS((exports, module) => {
|
|
|
1561
1875
|
redirectPlugin: () => redirectPlugin2
|
|
1562
1876
|
});
|
|
1563
1877
|
module.exports = __toCommonJS(plugins_exports);
|
|
1564
|
-
var
|
|
1878
|
+
var import_html3 = require_html2();
|
|
1565
1879
|
var defaultPlugin2 = () => {
|
|
1566
1880
|
return {
|
|
1567
1881
|
afterResponseHook: (res) => {
|
|
@@ -1574,7 +1888,7 @@ var require_plugins = __commonJS((exports, module) => {
|
|
|
1574
1888
|
};
|
|
1575
1889
|
var REDIRECT_STATUS_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
1576
1890
|
var generateRedirectHtml = (location) => {
|
|
1577
|
-
const content =
|
|
1891
|
+
const content = import_html3.html`<!DOCTYPE html>
|
|
1578
1892
|
<title>Redirecting to: ${location}</title>
|
|
1579
1893
|
<meta http-equiv="refresh" content="0;url=${location}" />
|
|
1580
1894
|
<meta name="robots" content="noindex" />
|
|
@@ -1636,27 +1950,27 @@ var require_ssg = __commonJS((exports, module) => {
|
|
|
1636
1950
|
toSSG: () => toSSG2
|
|
1637
1951
|
});
|
|
1638
1952
|
module.exports = __toCommonJS(ssg_exports);
|
|
1639
|
-
var
|
|
1953
|
+
var import_utils3 = require_utils();
|
|
1640
1954
|
var import_concurrent = require_concurrent();
|
|
1641
1955
|
var import_mime = require_mime();
|
|
1642
1956
|
var import_middleware = require_middleware();
|
|
1643
1957
|
var import_plugins = require_plugins();
|
|
1644
|
-
var
|
|
1958
|
+
var import_utils22 = require_utils2();
|
|
1645
1959
|
var DEFAULT_CONCURRENCY = 2;
|
|
1646
1960
|
var DEFAULT_CONTENT_TYPE = "text/plain";
|
|
1647
1961
|
var DEFAULT_OUTPUT_DIR = "./static";
|
|
1648
1962
|
var generateFilePath = (routePath, outDir, mimeType, extensionMap) => {
|
|
1649
1963
|
const extension = determineExtension(mimeType, extensionMap);
|
|
1650
1964
|
if (routePath.endsWith(`.${extension}`)) {
|
|
1651
|
-
return (0,
|
|
1965
|
+
return (0, import_utils22.joinPaths)(outDir, routePath);
|
|
1652
1966
|
}
|
|
1653
1967
|
if (routePath === "/") {
|
|
1654
|
-
return (0,
|
|
1968
|
+
return (0, import_utils22.joinPaths)(outDir, `index.${extension}`);
|
|
1655
1969
|
}
|
|
1656
1970
|
if (routePath.endsWith("/")) {
|
|
1657
|
-
return (0,
|
|
1971
|
+
return (0, import_utils22.joinPaths)(outDir, routePath, `index.${extension}`);
|
|
1658
1972
|
}
|
|
1659
|
-
return (0,
|
|
1973
|
+
return (0, import_utils22.joinPaths)(outDir, `${routePath}.${extension}`);
|
|
1660
1974
|
};
|
|
1661
1975
|
var parseResponseContent = async (response) => {
|
|
1662
1976
|
const contentType = response.headers.get("Content-Type");
|
|
@@ -1732,7 +2046,7 @@ var require_ssg = __commonJS((exports, module) => {
|
|
|
1732
2046
|
var fetchRoutesContent = function* (app, beforeRequestHook, afterResponseHook, concurrency) {
|
|
1733
2047
|
const baseURL = "http://localhost";
|
|
1734
2048
|
const pool = (0, import_concurrent.createPool)({ concurrency });
|
|
1735
|
-
for (const route of (0,
|
|
2049
|
+
for (const route of (0, import_utils22.filterStaticGenerateRoutes)(app)) {
|
|
1736
2050
|
const thisRouteBaseURL = new URL(route.path, baseURL).toString();
|
|
1737
2051
|
let forGetInfoURLRequest = new Request(thisRouteBaseURL);
|
|
1738
2052
|
yield new Promise(async (resolveGetInfo, rejectGetInfo) => {
|
|
@@ -1747,7 +2061,7 @@ var require_ssg = __commonJS((exports, module) => {
|
|
|
1747
2061
|
}
|
|
1748
2062
|
await pool.run(() => app.fetch(forGetInfoURLRequest, { [import_middleware.SSG_CONTEXT]: true }));
|
|
1749
2063
|
if (!forGetInfoURLRequest.ssgParams) {
|
|
1750
|
-
if ((0,
|
|
2064
|
+
if ((0, import_utils22.isDynamicRoute)(route.path)) {
|
|
1751
2065
|
resolveGetInfo(undefined);
|
|
1752
2066
|
return;
|
|
1753
2067
|
}
|
|
@@ -1761,7 +2075,7 @@ var require_ssg = __commonJS((exports, module) => {
|
|
|
1761
2075
|
for (const param of forGetInfoURLRequest.ssgParams) {
|
|
1762
2076
|
yield new Promise(async (resolveReq, rejectReq) => {
|
|
1763
2077
|
try {
|
|
1764
|
-
const replacedUrlParam = (0,
|
|
2078
|
+
const replacedUrlParam = (0, import_utils3.replaceUrlParam)(route.path, param);
|
|
1765
2079
|
let response = await pool.run(() => app.request(replacedUrlParam, requestInit, {
|
|
1766
2080
|
[import_middleware.SSG_CONTEXT]: true
|
|
1767
2081
|
}));
|
|
@@ -1804,7 +2118,7 @@ var require_ssg = __commonJS((exports, module) => {
|
|
|
1804
2118
|
}
|
|
1805
2119
|
const { routePath, content, mimeType } = awaitedData;
|
|
1806
2120
|
const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap);
|
|
1807
|
-
const dirPath = (0,
|
|
2121
|
+
const dirPath = (0, import_utils22.dirname)(filePath);
|
|
1808
2122
|
if (!createdDirs.has(dirPath)) {
|
|
1809
2123
|
await fsModule.mkdir(dirPath, { recursive: true });
|
|
1810
2124
|
createdDirs.add(dirPath);
|
|
@@ -8779,7 +9093,7 @@ class Protocol {
|
|
|
8779
9093
|
return;
|
|
8780
9094
|
}
|
|
8781
9095
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1000;
|
|
8782
|
-
await new Promise((
|
|
9096
|
+
await new Promise((resolve2) => setTimeout(resolve2, pollInterval));
|
|
8783
9097
|
options?.signal?.throwIfAborted();
|
|
8784
9098
|
}
|
|
8785
9099
|
} catch (error2) {
|
|
@@ -8791,7 +9105,7 @@ class Protocol {
|
|
|
8791
9105
|
}
|
|
8792
9106
|
request(request, resultSchema, options) {
|
|
8793
9107
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
8794
|
-
return new Promise((
|
|
9108
|
+
return new Promise((resolve2, reject) => {
|
|
8795
9109
|
const earlyReject = (error2) => {
|
|
8796
9110
|
reject(error2);
|
|
8797
9111
|
};
|
|
@@ -8869,7 +9183,7 @@ class Protocol {
|
|
|
8869
9183
|
if (!parseResult.success) {
|
|
8870
9184
|
reject(parseResult.error);
|
|
8871
9185
|
} else {
|
|
8872
|
-
|
|
9186
|
+
resolve2(parseResult.data);
|
|
8873
9187
|
}
|
|
8874
9188
|
} catch (error2) {
|
|
8875
9189
|
reject(error2);
|
|
@@ -9060,12 +9374,12 @@ class Protocol {
|
|
|
9060
9374
|
interval = task.pollInterval;
|
|
9061
9375
|
}
|
|
9062
9376
|
} catch {}
|
|
9063
|
-
return new Promise((
|
|
9377
|
+
return new Promise((resolve2, reject) => {
|
|
9064
9378
|
if (signal.aborted) {
|
|
9065
9379
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
9066
9380
|
return;
|
|
9067
9381
|
}
|
|
9068
|
-
const timeoutId = setTimeout(
|
|
9382
|
+
const timeoutId = setTimeout(resolve2, interval);
|
|
9069
9383
|
signal.addEventListener("abort", () => {
|
|
9070
9384
|
clearTimeout(timeoutId);
|
|
9071
9385
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -12050,7 +12364,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
12050
12364
|
const schOrFunc = root.refs[ref];
|
|
12051
12365
|
if (schOrFunc)
|
|
12052
12366
|
return schOrFunc;
|
|
12053
|
-
let _sch =
|
|
12367
|
+
let _sch = resolve2.call(this, root, ref);
|
|
12054
12368
|
if (_sch === undefined) {
|
|
12055
12369
|
const schema = (_a2 = root.localRefs) === null || _a2 === undefined ? undefined : _a2[ref];
|
|
12056
12370
|
const { schemaId } = this.opts;
|
|
@@ -12077,7 +12391,7 @@ var require_compile = __commonJS((exports) => {
|
|
|
12077
12391
|
function sameSchemaEnv(s1, s2) {
|
|
12078
12392
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
12079
12393
|
}
|
|
12080
|
-
function
|
|
12394
|
+
function resolve2(root, ref) {
|
|
12081
12395
|
let sch;
|
|
12082
12396
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
12083
12397
|
ref = sch;
|
|
@@ -12607,7 +12921,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
12607
12921
|
}
|
|
12608
12922
|
return uri;
|
|
12609
12923
|
}
|
|
12610
|
-
function
|
|
12924
|
+
function resolve2(baseURI, relativeURI, options) {
|
|
12611
12925
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
12612
12926
|
const resolved = resolveComponent(parse7(baseURI, schemelessOptions), parse7(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
12613
12927
|
schemelessOptions.skipEscape = true;
|
|
@@ -12835,7 +13149,7 @@ var require_fast_uri = __commonJS((exports, module) => {
|
|
|
12835
13149
|
var fastUri = {
|
|
12836
13150
|
SCHEMES,
|
|
12837
13151
|
normalize,
|
|
12838
|
-
resolve:
|
|
13152
|
+
resolve: resolve2,
|
|
12839
13153
|
resolveComponent,
|
|
12840
13154
|
equal,
|
|
12841
13155
|
serialize,
|
|
@@ -16218,12 +16532,12 @@ class StdioServerTransport {
|
|
|
16218
16532
|
this.onclose?.();
|
|
16219
16533
|
}
|
|
16220
16534
|
send(message) {
|
|
16221
|
-
return new Promise((
|
|
16535
|
+
return new Promise((resolve2) => {
|
|
16222
16536
|
const json = serializeMessage(message);
|
|
16223
16537
|
if (this._stdout.write(json)) {
|
|
16224
|
-
|
|
16538
|
+
resolve2();
|
|
16225
16539
|
} else {
|
|
16226
|
-
this._stdout.once("drain",
|
|
16540
|
+
this._stdout.once("drain", resolve2);
|
|
16227
16541
|
}
|
|
16228
16542
|
});
|
|
16229
16543
|
}
|
|
@@ -18011,8 +18325,150 @@ var cors = (options) => {
|
|
|
18011
18325
|
};
|
|
18012
18326
|
};
|
|
18013
18327
|
|
|
18328
|
+
// ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/utils/stream.js
|
|
18329
|
+
var StreamingApi = class {
|
|
18330
|
+
writer;
|
|
18331
|
+
encoder;
|
|
18332
|
+
writable;
|
|
18333
|
+
abortSubscribers = [];
|
|
18334
|
+
responseReadable;
|
|
18335
|
+
aborted = false;
|
|
18336
|
+
closed = false;
|
|
18337
|
+
constructor(writable, _readable) {
|
|
18338
|
+
this.writable = writable;
|
|
18339
|
+
this.writer = writable.getWriter();
|
|
18340
|
+
this.encoder = new TextEncoder;
|
|
18341
|
+
const reader = _readable.getReader();
|
|
18342
|
+
this.abortSubscribers.push(async () => {
|
|
18343
|
+
await reader.cancel();
|
|
18344
|
+
});
|
|
18345
|
+
this.responseReadable = new ReadableStream({
|
|
18346
|
+
async pull(controller) {
|
|
18347
|
+
const { done, value } = await reader.read();
|
|
18348
|
+
done ? controller.close() : controller.enqueue(value);
|
|
18349
|
+
},
|
|
18350
|
+
cancel: () => {
|
|
18351
|
+
this.abort();
|
|
18352
|
+
}
|
|
18353
|
+
});
|
|
18354
|
+
}
|
|
18355
|
+
async write(input) {
|
|
18356
|
+
try {
|
|
18357
|
+
if (typeof input === "string") {
|
|
18358
|
+
input = this.encoder.encode(input);
|
|
18359
|
+
}
|
|
18360
|
+
await this.writer.write(input);
|
|
18361
|
+
} catch {}
|
|
18362
|
+
return this;
|
|
18363
|
+
}
|
|
18364
|
+
async writeln(input) {
|
|
18365
|
+
await this.write(input + `
|
|
18366
|
+
`);
|
|
18367
|
+
return this;
|
|
18368
|
+
}
|
|
18369
|
+
sleep(ms) {
|
|
18370
|
+
return new Promise((res) => setTimeout(res, ms));
|
|
18371
|
+
}
|
|
18372
|
+
async close() {
|
|
18373
|
+
try {
|
|
18374
|
+
await this.writer.close();
|
|
18375
|
+
} catch {}
|
|
18376
|
+
this.closed = true;
|
|
18377
|
+
}
|
|
18378
|
+
async pipe(body) {
|
|
18379
|
+
this.writer.releaseLock();
|
|
18380
|
+
await body.pipeTo(this.writable, { preventClose: true });
|
|
18381
|
+
this.writer = this.writable.getWriter();
|
|
18382
|
+
}
|
|
18383
|
+
onAbort(listener) {
|
|
18384
|
+
this.abortSubscribers.push(listener);
|
|
18385
|
+
}
|
|
18386
|
+
abort() {
|
|
18387
|
+
if (!this.aborted) {
|
|
18388
|
+
this.aborted = true;
|
|
18389
|
+
this.abortSubscribers.forEach((subscriber) => subscriber());
|
|
18390
|
+
}
|
|
18391
|
+
}
|
|
18392
|
+
};
|
|
18393
|
+
|
|
18394
|
+
// ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/helper/streaming/utils.js
|
|
18395
|
+
var isOldBunVersion = () => {
|
|
18396
|
+
const version = typeof Bun !== "undefined" ? Bun.version : undefined;
|
|
18397
|
+
if (version === undefined) {
|
|
18398
|
+
return false;
|
|
18399
|
+
}
|
|
18400
|
+
const result = version.startsWith("1.1") || version.startsWith("1.0") || version.startsWith("0.");
|
|
18401
|
+
isOldBunVersion = () => result;
|
|
18402
|
+
return result;
|
|
18403
|
+
};
|
|
18404
|
+
|
|
18405
|
+
// ../../node_modules/.bun/hono@4.12.10/node_modules/hono/dist/helper/streaming/sse.js
|
|
18406
|
+
var SSEStreamingApi = class extends StreamingApi {
|
|
18407
|
+
constructor(writable, readable) {
|
|
18408
|
+
super(writable, readable);
|
|
18409
|
+
}
|
|
18410
|
+
async writeSSE(message) {
|
|
18411
|
+
const data = await resolveCallback(message.data, HtmlEscapedCallbackPhase.Stringify, false, {});
|
|
18412
|
+
const dataLines = data.split(/\r\n|\r|\n/).map((line) => {
|
|
18413
|
+
return `data: ${line}`;
|
|
18414
|
+
}).join(`
|
|
18415
|
+
`);
|
|
18416
|
+
for (const key of ["event", "id", "retry"]) {
|
|
18417
|
+
if (message[key] && /[\r\n]/.test(message[key])) {
|
|
18418
|
+
throw new Error(`${key} must not contain "\\r" or "\\n"`);
|
|
18419
|
+
}
|
|
18420
|
+
}
|
|
18421
|
+
const sseData = [
|
|
18422
|
+
message.event && `event: ${message.event}`,
|
|
18423
|
+
dataLines,
|
|
18424
|
+
message.id && `id: ${message.id}`,
|
|
18425
|
+
message.retry && `retry: ${message.retry}`
|
|
18426
|
+
].filter(Boolean).join(`
|
|
18427
|
+
`) + `
|
|
18428
|
+
|
|
18429
|
+
`;
|
|
18430
|
+
await this.write(sseData);
|
|
18431
|
+
}
|
|
18432
|
+
};
|
|
18433
|
+
var run = async (stream, cb, onError) => {
|
|
18434
|
+
try {
|
|
18435
|
+
await cb(stream);
|
|
18436
|
+
} catch (e) {
|
|
18437
|
+
if (e instanceof Error && onError) {
|
|
18438
|
+
await onError(e, stream);
|
|
18439
|
+
await stream.writeSSE({
|
|
18440
|
+
event: "error",
|
|
18441
|
+
data: e.message
|
|
18442
|
+
});
|
|
18443
|
+
} else {
|
|
18444
|
+
console.error(e);
|
|
18445
|
+
}
|
|
18446
|
+
} finally {
|
|
18447
|
+
stream.close();
|
|
18448
|
+
}
|
|
18449
|
+
};
|
|
18450
|
+
var contextStash = /* @__PURE__ */ new WeakMap;
|
|
18451
|
+
var streamSSE = (c, cb, onError) => {
|
|
18452
|
+
const { readable, writable } = new TransformStream;
|
|
18453
|
+
const stream = new SSEStreamingApi(writable, readable);
|
|
18454
|
+
if (isOldBunVersion()) {
|
|
18455
|
+
c.req.raw.signal.addEventListener("abort", () => {
|
|
18456
|
+
if (!stream.closed) {
|
|
18457
|
+
stream.abort();
|
|
18458
|
+
}
|
|
18459
|
+
});
|
|
18460
|
+
}
|
|
18461
|
+
contextStash.set(stream.responseReadable, c);
|
|
18462
|
+
c.header("Transfer-Encoding", "chunked");
|
|
18463
|
+
c.header("Content-Type", "text/event-stream");
|
|
18464
|
+
c.header("Cache-Control", "no-cache");
|
|
18465
|
+
c.header("Connection", "keep-alive");
|
|
18466
|
+
run(stream, cb, onError);
|
|
18467
|
+
return c.newResponse(stream.responseReadable);
|
|
18468
|
+
};
|
|
18469
|
+
|
|
18014
18470
|
// src/server.ts
|
|
18015
|
-
import { join as join3, resolve
|
|
18471
|
+
import { join as join3, resolve } from "path";
|
|
18016
18472
|
|
|
18017
18473
|
// src/lib/automation.ts
|
|
18018
18474
|
init_src();
|
|
@@ -18596,7 +19052,7 @@ function isTriggerDisabled(config, repository, workItem, trigger) {
|
|
|
18596
19052
|
}
|
|
18597
19053
|
function runCommand(command, args, options = {}) {
|
|
18598
19054
|
const timeoutMs = options.timeoutMs ?? 300000;
|
|
18599
|
-
return new Promise((
|
|
19055
|
+
return new Promise((resolve, reject) => {
|
|
18600
19056
|
const child = spawn(command, args, {
|
|
18601
19057
|
cwd: options.cwd,
|
|
18602
19058
|
env: {
|
|
@@ -18631,7 +19087,7 @@ function runCommand(command, args, options = {}) {
|
|
|
18631
19087
|
});
|
|
18632
19088
|
child.on("close", (exitCode) => {
|
|
18633
19089
|
clearTimeout(timeout);
|
|
18634
|
-
|
|
19090
|
+
resolve({
|
|
18635
19091
|
exitCode: exitCode ?? 1,
|
|
18636
19092
|
stdout: stdout.trim(),
|
|
18637
19093
|
stderr: stderr.trim()
|
|
@@ -18787,7 +19243,7 @@ var cachedLogin = null;
|
|
|
18787
19243
|
var cachedAt = 0;
|
|
18788
19244
|
var inFlight = null;
|
|
18789
19245
|
function runGhCommand(args) {
|
|
18790
|
-
return new Promise((
|
|
19246
|
+
return new Promise((resolve, reject) => {
|
|
18791
19247
|
const child = spawn2("gh", args, {
|
|
18792
19248
|
stdio: ["ignore", "pipe", "pipe"]
|
|
18793
19249
|
});
|
|
@@ -18801,7 +19257,7 @@ function runGhCommand(args) {
|
|
|
18801
19257
|
});
|
|
18802
19258
|
child.on("error", reject);
|
|
18803
19259
|
child.on("close", (exitCode) => {
|
|
18804
|
-
|
|
19260
|
+
resolve({
|
|
18805
19261
|
exitCode: exitCode ?? 1,
|
|
18806
19262
|
stdout: stdout.trim(),
|
|
18807
19263
|
stderr: stderr.trim()
|
|
@@ -18840,6 +19296,9 @@ var ConfigSchema = object({
|
|
|
18840
19296
|
controlPlaneUrl: pipe(string(), minLength(1)),
|
|
18841
19297
|
authorLogin: optional(string()),
|
|
18842
19298
|
internalApiToken: optional(string()),
|
|
19299
|
+
githubAppId: optional(pipe(string(), minLength(1))),
|
|
19300
|
+
githubPrivateKey: optional(pipe(string(), minLength(1))),
|
|
19301
|
+
githubApiUrl: optional(pipe(string(), minLength(1))),
|
|
18843
19302
|
syncIntervalSeconds: number(),
|
|
18844
19303
|
automationPollSeconds: number(),
|
|
18845
19304
|
runner: optional(object({
|
|
@@ -18877,7 +19336,7 @@ var ConfigSchema = object({
|
|
|
18877
19336
|
function defaultConfig() {
|
|
18878
19337
|
return {
|
|
18879
19338
|
controlPlaneUrl: "http://127.0.0.1:4130",
|
|
18880
|
-
syncIntervalSeconds:
|
|
19339
|
+
syncIntervalSeconds: 300,
|
|
18881
19340
|
automationPollSeconds: 20,
|
|
18882
19341
|
repositories: [],
|
|
18883
19342
|
pullRequestOverrides: []
|
|
@@ -18905,6 +19364,9 @@ function normalizeConfig(config) {
|
|
|
18905
19364
|
syncIntervalSeconds: config.syncIntervalSeconds,
|
|
18906
19365
|
automationPollSeconds: config.automationPollSeconds,
|
|
18907
19366
|
...config.internalApiToken ? { internalApiToken: config.internalApiToken } : {},
|
|
19367
|
+
...config.githubAppId ? { githubAppId: config.githubAppId } : {},
|
|
19368
|
+
...config.githubPrivateKey ? { githubPrivateKey: config.githubPrivateKey } : {},
|
|
19369
|
+
...config.githubApiUrl ? { githubApiUrl: config.githubApiUrl } : {},
|
|
18908
19370
|
repositories: config.repositories.map((repository) => ({
|
|
18909
19371
|
repository: repository.repository,
|
|
18910
19372
|
path: repository.path,
|
|
@@ -18942,6 +19404,35 @@ function withDetectedAuthorLogin(config, detectedAuthorLogin) {
|
|
|
18942
19404
|
// src/server.ts
|
|
18943
19405
|
init_local_db();
|
|
18944
19406
|
|
|
19407
|
+
// src/lib/github-sync.ts
|
|
19408
|
+
init_src();
|
|
19409
|
+
init_local_db();
|
|
19410
|
+
function toGitHubAppConfig(config) {
|
|
19411
|
+
if (!config.githubAppId || !config.githubPrivateKey) {
|
|
19412
|
+
throw new Error("GitHub App is not configured. Set githubAppId and githubPrivateKey in config.");
|
|
19413
|
+
}
|
|
19414
|
+
return {
|
|
19415
|
+
githubAppId: config.githubAppId,
|
|
19416
|
+
githubPrivateKey: config.githubPrivateKey,
|
|
19417
|
+
githubApiUrl: config.githubApiUrl ?? "https://api.github.com"
|
|
19418
|
+
};
|
|
19419
|
+
}
|
|
19420
|
+
async function syncAuthorPrs(db, config) {
|
|
19421
|
+
const appConfig = toGitHubAppConfig(config);
|
|
19422
|
+
if (!config.authorLogin) {
|
|
19423
|
+
throw new Error("authorLogin is not configured.");
|
|
19424
|
+
}
|
|
19425
|
+
const installations = await listAppInstallations(appConfig);
|
|
19426
|
+
const allItems = await Promise.all(installations.map((inst) => fetchOpenPullRequestsForAuthor(appConfig, inst.id, config.authorLogin)));
|
|
19427
|
+
const items = allItems.flat();
|
|
19428
|
+
for (const item of items) {
|
|
19429
|
+
const { alerts } = deriveAlerts(item);
|
|
19430
|
+
item.alerts = alerts;
|
|
19431
|
+
}
|
|
19432
|
+
upsertWorkItems(db, items);
|
|
19433
|
+
return { synchronized: items.length };
|
|
19434
|
+
}
|
|
19435
|
+
|
|
18945
19436
|
// src/lib/sync.ts
|
|
18946
19437
|
init_local_db();
|
|
18947
19438
|
async function syncFromControlPlane(db, config) {
|
|
@@ -18965,6 +19456,19 @@ async function syncFromControlPlane(db, config) {
|
|
|
18965
19456
|
}
|
|
18966
19457
|
|
|
18967
19458
|
// src/server.ts
|
|
19459
|
+
function createEventBus() {
|
|
19460
|
+
const listeners = new Set;
|
|
19461
|
+
return {
|
|
19462
|
+
subscribe(listener) {
|
|
19463
|
+
listeners.add(listener);
|
|
19464
|
+
return () => listeners.delete(listener);
|
|
19465
|
+
},
|
|
19466
|
+
broadcast(event, data) {
|
|
19467
|
+
for (const listener of listeners)
|
|
19468
|
+
listener(event, data);
|
|
19469
|
+
}
|
|
19470
|
+
};
|
|
19471
|
+
}
|
|
18968
19472
|
function runExclusive(guard, fn) {
|
|
18969
19473
|
if (guard.promise)
|
|
18970
19474
|
return guard.promise;
|
|
@@ -18974,13 +19478,30 @@ function runExclusive(guard, fn) {
|
|
|
18974
19478
|
return guard.promise;
|
|
18975
19479
|
}
|
|
18976
19480
|
function createLocalApp(db = openLocalDb()) {
|
|
19481
|
+
const bus = createEventBus();
|
|
18977
19482
|
const state = {
|
|
18978
19483
|
db,
|
|
19484
|
+
bus,
|
|
18979
19485
|
syncGuard: { promise: null },
|
|
18980
19486
|
workerGuard: { promise: null }
|
|
18981
19487
|
};
|
|
18982
19488
|
const app = new Hono2;
|
|
18983
19489
|
app.use("/api/*", cors());
|
|
19490
|
+
app.get("/api/events", (c) => {
|
|
19491
|
+
return streamSSE(c, async (stream2) => {
|
|
19492
|
+
const unsubscribe = bus.subscribe((event, data) => {
|
|
19493
|
+
stream2.writeSSE({ event, data: JSON.stringify(data) });
|
|
19494
|
+
});
|
|
19495
|
+
const heartbeat = setInterval(() => {
|
|
19496
|
+
stream2.writeSSE({ event: "heartbeat", data: "{}" });
|
|
19497
|
+
}, 30000);
|
|
19498
|
+
stream2.onAbort(() => {
|
|
19499
|
+
clearInterval(heartbeat);
|
|
19500
|
+
unsubscribe();
|
|
19501
|
+
});
|
|
19502
|
+
await new Promise(() => {});
|
|
19503
|
+
});
|
|
19504
|
+
});
|
|
18984
19505
|
app.get("/api/health", (_c) => {
|
|
18985
19506
|
return Response.json({ ok: true });
|
|
18986
19507
|
});
|
|
@@ -19001,6 +19522,18 @@ function createLocalApp(db = openLocalDb()) {
|
|
|
19001
19522
|
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
19002
19523
|
}
|
|
19003
19524
|
});
|
|
19525
|
+
app.post("/api/sync/author", async (c) => {
|
|
19526
|
+
if (state.syncGuard.promise) {
|
|
19527
|
+
return c.json({ error: "sync already in progress" }, 409);
|
|
19528
|
+
}
|
|
19529
|
+
try {
|
|
19530
|
+
const config = await loadConfig();
|
|
19531
|
+
const result = await runExclusive(state.syncGuard, () => syncAuthorPrs(state.db, config));
|
|
19532
|
+
return c.json(result);
|
|
19533
|
+
} catch (error) {
|
|
19534
|
+
return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
|
|
19535
|
+
}
|
|
19536
|
+
});
|
|
19004
19537
|
app.get("/api/work-items", (c) => c.json(listWorkItems(state.db, parseWorkItemFilterFromQuery((key) => c.req.query(key)))));
|
|
19005
19538
|
app.get("/api/work-items/:id", (c) => {
|
|
19006
19539
|
const item = getWorkItem(state.db, c.req.param("id"));
|
|
@@ -19023,19 +19556,69 @@ function createLocalApp(db = openLocalDb()) {
|
|
|
19023
19556
|
return app;
|
|
19024
19557
|
}
|
|
19025
19558
|
async function runSync(state, config) {
|
|
19026
|
-
|
|
19559
|
+
const result = await runExclusive(state.syncGuard, async () => {
|
|
19027
19560
|
const currentConfig = config ?? await loadConfig();
|
|
19028
19561
|
return syncFromControlPlane(state.db, currentConfig);
|
|
19029
19562
|
});
|
|
19563
|
+
state.bus.broadcast("sync_completed", {});
|
|
19564
|
+
return result;
|
|
19030
19565
|
}
|
|
19031
19566
|
async function runWorker(state, config) {
|
|
19032
|
-
|
|
19567
|
+
const result = await runExclusive(state.workerGuard, async () => {
|
|
19033
19568
|
const currentConfig = config ?? await loadConfig();
|
|
19034
19569
|
return processNextAutomationJob(state.db, currentConfig, "ops-harbor-local");
|
|
19035
19570
|
});
|
|
19571
|
+
state.bus.broadcast("automation_completed", {});
|
|
19572
|
+
return result;
|
|
19036
19573
|
}
|
|
19037
19574
|
function delay(ms) {
|
|
19038
|
-
return new Promise((
|
|
19575
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
19576
|
+
}
|
|
19577
|
+
function subscribeToControlPlane(state) {
|
|
19578
|
+
const connect = async () => {
|
|
19579
|
+
try {
|
|
19580
|
+
const config = await loadConfig();
|
|
19581
|
+
const url = `${config.controlPlaneUrl}/api/events`;
|
|
19582
|
+
const headers = { Accept: "text/event-stream" };
|
|
19583
|
+
if (config.internalApiToken) {
|
|
19584
|
+
headers["x-ops-harbor-token"] = config.internalApiToken;
|
|
19585
|
+
}
|
|
19586
|
+
const response = await fetch(url, { headers });
|
|
19587
|
+
if (!response.ok || !response.body) {
|
|
19588
|
+
throw new Error(`CP SSE connection failed: ${String(response.status)}`);
|
|
19589
|
+
}
|
|
19590
|
+
const reader = response.body.getReader();
|
|
19591
|
+
const decoder = new TextDecoder;
|
|
19592
|
+
let buffer = "";
|
|
19593
|
+
while (true) {
|
|
19594
|
+
const { done, value } = await reader.read();
|
|
19595
|
+
if (done)
|
|
19596
|
+
break;
|
|
19597
|
+
buffer += decoder.decode(value, { stream: true });
|
|
19598
|
+
const lines = buffer.split(`
|
|
19599
|
+
`);
|
|
19600
|
+
buffer = lines.pop() ?? "";
|
|
19601
|
+
let currentEvent = "";
|
|
19602
|
+
for (const line of lines) {
|
|
19603
|
+
if (line.startsWith("event: ")) {
|
|
19604
|
+
currentEvent = line.slice(7).trim();
|
|
19605
|
+
} else if (line.startsWith("data: ") && currentEvent) {
|
|
19606
|
+
if (currentEvent === "webhook_processed" || currentEvent === "sync_completed") {
|
|
19607
|
+
runSync(state);
|
|
19608
|
+
}
|
|
19609
|
+
currentEvent = "";
|
|
19610
|
+
} else if (line === "") {
|
|
19611
|
+
currentEvent = "";
|
|
19612
|
+
}
|
|
19613
|
+
}
|
|
19614
|
+
}
|
|
19615
|
+
} catch (error) {
|
|
19616
|
+
console.error("CP SSE error, reconnecting in 5s", error);
|
|
19617
|
+
}
|
|
19618
|
+
await delay(5000);
|
|
19619
|
+
subscribeToControlPlane(state);
|
|
19620
|
+
};
|
|
19621
|
+
connect();
|
|
19039
19622
|
}
|
|
19040
19623
|
async function startBackgroundLoops(state) {
|
|
19041
19624
|
const runSyncLoop = async () => {
|
|
@@ -19064,10 +19647,11 @@ async function startBackgroundLoops(state) {
|
|
|
19064
19647
|
};
|
|
19065
19648
|
runSyncLoop();
|
|
19066
19649
|
runWorkerLoop();
|
|
19650
|
+
subscribeToControlPlane(state);
|
|
19067
19651
|
}
|
|
19068
19652
|
function createServedApp() {
|
|
19069
19653
|
const { serveStatic: serveStatic2 } = require_bun();
|
|
19070
|
-
const distDir = import.meta.dir.endsWith("/src") ?
|
|
19654
|
+
const distDir = import.meta.dir.endsWith("/src") ? resolve(import.meta.dir, "../dist") : import.meta.dir;
|
|
19071
19655
|
const clientDir = join3(distDir, "client");
|
|
19072
19656
|
const app = createLocalApp();
|
|
19073
19657
|
app.use("/*", serveStatic2({ root: clientDir }));
|