@getpaseo/server 0.1.58 → 0.1.60
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/scripts/dev-runner.js +26 -7
- package/dist/scripts/dev-runner.js.map +1 -1
- package/dist/server/client/daemon-client-runtime-metrics.d.ts +39 -0
- package/dist/server/client/daemon-client-runtime-metrics.d.ts.map +1 -0
- package/dist/server/client/daemon-client-runtime-metrics.js +173 -0
- package/dist/server/client/daemon-client-runtime-metrics.js.map +1 -0
- package/dist/server/client/daemon-client.d.ts +58 -9
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +151 -10
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +55 -48
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +541 -331
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.d.ts +3 -2
- package/dist/server/server/agent/agent-metadata-generator.d.ts.map +1 -1
- package/dist/server/server/agent/agent-metadata-generator.js +31 -16
- package/dist/server/server/agent/agent-metadata-generator.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts +2 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +29 -1
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +9 -5
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +76 -69
- package/dist/server/server/agent/agent-storage.d.ts.map +1 -1
- package/dist/server/server/agent/agent-storage.js +13 -6
- package/dist/server/server/agent/agent-storage.js.map +1 -1
- package/dist/server/server/agent/agent-stream-coalescer.d.ts +41 -0
- package/dist/server/server/agent/agent-stream-coalescer.d.ts.map +1 -0
- package/dist/server/server/agent/agent-stream-coalescer.js +166 -0
- package/dist/server/server/agent/agent-stream-coalescer.js.map +1 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts +54 -0
- package/dist/server/server/agent/agent-timeline-store-types.d.ts.map +1 -0
- package/dist/server/server/agent/agent-timeline-store-types.js +2 -0
- package/dist/server/server/agent/agent-timeline-store-types.js.map +1 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts +32 -0
- package/dist/server/server/agent/agent-timeline-store.d.ts.map +1 -0
- package/dist/server/server/agent/agent-timeline-store.js +245 -0
- package/dist/server/server/agent/agent-timeline-store.js.map +1 -0
- package/dist/server/server/agent/mcp-server.d.ts +12 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +276 -65
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/mcp-shared.d.ts +196 -152
- package/dist/server/server/agent/mcp-shared.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-shared.js +40 -42
- package/dist/server/server/agent/mcp-shared.js.map +1 -1
- package/dist/server/server/agent/model-resolver.d.ts.map +1 -1
- package/dist/server/server/agent/model-resolver.js +3 -1
- package/dist/server/server/agent/model-resolver.js.map +1 -1
- package/dist/server/server/agent/prompt-attachments.d.ts +6 -0
- package/dist/server/server/agent/prompt-attachments.d.ts.map +1 -0
- package/dist/server/server/agent/prompt-attachments.js +31 -0
- package/dist/server/server/agent/prompt-attachments.js.map +1 -0
- package/dist/server/server/agent/provider-launch-config.d.ts +12 -10
- package/dist/server/server/agent/provider-launch-config.d.ts.map +1 -1
- package/dist/server/server/agent/provider-launch-config.js +34 -0
- package/dist/server/server/agent/provider-launch-config.js.map +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts +1 -0
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +22 -1
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts +5 -2
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +20 -9
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +17 -5
- package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.js +150 -61
- package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.d.ts +8 -4
- package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.js +73 -8
- package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/claude/claude-models.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/claude-models.js +21 -0
- package/dist/server/server/agent/providers/claude/claude-models.js.map +1 -1
- package/dist/server/server/agent/providers/claude/task-notification-tool-call.d.ts +2 -2
- package/dist/server/server/agent/providers/claude-agent.d.ts +1 -1
- package/dist/server/server/agent/providers/claude-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude-agent.js +15 -8
- package/dist/server/server/agent/providers/claude-agent.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +37 -4
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +61 -31
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/copilot-acp-agent.js +3 -2
- package/dist/server/server/agent/providers/copilot-acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts +64 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js +585 -0
- package/dist/server/server/agent/providers/mock-load-test-agent.js.map +1 -0
- package/dist/server/server/agent/providers/opencode-agent.d.ts +19 -4
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +227 -118
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts +69 -0
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/pi-direct-agent.js +1177 -0
- package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +7 -4
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.d.ts +13 -13
- package/dist/server/server/agent-attention-policy.d.ts.map +1 -1
- package/dist/server/server/agent-attention-policy.js +20 -36
- package/dist/server/server/agent-attention-policy.js.map +1 -1
- package/dist/server/server/bootstrap.d.ts +6 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +113 -11
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/chat/chat-rpc-schemas.d.ts +44 -44
- package/dist/server/server/chat/chat-types.d.ts +6 -6
- package/dist/server/server/checkout-diff-manager.d.ts +0 -1
- package/dist/server/server/checkout-diff-manager.d.ts.map +1 -1
- package/dist/server/server/checkout-diff-manager.js +6 -4
- package/dist/server/server/checkout-diff-manager.js.map +1 -1
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +1 -0
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/file-explorer/service.d.ts.map +1 -1
- package/dist/server/server/file-explorer/service.js +2 -1
- package/dist/server/server/file-explorer/service.js.map +1 -1
- package/dist/server/server/loop/rpc-schemas.d.ts +392 -392
- package/dist/server/server/loop-service.d.ts +52 -52
- package/dist/server/server/paseo-worktree-archive-service.d.ts +41 -0
- package/dist/server/server/paseo-worktree-archive-service.d.ts.map +1 -0
- package/dist/server/server/paseo-worktree-archive-service.js +137 -0
- package/dist/server/server/paseo-worktree-archive-service.js.map +1 -0
- package/dist/server/server/paseo-worktree-service.d.ts +24 -0
- package/dist/server/server/paseo-worktree-service.d.ts.map +1 -0
- package/dist/server/server/paseo-worktree-service.js +94 -0
- package/dist/server/server/paseo-worktree-service.js.map +1 -0
- package/dist/server/server/path-utils.d.ts +1 -0
- package/dist/server/server/path-utils.d.ts.map +1 -1
- package/dist/server/server/path-utils.js +9 -0
- package/dist/server/server/path-utils.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +73 -73
- package/dist/server/server/persistence-hooks.d.ts.map +1 -1
- package/dist/server/server/persistence-hooks.js +3 -0
- package/dist/server/server/persistence-hooks.js.map +1 -1
- package/dist/server/server/resolve-worktree-creation-intent.d.ts +30 -0
- package/dist/server/server/resolve-worktree-creation-intent.d.ts.map +1 -0
- package/dist/server/server/resolve-worktree-creation-intent.js +163 -0
- package/dist/server/server/resolve-worktree-creation-intent.js.map +1 -0
- package/dist/server/server/schedule/rpc-schemas.d.ts +192 -192
- package/dist/server/server/schedule/service.d.ts +1 -1
- package/dist/server/server/schedule/service.d.ts.map +1 -1
- package/dist/server/server/schedule/types.d.ts +44 -44
- package/dist/server/server/script-health-monitor.d.ts +39 -0
- package/dist/server/server/script-health-monitor.d.ts.map +1 -0
- package/dist/server/server/script-health-monitor.js +158 -0
- package/dist/server/server/script-health-monitor.js.map +1 -0
- package/dist/server/server/script-proxy.d.ts +40 -0
- package/dist/server/server/script-proxy.d.ts.map +1 -0
- package/dist/server/server/script-proxy.js +245 -0
- package/dist/server/server/script-proxy.js.map +1 -0
- package/dist/server/server/script-route-branch-handler.d.ts +10 -0
- package/dist/server/server/script-route-branch-handler.d.ts.map +1 -0
- package/dist/server/server/script-route-branch-handler.js +45 -0
- package/dist/server/server/script-route-branch-handler.js.map +1 -0
- package/dist/server/server/script-status-projection.d.ts +29 -0
- package/dist/server/server/script-status-projection.d.ts.map +1 -0
- package/dist/server/server/script-status-projection.js +133 -0
- package/dist/server/server/script-status-projection.js.map +1 -0
- package/dist/server/server/session.d.ts +77 -13
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +1290 -548
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts +27 -3
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +112 -29
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/workspace-archive-service.d.ts +8 -0
- package/dist/server/server/workspace-archive-service.d.ts.map +1 -0
- package/dist/server/server/workspace-archive-service.js +17 -0
- package/dist/server/server/workspace-archive-service.js.map +1 -0
- package/dist/server/server/workspace-git-metadata.d.ts +24 -0
- package/dist/server/server/workspace-git-metadata.d.ts.map +1 -0
- package/dist/server/server/workspace-git-metadata.js +78 -0
- package/dist/server/server/workspace-git-metadata.js.map +1 -0
- package/dist/server/server/workspace-git-service.d.ts +104 -5
- package/dist/server/server/workspace-git-service.d.ts.map +1 -1
- package/dist/server/server/workspace-git-service.js +442 -56
- package/dist/server/server/workspace-git-service.js.map +1 -1
- package/dist/server/server/workspace-reconciliation-service.d.ts +54 -0
- package/dist/server/server/workspace-reconciliation-service.d.ts.map +1 -0
- package/dist/server/server/workspace-reconciliation-service.js +176 -0
- package/dist/server/server/workspace-reconciliation-service.js.map +1 -0
- package/dist/server/server/workspace-registry-bootstrap.d.ts.map +1 -1
- package/dist/server/server/workspace-registry-bootstrap.js +4 -3
- package/dist/server/server/workspace-registry-bootstrap.js.map +1 -1
- package/dist/server/server/workspace-registry.d.ts +8 -8
- package/dist/server/server/workspace-registry.test-helpers.d.ts +37 -0
- package/dist/server/server/workspace-registry.test-helpers.d.ts.map +1 -0
- package/dist/server/server/workspace-registry.test-helpers.js +121 -0
- package/dist/server/server/workspace-registry.test-helpers.js.map +1 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts +28 -0
- package/dist/server/server/workspace-script-runtime-store.d.ts.map +1 -0
- package/dist/server/server/workspace-script-runtime-store.js +78 -0
- package/dist/server/server/workspace-script-runtime-store.js.map +1 -0
- package/dist/server/server/workspace-service-env.d.ts +17 -0
- package/dist/server/server/workspace-service-env.d.ts.map +1 -0
- package/dist/server/server/workspace-service-env.js +80 -0
- package/dist/server/server/workspace-service-env.js.map +1 -0
- package/dist/server/server/workspace-service-port-registry.d.ts +19 -0
- package/dist/server/server/workspace-service-port-registry.d.ts.map +1 -0
- package/dist/server/server/workspace-service-port-registry.js +59 -0
- package/dist/server/server/workspace-service-port-registry.js.map +1 -0
- package/dist/server/server/worktree-bootstrap.d.ts +55 -10
- package/dist/server/server/worktree-bootstrap.d.ts.map +1 -1
- package/dist/server/server/worktree-bootstrap.js +290 -112
- package/dist/server/server/worktree-bootstrap.js.map +1 -1
- package/dist/server/server/worktree-core.d.ts +25 -0
- package/dist/server/server/worktree-core.d.ts.map +1 -0
- package/dist/server/server/worktree-core.js +75 -0
- package/dist/server/server/worktree-core.js.map +1 -0
- package/dist/server/server/worktree-errors.d.ts +12 -0
- package/dist/server/server/worktree-errors.d.ts.map +1 -0
- package/dist/server/server/worktree-errors.js +31 -0
- package/dist/server/server/worktree-errors.js.map +1 -0
- package/dist/server/server/worktree-session.d.ts +56 -70
- package/dist/server/server/worktree-session.d.ts.map +1 -1
- package/dist/server/server/worktree-session.js +176 -251
- package/dist/server/server/worktree-session.js.map +1 -1
- package/dist/server/services/github-service.d.ts +225 -0
- package/dist/server/services/github-service.d.ts.map +1 -0
- package/dist/server/services/github-service.js +1381 -0
- package/dist/server/services/github-service.js.map +1 -0
- package/dist/server/shared/messages.d.ts +29408 -12268
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +391 -65
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/server/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/dist/server/terminal/terminal-manager.d.ts +9 -0
- package/dist/server/terminal/terminal-manager.d.ts.map +1 -1
- package/dist/server/terminal/terminal-manager.js +27 -0
- package/dist/server/terminal/terminal-manager.js.map +1 -1
- package/dist/server/terminal/terminal-output-coalescer.d.ts +30 -0
- package/dist/server/terminal/terminal-output-coalescer.d.ts.map +1 -0
- package/dist/server/terminal/terminal-output-coalescer.js +55 -0
- package/dist/server/terminal/terminal-output-coalescer.js.map +1 -0
- package/dist/server/terminal/terminal.d.ts +32 -1
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +397 -17
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +63 -10
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +321 -229
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/promise-timeout.d.ts +9 -0
- package/dist/server/utils/promise-timeout.d.ts.map +1 -0
- package/dist/server/utils/promise-timeout.js +25 -0
- package/dist/server/utils/promise-timeout.js.map +1 -0
- package/dist/server/utils/script-hostname.d.ts +8 -0
- package/dist/server/utils/script-hostname.d.ts.map +1 -0
- package/dist/server/utils/script-hostname.js +14 -0
- package/dist/server/utils/script-hostname.js.map +1 -0
- package/dist/server/utils/string-command-shell.d.ts +10 -0
- package/dist/server/utils/string-command-shell.d.ts.map +1 -0
- package/dist/server/utils/string-command-shell.js +21 -0
- package/dist/server/utils/string-command-shell.js.map +1 -0
- package/dist/server/utils/worktree.d.ts +54 -7
- package/dist/server/utils/worktree.d.ts.map +1 -1
- package/dist/server/utils/worktree.js +434 -129
- package/dist/server/utils/worktree.js.map +1 -1
- package/dist/src/terminal/shell-integration/zsh/.zshenv +17 -0
- package/dist/src/terminal/shell-integration/zsh/paseo-integration.zsh +32 -0
- package/package.json +11 -14
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts +0 -28
- package/dist/server/server/agent/providers/pi-acp-agent.d.ts.map +0 -1
- package/dist/server/server/agent/providers/pi-acp-agent.js +0 -302
- package/dist/server/server/agent/providers/pi-acp-agent.js.map +0 -1
|
@@ -0,0 +1,1381 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { findExecutable } from "../utils/executable.js";
|
|
3
|
+
import { execCommand } from "../utils/spawn.js";
|
|
4
|
+
const DEFAULT_GITHUB_CACHE_TTL_MS = 30000;
|
|
5
|
+
export const GITHUB_POLL_FAST_INTERVAL_MS = 20000;
|
|
6
|
+
export const GITHUB_POLL_SLOW_INTERVAL_MS = 120000;
|
|
7
|
+
export const GITHUB_POLL_ERROR_BACKOFF_CAP_MS = 300000;
|
|
8
|
+
const GITHUB_ENV = {
|
|
9
|
+
...process.env,
|
|
10
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
11
|
+
};
|
|
12
|
+
const LabelSchema = z.object({
|
|
13
|
+
name: z.string().optional(),
|
|
14
|
+
});
|
|
15
|
+
const GitHubIssueSummarySchema = z.object({
|
|
16
|
+
number: z.number(),
|
|
17
|
+
title: z.string().catch(""),
|
|
18
|
+
url: z.string().catch(""),
|
|
19
|
+
state: z.string().catch(""),
|
|
20
|
+
body: z.string().nullable().catch(null),
|
|
21
|
+
labels: z.array(LabelSchema).catch([]),
|
|
22
|
+
updatedAt: z.string().catch(""),
|
|
23
|
+
});
|
|
24
|
+
const GitHubPullRequestSummarySchema = z.object({
|
|
25
|
+
number: z.number(),
|
|
26
|
+
title: z.string().catch(""),
|
|
27
|
+
url: z.string().catch(""),
|
|
28
|
+
state: z.string().catch(""),
|
|
29
|
+
body: z.string().nullable().catch(null),
|
|
30
|
+
baseRefName: z.string().catch(""),
|
|
31
|
+
headRefName: z.string().catch(""),
|
|
32
|
+
labels: z.array(LabelSchema).catch([]),
|
|
33
|
+
updatedAt: z.string().catch(""),
|
|
34
|
+
});
|
|
35
|
+
const PullRequestCheckRunNodeSchema = z.object({
|
|
36
|
+
__typename: z.literal("CheckRun"),
|
|
37
|
+
name: z.string(),
|
|
38
|
+
workflowName: z.string().nullable().optional(),
|
|
39
|
+
conclusion: z.string().nullable().optional(),
|
|
40
|
+
status: z.string().nullable().optional(),
|
|
41
|
+
detailsUrl: z.string().nullable().optional(),
|
|
42
|
+
startedAt: z.string().nullable().optional(),
|
|
43
|
+
completedAt: z.string().nullable().optional(),
|
|
44
|
+
checkSuite: z
|
|
45
|
+
.object({
|
|
46
|
+
workflowRun: z
|
|
47
|
+
.object({
|
|
48
|
+
databaseId: z.number().nullable().optional(),
|
|
49
|
+
})
|
|
50
|
+
.nullable()
|
|
51
|
+
.optional(),
|
|
52
|
+
})
|
|
53
|
+
.nullable()
|
|
54
|
+
.optional(),
|
|
55
|
+
});
|
|
56
|
+
const PullRequestStatusContextNodeSchema = z.object({
|
|
57
|
+
__typename: z.literal("StatusContext"),
|
|
58
|
+
context: z.string(),
|
|
59
|
+
state: z.string().nullable().optional(),
|
|
60
|
+
targetUrl: z.string().nullable().optional(),
|
|
61
|
+
createdAt: z.string().nullable().optional(),
|
|
62
|
+
});
|
|
63
|
+
const PullRequestStatusCheckRollupNodeSchema = z.discriminatedUnion("__typename", [
|
|
64
|
+
PullRequestCheckRunNodeSchema,
|
|
65
|
+
PullRequestStatusContextNodeSchema,
|
|
66
|
+
]);
|
|
67
|
+
const PullRequestStatusCheckRollupArraySchema = z.array(z.unknown());
|
|
68
|
+
const LegacyPullRequestStatusCheckRollupSchema = z.object({
|
|
69
|
+
contexts: z.array(z.unknown()),
|
|
70
|
+
});
|
|
71
|
+
const PullRequestReviewDecisionSchema = z
|
|
72
|
+
.enum(["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"])
|
|
73
|
+
.nullable()
|
|
74
|
+
.catch(null);
|
|
75
|
+
const HeadRepositoryOwnerSchema = z
|
|
76
|
+
.object({
|
|
77
|
+
login: z.string().optional(),
|
|
78
|
+
})
|
|
79
|
+
.nullable()
|
|
80
|
+
.optional();
|
|
81
|
+
const CurrentPullRequestStatusSchema = z.object({
|
|
82
|
+
number: z.number().optional(),
|
|
83
|
+
url: z.string().catch(""),
|
|
84
|
+
title: z.string().catch(""),
|
|
85
|
+
state: z.string().catch(""),
|
|
86
|
+
isDraft: z.boolean().optional().catch(false),
|
|
87
|
+
baseRefName: z.string().catch(""),
|
|
88
|
+
headRefName: z.string().catch(""),
|
|
89
|
+
mergedAt: z.string().nullable().optional(),
|
|
90
|
+
statusCheckRollup: z.unknown().optional(),
|
|
91
|
+
reviewDecision: z.unknown().optional(),
|
|
92
|
+
headRepositoryOwner: HeadRepositoryOwnerSchema,
|
|
93
|
+
});
|
|
94
|
+
const TimelineAuthorSchema = z
|
|
95
|
+
.object({
|
|
96
|
+
login: z.string().optional(),
|
|
97
|
+
url: z.string().nullable().optional(),
|
|
98
|
+
})
|
|
99
|
+
.nullable()
|
|
100
|
+
.optional();
|
|
101
|
+
const PullRequestTimelineReviewNodeSchema = z.object({
|
|
102
|
+
id: z.string().catch(""),
|
|
103
|
+
state: z.string().catch(""),
|
|
104
|
+
body: z.string().nullable().catch(null),
|
|
105
|
+
url: z.string().catch(""),
|
|
106
|
+
submittedAt: z.string().nullable().catch(null),
|
|
107
|
+
author: TimelineAuthorSchema,
|
|
108
|
+
});
|
|
109
|
+
const PullRequestTimelineCommentNodeSchema = z.object({
|
|
110
|
+
id: z.string().catch(""),
|
|
111
|
+
body: z.string().nullable().catch(null),
|
|
112
|
+
url: z.string().catch(""),
|
|
113
|
+
createdAt: z.string().nullable().catch(null),
|
|
114
|
+
author: TimelineAuthorSchema,
|
|
115
|
+
});
|
|
116
|
+
const PullRequestTimelinePageInfoSchema = z.object({
|
|
117
|
+
hasNextPage: z.boolean().catch(false),
|
|
118
|
+
});
|
|
119
|
+
const PullRequestTimelineGraphqlSchema = z.object({
|
|
120
|
+
data: z
|
|
121
|
+
.object({
|
|
122
|
+
repository: z
|
|
123
|
+
.object({
|
|
124
|
+
pullRequest: z
|
|
125
|
+
.object({
|
|
126
|
+
number: z.number().optional(),
|
|
127
|
+
reviews: z
|
|
128
|
+
.object({
|
|
129
|
+
nodes: z.array(PullRequestTimelineReviewNodeSchema).catch([]),
|
|
130
|
+
pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
|
|
131
|
+
})
|
|
132
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
133
|
+
comments: z
|
|
134
|
+
.object({
|
|
135
|
+
nodes: z.array(PullRequestTimelineCommentNodeSchema).catch([]),
|
|
136
|
+
pageInfo: PullRequestTimelinePageInfoSchema.catch({ hasNextPage: false }),
|
|
137
|
+
})
|
|
138
|
+
.catch({ nodes: [], pageInfo: { hasNextPage: false } }),
|
|
139
|
+
})
|
|
140
|
+
.nullable()
|
|
141
|
+
.optional(),
|
|
142
|
+
})
|
|
143
|
+
.nullable()
|
|
144
|
+
.optional(),
|
|
145
|
+
})
|
|
146
|
+
.optional(),
|
|
147
|
+
});
|
|
148
|
+
const GitHubRepoViewSchema = z.object({
|
|
149
|
+
owner: z
|
|
150
|
+
.object({
|
|
151
|
+
login: z.string().optional(),
|
|
152
|
+
})
|
|
153
|
+
.nullable()
|
|
154
|
+
.optional(),
|
|
155
|
+
name: z.string().optional(),
|
|
156
|
+
parent: z
|
|
157
|
+
.object({
|
|
158
|
+
owner: z
|
|
159
|
+
.object({
|
|
160
|
+
login: z.string().optional(),
|
|
161
|
+
})
|
|
162
|
+
.nullable()
|
|
163
|
+
.optional(),
|
|
164
|
+
name: z.string().optional(),
|
|
165
|
+
})
|
|
166
|
+
.nullable()
|
|
167
|
+
.optional(),
|
|
168
|
+
});
|
|
169
|
+
const PullRequestCheckoutTargetSchema = z.object({
|
|
170
|
+
data: z.object({
|
|
171
|
+
repository: z.object({
|
|
172
|
+
pullRequest: z
|
|
173
|
+
.object({
|
|
174
|
+
number: z.number(),
|
|
175
|
+
baseRefName: z.string().catch(""),
|
|
176
|
+
headRefName: z.string().catch(""),
|
|
177
|
+
isCrossRepository: z.boolean().catch(false),
|
|
178
|
+
headRepositoryOwner: z
|
|
179
|
+
.object({
|
|
180
|
+
login: z.string().catch(""),
|
|
181
|
+
})
|
|
182
|
+
.nullable()
|
|
183
|
+
.optional(),
|
|
184
|
+
headRepository: z
|
|
185
|
+
.object({
|
|
186
|
+
sshUrl: z.string().nullable().optional(),
|
|
187
|
+
url: z.string().nullable().optional(),
|
|
188
|
+
})
|
|
189
|
+
.nullable()
|
|
190
|
+
.optional(),
|
|
191
|
+
})
|
|
192
|
+
.nullable(),
|
|
193
|
+
}),
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
196
|
+
const PULL_REQUEST_CHECKOUT_TARGET_QUERY = `
|
|
197
|
+
query PullRequestCheckoutTarget($owner: String!, $name: String!, $number: Int!) {
|
|
198
|
+
repository(owner: $owner, name: $name) {
|
|
199
|
+
pullRequest(number: $number) {
|
|
200
|
+
number
|
|
201
|
+
baseRefName
|
|
202
|
+
headRefName
|
|
203
|
+
isCrossRepository
|
|
204
|
+
headRepositoryOwner {
|
|
205
|
+
login
|
|
206
|
+
}
|
|
207
|
+
headRepository {
|
|
208
|
+
sshUrl
|
|
209
|
+
url
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}`;
|
|
214
|
+
const CURRENT_PR_STATUS_FIELDS = "number,url,title,state,isDraft,baseRefName,headRefName,mergedAt,statusCheckRollup,reviewDecision,headRepositoryOwner";
|
|
215
|
+
const PULL_REQUEST_TIMELINE_QUERY = `
|
|
216
|
+
query PullRequestTimeline($owner: String!, $name: String!, $number: Int!) {
|
|
217
|
+
repository(owner: $owner, name: $name) {
|
|
218
|
+
pullRequest(number: $number) {
|
|
219
|
+
number
|
|
220
|
+
reviews(first: 100) {
|
|
221
|
+
nodes {
|
|
222
|
+
id
|
|
223
|
+
state
|
|
224
|
+
body
|
|
225
|
+
url
|
|
226
|
+
submittedAt
|
|
227
|
+
author {
|
|
228
|
+
login
|
|
229
|
+
url
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
pageInfo {
|
|
233
|
+
hasNextPage
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
comments(first: 100) {
|
|
237
|
+
nodes {
|
|
238
|
+
id
|
|
239
|
+
body
|
|
240
|
+
url
|
|
241
|
+
createdAt
|
|
242
|
+
author {
|
|
243
|
+
login
|
|
244
|
+
url
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
pageInfo {
|
|
248
|
+
hasNextPage
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}`;
|
|
254
|
+
export class GitHubCliMissingError extends Error {
|
|
255
|
+
constructor() {
|
|
256
|
+
super("GitHub CLI (gh) is not installed or not in PATH");
|
|
257
|
+
this.kind = "missing-cli";
|
|
258
|
+
this.name = "GitHubCliMissingError";
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
export class GitHubAuthenticationError extends Error {
|
|
262
|
+
constructor(params) {
|
|
263
|
+
super("GitHub CLI authentication failed");
|
|
264
|
+
this.kind = "auth-failure";
|
|
265
|
+
this.name = "GitHubAuthenticationError";
|
|
266
|
+
this.stderr = params.stderr;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
export class GitHubCommandError extends Error {
|
|
270
|
+
constructor(params) {
|
|
271
|
+
super(`GitHub CLI command failed: gh ${params.args.join(" ")}`);
|
|
272
|
+
this.kind = "command-error";
|
|
273
|
+
this.name = "GitHubCommandError";
|
|
274
|
+
this.args = [...params.args];
|
|
275
|
+
this.cwd = params.cwd;
|
|
276
|
+
this.exitCode = params.exitCode;
|
|
277
|
+
this.stderr = params.stderr;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
export function createGitHubService(options = {}) {
|
|
281
|
+
const ttlMs = options.ttlMs ?? DEFAULT_GITHUB_CACHE_TTL_MS;
|
|
282
|
+
const deps = {
|
|
283
|
+
runner: options.runner ?? runGhCommand,
|
|
284
|
+
resolveGhPath: options.resolveGhPath ?? resolveGhPath,
|
|
285
|
+
now: options.now ?? Date.now,
|
|
286
|
+
};
|
|
287
|
+
const cache = new Map();
|
|
288
|
+
const inFlight = new Map();
|
|
289
|
+
const pollTargets = new Map();
|
|
290
|
+
let api;
|
|
291
|
+
async function cached(params) {
|
|
292
|
+
if (params.readOptions?.force && !params.readOptions.reason) {
|
|
293
|
+
throw new Error("GitHubService forced read requires a reason");
|
|
294
|
+
}
|
|
295
|
+
const key = buildCacheKey({
|
|
296
|
+
cwd: params.cwd,
|
|
297
|
+
method: params.method,
|
|
298
|
+
args: params.args,
|
|
299
|
+
});
|
|
300
|
+
const cachedEntry = cache.get(key);
|
|
301
|
+
const now = deps.now();
|
|
302
|
+
if (!params.readOptions?.force && cachedEntry && cachedEntry.expiresAt > now) {
|
|
303
|
+
return cachedEntry.value;
|
|
304
|
+
}
|
|
305
|
+
const existing = inFlight.get(key);
|
|
306
|
+
if (existing && (!params.readOptions?.force || existing.force)) {
|
|
307
|
+
return existing.promise;
|
|
308
|
+
}
|
|
309
|
+
const request = params
|
|
310
|
+
.load()
|
|
311
|
+
.then((value) => {
|
|
312
|
+
if (inFlight.get(key)?.promise === request) {
|
|
313
|
+
cache.set(key, {
|
|
314
|
+
value,
|
|
315
|
+
cwd: params.cwd,
|
|
316
|
+
expiresAt: deps.now() + ttlMs,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
return value;
|
|
320
|
+
})
|
|
321
|
+
.finally(() => {
|
|
322
|
+
if (inFlight.get(key)?.promise === request) {
|
|
323
|
+
inFlight.delete(key);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
inFlight.set(key, {
|
|
327
|
+
cwd: params.cwd,
|
|
328
|
+
promise: request,
|
|
329
|
+
force: params.readOptions?.force === true,
|
|
330
|
+
});
|
|
331
|
+
return request;
|
|
332
|
+
}
|
|
333
|
+
async function run(args, options) {
|
|
334
|
+
const ghPath = await deps.resolveGhPath();
|
|
335
|
+
if (!ghPath) {
|
|
336
|
+
throw new GitHubCliMissingError();
|
|
337
|
+
}
|
|
338
|
+
try {
|
|
339
|
+
const result = await deps.runner(args, options);
|
|
340
|
+
return result.stdout.trim();
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
throw normalizeGitHubCommandError(error, {
|
|
344
|
+
args,
|
|
345
|
+
cwd: options.cwd,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function getPollTargetKey(options) {
|
|
350
|
+
return buildCacheKey({
|
|
351
|
+
cwd: options.cwd,
|
|
352
|
+
method: "getCurrentPullRequestStatus",
|
|
353
|
+
args: { headRef: options.headRef },
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function updatePollTargetAfterSuccess(options) {
|
|
357
|
+
const target = pollTargets.get(getPollTargetKey(options));
|
|
358
|
+
if (!target) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
target.latestStatus = options.status;
|
|
362
|
+
target.consecutiveErrors = 0;
|
|
363
|
+
if (options.notify) {
|
|
364
|
+
for (const callback of target.callbacks) {
|
|
365
|
+
callback(options.status);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
scheduleGitHubPoll(target);
|
|
369
|
+
}
|
|
370
|
+
function scheduleGitHubPoll(target) {
|
|
371
|
+
scheduleGitHubPollAfter(target, computeGithubNextInterval(target.latestStatus, target.consecutiveErrors));
|
|
372
|
+
}
|
|
373
|
+
function scheduleImmediateGitHubPoll(target) {
|
|
374
|
+
scheduleGitHubPollAfter(target, 0);
|
|
375
|
+
}
|
|
376
|
+
function scheduleGitHubPollAfter(target, delayMs) {
|
|
377
|
+
if (target.retainCount <= 0) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
if (target.timer) {
|
|
381
|
+
clearTimeout(target.timer);
|
|
382
|
+
}
|
|
383
|
+
target.timer = setTimeout(() => {
|
|
384
|
+
target.timer = null;
|
|
385
|
+
void runGitHubPoll(target);
|
|
386
|
+
}, delayMs);
|
|
387
|
+
}
|
|
388
|
+
async function runGitHubPoll(target) {
|
|
389
|
+
try {
|
|
390
|
+
await api.getCurrentPullRequestStatus({
|
|
391
|
+
cwd: target.cwd,
|
|
392
|
+
headRef: target.headRef,
|
|
393
|
+
reason: "self-heal-github",
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
target.consecutiveErrors += 1;
|
|
398
|
+
for (const callback of target.errorCallbacks) {
|
|
399
|
+
callback(error);
|
|
400
|
+
}
|
|
401
|
+
scheduleGitHubPoll(target);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function closeGitHubPollTarget(target) {
|
|
405
|
+
if (target.timer) {
|
|
406
|
+
clearTimeout(target.timer);
|
|
407
|
+
target.timer = null;
|
|
408
|
+
}
|
|
409
|
+
target.retainCount = 0;
|
|
410
|
+
target.callbacks.clear();
|
|
411
|
+
target.errorCallbacks.clear();
|
|
412
|
+
}
|
|
413
|
+
api = {
|
|
414
|
+
listPullRequests(options) {
|
|
415
|
+
return cached({
|
|
416
|
+
cwd: options.cwd,
|
|
417
|
+
method: "listPullRequests",
|
|
418
|
+
args: { query: options.query ?? "", limit: options.limit ?? 20 },
|
|
419
|
+
readOptions: options,
|
|
420
|
+
load: async () => {
|
|
421
|
+
const stdout = await run([
|
|
422
|
+
"pr",
|
|
423
|
+
"list",
|
|
424
|
+
"--search",
|
|
425
|
+
options.query ?? "",
|
|
426
|
+
"--json",
|
|
427
|
+
"number,title,url,state,body,labels,baseRefName,headRefName,updatedAt",
|
|
428
|
+
"--limit",
|
|
429
|
+
String(options.limit ?? 20),
|
|
430
|
+
], { cwd: options.cwd });
|
|
431
|
+
return parsePullRequestSummaries(stdout);
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
},
|
|
435
|
+
listIssues(options) {
|
|
436
|
+
return cached({
|
|
437
|
+
cwd: options.cwd,
|
|
438
|
+
method: "listIssues",
|
|
439
|
+
args: { query: options.query ?? "", limit: options.limit ?? 20 },
|
|
440
|
+
readOptions: options,
|
|
441
|
+
load: async () => {
|
|
442
|
+
const stdout = await run([
|
|
443
|
+
"issue",
|
|
444
|
+
"list",
|
|
445
|
+
"--search",
|
|
446
|
+
options.query ?? "",
|
|
447
|
+
"--json",
|
|
448
|
+
"number,title,url,state,body,labels,updatedAt",
|
|
449
|
+
"--limit",
|
|
450
|
+
String(options.limit ?? 20),
|
|
451
|
+
], { cwd: options.cwd });
|
|
452
|
+
return parseIssueSummaries(stdout);
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
},
|
|
456
|
+
getPullRequest(options) {
|
|
457
|
+
return cached({
|
|
458
|
+
cwd: options.cwd,
|
|
459
|
+
method: "getPullRequest",
|
|
460
|
+
args: { number: options.number },
|
|
461
|
+
readOptions: options,
|
|
462
|
+
load: async () => {
|
|
463
|
+
const stdout = await run([
|
|
464
|
+
"pr",
|
|
465
|
+
"view",
|
|
466
|
+
String(options.number),
|
|
467
|
+
"--json",
|
|
468
|
+
"number,title,url,state,body,labels,baseRefName,headRefName,updatedAt",
|
|
469
|
+
], { cwd: options.cwd });
|
|
470
|
+
return parsePullRequestSummary(stdout);
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
},
|
|
474
|
+
async getPullRequestHeadRef(options) {
|
|
475
|
+
const pullRequest = await this.getPullRequest(options);
|
|
476
|
+
return pullRequest.headRefName;
|
|
477
|
+
},
|
|
478
|
+
getPullRequestCheckoutTarget(options) {
|
|
479
|
+
return cached({
|
|
480
|
+
cwd: options.cwd,
|
|
481
|
+
method: "getPullRequestCheckoutTarget",
|
|
482
|
+
args: { number: options.number },
|
|
483
|
+
readOptions: options,
|
|
484
|
+
load: async () => {
|
|
485
|
+
const repo = await getGitHubRepoView({ cwd: options.cwd, run });
|
|
486
|
+
const owner = repo?.owner?.login;
|
|
487
|
+
const name = repo?.name;
|
|
488
|
+
if (!owner || !name) {
|
|
489
|
+
throw new Error("Unable to resolve GitHub repository for pull request checkout");
|
|
490
|
+
}
|
|
491
|
+
const stdout = await run([
|
|
492
|
+
"api",
|
|
493
|
+
"graphql",
|
|
494
|
+
"-f",
|
|
495
|
+
`query=${PULL_REQUEST_CHECKOUT_TARGET_QUERY}`,
|
|
496
|
+
"-F",
|
|
497
|
+
`owner=${owner}`,
|
|
498
|
+
"-F",
|
|
499
|
+
`name=${name}`,
|
|
500
|
+
"-F",
|
|
501
|
+
`number=${options.number}`,
|
|
502
|
+
], { cwd: options.cwd });
|
|
503
|
+
return parsePullRequestCheckoutTarget(stdout);
|
|
504
|
+
},
|
|
505
|
+
});
|
|
506
|
+
},
|
|
507
|
+
getCurrentPullRequestStatus(options) {
|
|
508
|
+
return cached({
|
|
509
|
+
cwd: options.cwd,
|
|
510
|
+
method: "getCurrentPullRequestStatus",
|
|
511
|
+
args: { headRef: options.headRef },
|
|
512
|
+
readOptions: options,
|
|
513
|
+
load: async () => {
|
|
514
|
+
return resolveCurrentPullRequestView({
|
|
515
|
+
cwd: options.cwd,
|
|
516
|
+
headRef: options.headRef,
|
|
517
|
+
run,
|
|
518
|
+
});
|
|
519
|
+
},
|
|
520
|
+
}).then((status) => {
|
|
521
|
+
updatePollTargetAfterSuccess({
|
|
522
|
+
cwd: options.cwd,
|
|
523
|
+
headRef: options.headRef,
|
|
524
|
+
status,
|
|
525
|
+
notify: options.reason === "self-heal-github",
|
|
526
|
+
});
|
|
527
|
+
return status;
|
|
528
|
+
});
|
|
529
|
+
},
|
|
530
|
+
getPullRequestTimeline(options) {
|
|
531
|
+
return cached({
|
|
532
|
+
cwd: options.cwd,
|
|
533
|
+
method: "getPullRequestTimeline",
|
|
534
|
+
args: { prNumber: options.prNumber },
|
|
535
|
+
readOptions: options,
|
|
536
|
+
load: async () => {
|
|
537
|
+
try {
|
|
538
|
+
const stdout = await run([
|
|
539
|
+
"api",
|
|
540
|
+
"graphql",
|
|
541
|
+
"-f",
|
|
542
|
+
`query=${PULL_REQUEST_TIMELINE_QUERY}`,
|
|
543
|
+
"-F",
|
|
544
|
+
`owner=${options.repoOwner}`,
|
|
545
|
+
"-F",
|
|
546
|
+
`name=${options.repoName}`,
|
|
547
|
+
"-F",
|
|
548
|
+
`number=${options.prNumber}`,
|
|
549
|
+
], { cwd: options.cwd });
|
|
550
|
+
return parsePullRequestTimeline(stdout, {
|
|
551
|
+
prNumber: options.prNumber,
|
|
552
|
+
repoOwner: options.repoOwner,
|
|
553
|
+
repoName: options.repoName,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
return {
|
|
558
|
+
prNumber: options.prNumber,
|
|
559
|
+
repoOwner: options.repoOwner,
|
|
560
|
+
repoName: options.repoName,
|
|
561
|
+
items: [],
|
|
562
|
+
truncated: false,
|
|
563
|
+
error: mapPullRequestTimelineError(error),
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
});
|
|
568
|
+
},
|
|
569
|
+
async searchIssuesAndPrs(options) {
|
|
570
|
+
if (options.force && !options.reason) {
|
|
571
|
+
throw new Error("GitHubService forced read requires a reason");
|
|
572
|
+
}
|
|
573
|
+
const kinds = options.kinds ?? ["github-issue", "github-pr"];
|
|
574
|
+
const shouldFetchIssues = kinds.includes("github-issue");
|
|
575
|
+
const shouldFetchPullRequests = kinds.includes("github-pr");
|
|
576
|
+
const readOptions = options.force
|
|
577
|
+
? { force: true, reason: options.reason }
|
|
578
|
+
: { force: false, reason: options.reason };
|
|
579
|
+
const [issuesResult, prsResult] = await Promise.allSettled([
|
|
580
|
+
shouldFetchIssues
|
|
581
|
+
? this.listIssues({
|
|
582
|
+
cwd: options.cwd,
|
|
583
|
+
query: options.query,
|
|
584
|
+
limit: options.limit,
|
|
585
|
+
...readOptions,
|
|
586
|
+
})
|
|
587
|
+
: Promise.resolve(null),
|
|
588
|
+
shouldFetchPullRequests
|
|
589
|
+
? this.listPullRequests({
|
|
590
|
+
cwd: options.cwd,
|
|
591
|
+
query: options.query,
|
|
592
|
+
limit: options.limit,
|
|
593
|
+
...readOptions,
|
|
594
|
+
})
|
|
595
|
+
: Promise.resolve(null),
|
|
596
|
+
]);
|
|
597
|
+
const items = [];
|
|
598
|
+
const requestedResults = [
|
|
599
|
+
shouldFetchIssues ? issuesResult : null,
|
|
600
|
+
shouldFetchPullRequests ? prsResult : null,
|
|
601
|
+
].filter((result) => result !== null);
|
|
602
|
+
if (requestedResults.length > 0 &&
|
|
603
|
+
requestedResults.every((result) => result.status === "rejected" &&
|
|
604
|
+
(result.reason instanceof GitHubCliMissingError ||
|
|
605
|
+
result.reason instanceof GitHubAuthenticationError))) {
|
|
606
|
+
return { items: [], githubFeaturesEnabled: false };
|
|
607
|
+
}
|
|
608
|
+
if (shouldFetchIssues && issuesResult.status === "fulfilled") {
|
|
609
|
+
for (const item of issuesResult.value ?? []) {
|
|
610
|
+
items.push({
|
|
611
|
+
kind: "issue",
|
|
612
|
+
number: item.number,
|
|
613
|
+
title: item.title,
|
|
614
|
+
url: item.url,
|
|
615
|
+
state: item.state,
|
|
616
|
+
body: item.body,
|
|
617
|
+
labels: item.labels,
|
|
618
|
+
baseRefName: null,
|
|
619
|
+
headRefName: null,
|
|
620
|
+
updatedAt: item.updatedAt,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
if (shouldFetchPullRequests && prsResult.status === "fulfilled") {
|
|
625
|
+
for (const item of prsResult.value ?? []) {
|
|
626
|
+
items.push({
|
|
627
|
+
kind: "pr",
|
|
628
|
+
number: item.number,
|
|
629
|
+
title: item.title,
|
|
630
|
+
url: item.url,
|
|
631
|
+
state: item.state,
|
|
632
|
+
body: item.body,
|
|
633
|
+
labels: item.labels,
|
|
634
|
+
baseRefName: item.baseRefName,
|
|
635
|
+
headRefName: item.headRefName,
|
|
636
|
+
updatedAt: item.updatedAt,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
items.sort((left, right) => {
|
|
641
|
+
const leftTime = parseOptionalTime(left.updatedAt ?? null);
|
|
642
|
+
const rightTime = parseOptionalTime(right.updatedAt ?? null);
|
|
643
|
+
return rightTime - leftTime;
|
|
644
|
+
});
|
|
645
|
+
return { items, githubFeaturesEnabled: true };
|
|
646
|
+
},
|
|
647
|
+
async createPullRequest(options) {
|
|
648
|
+
const args = [
|
|
649
|
+
"api",
|
|
650
|
+
"-X",
|
|
651
|
+
"POST",
|
|
652
|
+
`repos/${options.repo}/pulls`,
|
|
653
|
+
"-f",
|
|
654
|
+
`title=${options.title}`,
|
|
655
|
+
];
|
|
656
|
+
args.push("-f", `head=${options.head}`);
|
|
657
|
+
args.push("-f", `base=${options.base}`);
|
|
658
|
+
if (options.body) {
|
|
659
|
+
args.push("-f", `body=${options.body}`);
|
|
660
|
+
}
|
|
661
|
+
const stdout = await run(args, { cwd: options.cwd });
|
|
662
|
+
const parsed = z
|
|
663
|
+
.object({
|
|
664
|
+
url: z.string(),
|
|
665
|
+
number: z.number(),
|
|
666
|
+
})
|
|
667
|
+
.parse(JSON.parse(stdout || "{}"));
|
|
668
|
+
return parsed;
|
|
669
|
+
},
|
|
670
|
+
isAuthenticated(options) {
|
|
671
|
+
return cached({
|
|
672
|
+
cwd: options.cwd,
|
|
673
|
+
method: "isAuthenticated",
|
|
674
|
+
args: {},
|
|
675
|
+
readOptions: options,
|
|
676
|
+
load: async () => {
|
|
677
|
+
try {
|
|
678
|
+
await run(["auth", "status"], { cwd: options.cwd });
|
|
679
|
+
return true;
|
|
680
|
+
}
|
|
681
|
+
catch (error) {
|
|
682
|
+
if (isGitHubAuthenticationError(error)) {
|
|
683
|
+
throw error;
|
|
684
|
+
}
|
|
685
|
+
if (error instanceof GitHubCommandError && isAuthFailureText(error.stderr)) {
|
|
686
|
+
throw new GitHubAuthenticationError({ stderr: error.stderr });
|
|
687
|
+
}
|
|
688
|
+
throw error;
|
|
689
|
+
}
|
|
690
|
+
},
|
|
691
|
+
});
|
|
692
|
+
},
|
|
693
|
+
retainCurrentPullRequestStatusPoll(options) {
|
|
694
|
+
const key = getPollTargetKey(options);
|
|
695
|
+
let target = pollTargets.get(key);
|
|
696
|
+
if (!target) {
|
|
697
|
+
target = {
|
|
698
|
+
cwd: options.cwd,
|
|
699
|
+
headRef: options.headRef,
|
|
700
|
+
retainCount: 0,
|
|
701
|
+
timer: null,
|
|
702
|
+
latestStatus: null,
|
|
703
|
+
consecutiveErrors: 0,
|
|
704
|
+
callbacks: new Set(),
|
|
705
|
+
errorCallbacks: new Set(),
|
|
706
|
+
};
|
|
707
|
+
pollTargets.set(key, target);
|
|
708
|
+
}
|
|
709
|
+
const isNewlyRetained = target.retainCount === 0;
|
|
710
|
+
target.retainCount += 1;
|
|
711
|
+
if (options.onStatus) {
|
|
712
|
+
target.callbacks.add(options.onStatus);
|
|
713
|
+
}
|
|
714
|
+
if (options.onError) {
|
|
715
|
+
target.errorCallbacks.add(options.onError);
|
|
716
|
+
}
|
|
717
|
+
if (isNewlyRetained) {
|
|
718
|
+
scheduleImmediateGitHubPoll(target);
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
scheduleGitHubPoll(target);
|
|
722
|
+
}
|
|
723
|
+
let unsubscribed = false;
|
|
724
|
+
return {
|
|
725
|
+
unsubscribe: () => {
|
|
726
|
+
if (unsubscribed) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
unsubscribed = true;
|
|
730
|
+
if (options.onStatus) {
|
|
731
|
+
target.callbacks.delete(options.onStatus);
|
|
732
|
+
}
|
|
733
|
+
if (options.onError) {
|
|
734
|
+
target.errorCallbacks.delete(options.onError);
|
|
735
|
+
}
|
|
736
|
+
target.retainCount -= 1;
|
|
737
|
+
if (target.retainCount > 0) {
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
closeGitHubPollTarget(target);
|
|
741
|
+
pollTargets.delete(key);
|
|
742
|
+
},
|
|
743
|
+
};
|
|
744
|
+
},
|
|
745
|
+
invalidate(options) {
|
|
746
|
+
// Local checkout mutations that can alter the current PR identity or PR status
|
|
747
|
+
// must call this with the affected cwd before broadcasting fresh git state.
|
|
748
|
+
for (const [key, entry] of cache.entries()) {
|
|
749
|
+
if (entry.cwd === options.cwd) {
|
|
750
|
+
cache.delete(key);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
for (const [key, entry] of inFlight.entries()) {
|
|
754
|
+
if (entry.cwd === options.cwd) {
|
|
755
|
+
inFlight.delete(key);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
dispose() {
|
|
760
|
+
for (const target of pollTargets.values()) {
|
|
761
|
+
closeGitHubPollTarget(target);
|
|
762
|
+
}
|
|
763
|
+
pollTargets.clear();
|
|
764
|
+
},
|
|
765
|
+
};
|
|
766
|
+
return api;
|
|
767
|
+
}
|
|
768
|
+
export function computeGithubNextInterval(status, consecutiveErrors) {
|
|
769
|
+
const baseInterval = isGitHubStatusPending(status)
|
|
770
|
+
? GITHUB_POLL_FAST_INTERVAL_MS
|
|
771
|
+
: GITHUB_POLL_SLOW_INTERVAL_MS;
|
|
772
|
+
if (consecutiveErrors <= 1) {
|
|
773
|
+
return baseInterval;
|
|
774
|
+
}
|
|
775
|
+
return Math.min(baseInterval * 2 ** (consecutiveErrors - 1), GITHUB_POLL_ERROR_BACKOFF_CAP_MS);
|
|
776
|
+
}
|
|
777
|
+
function isGitHubStatusPending(status) {
|
|
778
|
+
if (!status) {
|
|
779
|
+
return false;
|
|
780
|
+
}
|
|
781
|
+
if (status.checksStatus === "pending") {
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
return status.checks.some((check) => check.status === "pending");
|
|
785
|
+
}
|
|
786
|
+
async function resolveGhPath() {
|
|
787
|
+
return findExecutable("gh");
|
|
788
|
+
}
|
|
789
|
+
async function runGhCommand(args, options) {
|
|
790
|
+
return execCommand("gh", args, {
|
|
791
|
+
cwd: options.cwd,
|
|
792
|
+
env: GITHUB_ENV,
|
|
793
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
function buildCacheKey(params) {
|
|
797
|
+
return `${params.cwd}:${params.method}:${stableStringify(params.args)}`;
|
|
798
|
+
}
|
|
799
|
+
function stableStringify(value) {
|
|
800
|
+
return JSON.stringify(sortJsonValue(value));
|
|
801
|
+
}
|
|
802
|
+
function sortJsonValue(value) {
|
|
803
|
+
if (Array.isArray(value)) {
|
|
804
|
+
return value.map(sortJsonValue);
|
|
805
|
+
}
|
|
806
|
+
if (!value || typeof value !== "object") {
|
|
807
|
+
return value;
|
|
808
|
+
}
|
|
809
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
810
|
+
const sorted = {};
|
|
811
|
+
for (const [key, entryValue] of entries) {
|
|
812
|
+
sorted[key] = sortJsonValue(entryValue);
|
|
813
|
+
}
|
|
814
|
+
return sorted;
|
|
815
|
+
}
|
|
816
|
+
function normalizeGitHubCommandError(error, context) {
|
|
817
|
+
if (error instanceof GitHubAuthenticationError) {
|
|
818
|
+
return error;
|
|
819
|
+
}
|
|
820
|
+
if (error instanceof GitHubCommandError) {
|
|
821
|
+
if (isAuthFailureText(error.stderr)) {
|
|
822
|
+
return new GitHubAuthenticationError({ stderr: error.stderr });
|
|
823
|
+
}
|
|
824
|
+
return error;
|
|
825
|
+
}
|
|
826
|
+
const failure = toCommandFailureLike(error);
|
|
827
|
+
if (failure.code === "ENOENT") {
|
|
828
|
+
return new GitHubCliMissingError();
|
|
829
|
+
}
|
|
830
|
+
const stderr = bufferOrStringToString(failure.stderr);
|
|
831
|
+
const message = failure.message ?? "";
|
|
832
|
+
if (isAuthFailureText(stderr) || isAuthFailureText(message)) {
|
|
833
|
+
return new GitHubAuthenticationError({ stderr });
|
|
834
|
+
}
|
|
835
|
+
return new GitHubCommandError({
|
|
836
|
+
args: context.args,
|
|
837
|
+
cwd: context.cwd,
|
|
838
|
+
exitCode: typeof failure.code === "number" ? failure.code : null,
|
|
839
|
+
stderr: stderr || message,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
function toCommandFailureLike(error) {
|
|
843
|
+
if (!error || typeof error !== "object") {
|
|
844
|
+
return { message: String(error) };
|
|
845
|
+
}
|
|
846
|
+
const record = error;
|
|
847
|
+
return {
|
|
848
|
+
code: typeof record.code === "string" || typeof record.code === "number" || record.code === null
|
|
849
|
+
? record.code
|
|
850
|
+
: undefined,
|
|
851
|
+
stderr: typeof record.stderr === "string" || Buffer.isBuffer(record.stderr)
|
|
852
|
+
? record.stderr
|
|
853
|
+
: undefined,
|
|
854
|
+
stdout: typeof record.stdout === "string" || Buffer.isBuffer(record.stdout)
|
|
855
|
+
? record.stdout
|
|
856
|
+
: undefined,
|
|
857
|
+
message: typeof record.message === "string" ? record.message : undefined,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
function bufferOrStringToString(value) {
|
|
861
|
+
if (Buffer.isBuffer(value)) {
|
|
862
|
+
return value.toString("utf8");
|
|
863
|
+
}
|
|
864
|
+
return value ?? "";
|
|
865
|
+
}
|
|
866
|
+
function isGitHubAuthenticationError(error) {
|
|
867
|
+
return error instanceof GitHubAuthenticationError;
|
|
868
|
+
}
|
|
869
|
+
function isAuthFailureText(text) {
|
|
870
|
+
const normalized = text.toLowerCase();
|
|
871
|
+
return (normalized.includes("gh auth login") ||
|
|
872
|
+
normalized.includes("not logged into any github hosts") ||
|
|
873
|
+
normalized.includes("authentication failed") ||
|
|
874
|
+
normalized.includes("authentication required") ||
|
|
875
|
+
normalized.includes("bad credentials") ||
|
|
876
|
+
normalized.includes("http 401"));
|
|
877
|
+
}
|
|
878
|
+
function isNoPullRequestFoundError(error) {
|
|
879
|
+
if (!(error instanceof GitHubCommandError)) {
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
const text = error.stderr.toLowerCase();
|
|
883
|
+
return text.includes("no pull requests found");
|
|
884
|
+
}
|
|
885
|
+
async function resolveCurrentPullRequestView(options) {
|
|
886
|
+
const viewCandidate = await tryCurrentPullRequestView(options);
|
|
887
|
+
if (viewCandidate && isCandidateForHeadRef(viewCandidate, options.headRef)) {
|
|
888
|
+
return viewCandidate.status;
|
|
889
|
+
}
|
|
890
|
+
const repo = await getGitHubRepoView(options);
|
|
891
|
+
const forkOwner = repo?.owner?.login;
|
|
892
|
+
const parentOwner = repo?.parent?.owner?.login;
|
|
893
|
+
const parentName = repo?.parent?.name;
|
|
894
|
+
if (!forkOwner || !parentOwner || !parentName) {
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
const parentCandidates = await listCurrentPullRequestCandidates({
|
|
898
|
+
cwd: options.cwd,
|
|
899
|
+
headRef: `${forkOwner}:${options.headRef}`,
|
|
900
|
+
run: options.run,
|
|
901
|
+
repo: `${parentOwner}/${parentName}`,
|
|
902
|
+
});
|
|
903
|
+
const parentMatch = pickPullRequestCandidate({
|
|
904
|
+
candidates: parentCandidates,
|
|
905
|
+
headRef: options.headRef,
|
|
906
|
+
headRepositoryOwner: forkOwner,
|
|
907
|
+
});
|
|
908
|
+
return parentMatch?.status ?? null;
|
|
909
|
+
}
|
|
910
|
+
async function tryCurrentPullRequestView(options) {
|
|
911
|
+
try {
|
|
912
|
+
const stdout = await options.run(["pr", "view", "--json", CURRENT_PR_STATUS_FIELDS], {
|
|
913
|
+
cwd: options.cwd,
|
|
914
|
+
});
|
|
915
|
+
return parseCurrentPullRequestCandidate(stdout, options.headRef);
|
|
916
|
+
}
|
|
917
|
+
catch (error) {
|
|
918
|
+
if (isNoPullRequestFoundError(error)) {
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
throw error;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
async function listCurrentPullRequestCandidates(options) {
|
|
925
|
+
const args = ["pr", "list"];
|
|
926
|
+
if (options.repo) {
|
|
927
|
+
args.push("--repo", options.repo);
|
|
928
|
+
}
|
|
929
|
+
args.push("--state", "all", "--head", options.headRef, "--json", CURRENT_PR_STATUS_FIELDS, "--limit", "10");
|
|
930
|
+
try {
|
|
931
|
+
const stdout = await options.run(args, { cwd: options.cwd });
|
|
932
|
+
return parseCurrentPullRequestCandidateList(stdout, options.headRef);
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
if (isNoPullRequestFoundError(error)) {
|
|
936
|
+
return [];
|
|
937
|
+
}
|
|
938
|
+
throw error;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
async function getGitHubRepoView(options) {
|
|
942
|
+
try {
|
|
943
|
+
const stdout = await options.run(["repo", "view", "--json", "owner,name,parent"], {
|
|
944
|
+
cwd: options.cwd,
|
|
945
|
+
});
|
|
946
|
+
return GitHubRepoViewSchema.parse(JSON.parse(stdout || "{}"));
|
|
947
|
+
}
|
|
948
|
+
catch {
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
function parseCurrentPullRequestCandidate(stdout, fallbackHeadRefName) {
|
|
953
|
+
const item = CurrentPullRequestStatusSchema.parse(JSON.parse(stdout || "{}"));
|
|
954
|
+
return toCurrentPullRequestCandidate(item, fallbackHeadRefName);
|
|
955
|
+
}
|
|
956
|
+
function parseCurrentPullRequestCandidateList(stdout, fallbackHeadRefName) {
|
|
957
|
+
const items = z.array(CurrentPullRequestStatusSchema).parse(JSON.parse(stdout || "[]"));
|
|
958
|
+
return items
|
|
959
|
+
.map((item) => toCurrentPullRequestCandidate(item, fallbackHeadRefName))
|
|
960
|
+
.filter((candidate) => candidate !== null);
|
|
961
|
+
}
|
|
962
|
+
function toCurrentPullRequestCandidate(item, fallbackHeadRefName) {
|
|
963
|
+
const status = toCurrentPullRequestStatus(item, fallbackHeadRefName);
|
|
964
|
+
if (!status) {
|
|
965
|
+
return null;
|
|
966
|
+
}
|
|
967
|
+
const headRepositoryOwner = item.headRepositoryOwner?.login;
|
|
968
|
+
return {
|
|
969
|
+
status,
|
|
970
|
+
...(headRepositoryOwner ? { headRepositoryOwner } : {}),
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function isCandidateForHeadRef(candidate, headRef) {
|
|
974
|
+
return candidate.status.headRefName === headRef && hasResolvedRepoIdentity(candidate.status);
|
|
975
|
+
}
|
|
976
|
+
function hasResolvedRepoIdentity(status) {
|
|
977
|
+
return Boolean(status.repoOwner && status.repoName);
|
|
978
|
+
}
|
|
979
|
+
function pickPullRequestCandidate(options) {
|
|
980
|
+
const matching = options.candidates.filter((candidate) => {
|
|
981
|
+
if (!isCandidateForHeadRef(candidate, options.headRef)) {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
if (!options.headRepositoryOwner) {
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
return candidate.headRepositoryOwner === options.headRepositoryOwner;
|
|
988
|
+
});
|
|
989
|
+
matching.sort(comparePullRequestCandidatePreference);
|
|
990
|
+
return matching[0] ?? null;
|
|
991
|
+
}
|
|
992
|
+
function comparePullRequestCandidatePreference(left, right) {
|
|
993
|
+
return getPullRequestStateRank(left.status) - getPullRequestStateRank(right.status);
|
|
994
|
+
}
|
|
995
|
+
function getPullRequestStateRank(status) {
|
|
996
|
+
if (status.state === "open" || status.isDraft) {
|
|
997
|
+
return 0;
|
|
998
|
+
}
|
|
999
|
+
if (status.state === "merged") {
|
|
1000
|
+
return 1;
|
|
1001
|
+
}
|
|
1002
|
+
return 2;
|
|
1003
|
+
}
|
|
1004
|
+
function parsePullRequestSummaries(stdout) {
|
|
1005
|
+
const parsed = z.array(GitHubPullRequestSummarySchema).parse(JSON.parse(stdout || "[]"));
|
|
1006
|
+
return parsed.map(toPullRequestSummary);
|
|
1007
|
+
}
|
|
1008
|
+
function parsePullRequestSummary(stdout) {
|
|
1009
|
+
return toPullRequestSummary(GitHubPullRequestSummarySchema.parse(JSON.parse(stdout || "{}")));
|
|
1010
|
+
}
|
|
1011
|
+
function parsePullRequestCheckoutTarget(stdout) {
|
|
1012
|
+
const parsed = PullRequestCheckoutTargetSchema.parse(JSON.parse(stdout || "{}"));
|
|
1013
|
+
const pullRequest = parsed.data.repository.pullRequest;
|
|
1014
|
+
if (!pullRequest) {
|
|
1015
|
+
throw new Error("Pull request not found");
|
|
1016
|
+
}
|
|
1017
|
+
return {
|
|
1018
|
+
number: pullRequest.number,
|
|
1019
|
+
baseRefName: pullRequest.baseRefName,
|
|
1020
|
+
headRefName: pullRequest.headRefName,
|
|
1021
|
+
headOwnerLogin: pullRequest.headRepositoryOwner?.login || null,
|
|
1022
|
+
headRepositorySshUrl: pullRequest.headRepository?.sshUrl || null,
|
|
1023
|
+
headRepositoryUrl: pullRequest.headRepository?.url || null,
|
|
1024
|
+
isCrossRepository: pullRequest.isCrossRepository,
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
function toPullRequestSummary(item) {
|
|
1028
|
+
return {
|
|
1029
|
+
number: item.number,
|
|
1030
|
+
title: item.title,
|
|
1031
|
+
url: item.url,
|
|
1032
|
+
state: item.state,
|
|
1033
|
+
body: item.body,
|
|
1034
|
+
baseRefName: item.baseRefName,
|
|
1035
|
+
headRefName: item.headRefName,
|
|
1036
|
+
labels: item.labels.map((label) => label.name ?? "").filter((name) => name.length > 0),
|
|
1037
|
+
updatedAt: item.updatedAt,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
function parseIssueSummaries(stdout) {
|
|
1041
|
+
const parsed = z.array(GitHubIssueSummarySchema).parse(JSON.parse(stdout || "[]"));
|
|
1042
|
+
return parsed.map((item) => ({
|
|
1043
|
+
number: item.number,
|
|
1044
|
+
title: item.title,
|
|
1045
|
+
url: item.url,
|
|
1046
|
+
state: item.state,
|
|
1047
|
+
body: item.body,
|
|
1048
|
+
labels: item.labels.map((label) => label.name ?? "").filter((name) => name.length > 0),
|
|
1049
|
+
updatedAt: item.updatedAt,
|
|
1050
|
+
}));
|
|
1051
|
+
}
|
|
1052
|
+
function parsePullRequestTimeline(stdout, identity) {
|
|
1053
|
+
const parsed = PullRequestTimelineGraphqlSchema.parse(JSON.parse(stdout || "{}"));
|
|
1054
|
+
const pullRequest = parsed.data?.repository?.pullRequest;
|
|
1055
|
+
const items = pullRequest
|
|
1056
|
+
? [
|
|
1057
|
+
...pullRequest.reviews.nodes.flatMap(toPullRequestTimelineReviewItem),
|
|
1058
|
+
...pullRequest.comments.nodes.map(toPullRequestTimelineCommentItem),
|
|
1059
|
+
].sort(compareTimelineItems)
|
|
1060
|
+
: [];
|
|
1061
|
+
return {
|
|
1062
|
+
prNumber: pullRequest?.number ?? identity.prNumber,
|
|
1063
|
+
repoOwner: identity.repoOwner,
|
|
1064
|
+
repoName: identity.repoName,
|
|
1065
|
+
items,
|
|
1066
|
+
// S3 deliberately caps timeline fetches at the first 100 reviews and first 100 comments.
|
|
1067
|
+
truncated: Boolean(pullRequest?.reviews.pageInfo.hasNextPage || pullRequest?.comments.pageInfo.hasNextPage),
|
|
1068
|
+
error: pullRequest ? null : { kind: "not_found", message: "Pull request not found" },
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function toPullRequestTimelineReviewItem(review) {
|
|
1072
|
+
const reviewState = mapTimelineReviewState(review.state, review.body ?? "");
|
|
1073
|
+
if (!reviewState) {
|
|
1074
|
+
return [];
|
|
1075
|
+
}
|
|
1076
|
+
return [
|
|
1077
|
+
{
|
|
1078
|
+
kind: "review",
|
|
1079
|
+
id: review.id,
|
|
1080
|
+
author: review.author?.login ?? "unknown",
|
|
1081
|
+
authorUrl: review.author?.url ?? null,
|
|
1082
|
+
body: review.body ?? "",
|
|
1083
|
+
createdAt: parseOptionalTime(review.submittedAt ?? null),
|
|
1084
|
+
url: review.url,
|
|
1085
|
+
reviewState,
|
|
1086
|
+
},
|
|
1087
|
+
];
|
|
1088
|
+
}
|
|
1089
|
+
function toPullRequestTimelineCommentItem(comment) {
|
|
1090
|
+
return {
|
|
1091
|
+
kind: "comment",
|
|
1092
|
+
id: comment.id,
|
|
1093
|
+
author: comment.author?.login ?? "unknown",
|
|
1094
|
+
authorUrl: comment.author?.url ?? null,
|
|
1095
|
+
body: comment.body ?? "",
|
|
1096
|
+
createdAt: parseOptionalTime(comment.createdAt ?? null),
|
|
1097
|
+
url: comment.url,
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function mapTimelineReviewState(state, body) {
|
|
1101
|
+
switch (state) {
|
|
1102
|
+
case "APPROVED":
|
|
1103
|
+
return "approved";
|
|
1104
|
+
case "CHANGES_REQUESTED":
|
|
1105
|
+
return "changes_requested";
|
|
1106
|
+
case "COMMENTED":
|
|
1107
|
+
return "commented";
|
|
1108
|
+
case "DISMISSED":
|
|
1109
|
+
case "PENDING":
|
|
1110
|
+
return body.trim().length > 0 ? "commented" : null;
|
|
1111
|
+
default:
|
|
1112
|
+
return body.trim().length > 0 ? "commented" : null;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
function compareTimelineItems(left, right) {
|
|
1116
|
+
if (left.createdAt !== right.createdAt) {
|
|
1117
|
+
return left.createdAt - right.createdAt;
|
|
1118
|
+
}
|
|
1119
|
+
return left.id.localeCompare(right.id);
|
|
1120
|
+
}
|
|
1121
|
+
function mapPullRequestTimelineError(error) {
|
|
1122
|
+
if (error instanceof GitHubCommandError) {
|
|
1123
|
+
return {
|
|
1124
|
+
kind: classifyPullRequestTimelineError(error.stderr),
|
|
1125
|
+
message: error.stderr || error.message,
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
if (error instanceof GitHubAuthenticationError) {
|
|
1129
|
+
return {
|
|
1130
|
+
kind: "forbidden",
|
|
1131
|
+
message: error.stderr || error.message,
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
return {
|
|
1135
|
+
kind: "unknown",
|
|
1136
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
function classifyPullRequestTimelineError(stderr) {
|
|
1140
|
+
const normalized = stderr.toLowerCase();
|
|
1141
|
+
if (normalized.includes("could not resolve to a pullrequest") ||
|
|
1142
|
+
normalized.includes("pull request not found") ||
|
|
1143
|
+
normalized.includes("pullrequest not found")) {
|
|
1144
|
+
return "not_found";
|
|
1145
|
+
}
|
|
1146
|
+
if (normalized.includes("forbidden") ||
|
|
1147
|
+
normalized.includes("resource not accessible") ||
|
|
1148
|
+
normalized.includes("permission") ||
|
|
1149
|
+
normalized.includes("access denied") ||
|
|
1150
|
+
normalized.includes("requires authentication") ||
|
|
1151
|
+
normalized.includes("http 403")) {
|
|
1152
|
+
return "forbidden";
|
|
1153
|
+
}
|
|
1154
|
+
return "unknown";
|
|
1155
|
+
}
|
|
1156
|
+
function toCurrentPullRequestStatus(item, fallbackHeadRefName) {
|
|
1157
|
+
if (!item.url || !item.title) {
|
|
1158
|
+
return null;
|
|
1159
|
+
}
|
|
1160
|
+
const repoIdentity = parseGitHubPullRequestRepo(item.url);
|
|
1161
|
+
const mergedAt = typeof item.mergedAt === "string" && item.mergedAt.trim().length > 0 ? item.mergedAt : null;
|
|
1162
|
+
const state = mergedAt !== null ? "merged" : item.state.trim().length > 0 ? item.state.toLowerCase() : "";
|
|
1163
|
+
const checks = parseStatusCheckRollup(item.statusCheckRollup);
|
|
1164
|
+
return {
|
|
1165
|
+
...(typeof item.number === "number" ? { number: item.number } : {}),
|
|
1166
|
+
...(repoIdentity ? { repoOwner: repoIdentity.owner, repoName: repoIdentity.name } : {}),
|
|
1167
|
+
url: item.url,
|
|
1168
|
+
title: item.title,
|
|
1169
|
+
state,
|
|
1170
|
+
baseRefName: item.baseRefName,
|
|
1171
|
+
headRefName: item.headRefName || fallbackHeadRefName,
|
|
1172
|
+
isMerged: mergedAt !== null,
|
|
1173
|
+
isDraft: item.isDraft ?? false,
|
|
1174
|
+
checks,
|
|
1175
|
+
checksStatus: computeChecksStatus(checks),
|
|
1176
|
+
reviewDecision: mapReviewDecision(item.reviewDecision),
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
function parseGitHubPullRequestRepo(url) {
|
|
1180
|
+
try {
|
|
1181
|
+
const parsed = new URL(url);
|
|
1182
|
+
if (parsed.hostname !== "github.com") {
|
|
1183
|
+
return null;
|
|
1184
|
+
}
|
|
1185
|
+
const [owner, name, kind] = parsed.pathname.split("/").filter(Boolean);
|
|
1186
|
+
if (!owner || !name || kind !== "pull") {
|
|
1187
|
+
return null;
|
|
1188
|
+
}
|
|
1189
|
+
return { owner, name };
|
|
1190
|
+
}
|
|
1191
|
+
catch {
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
export function parseStatusCheckRollup(value) {
|
|
1196
|
+
const directContexts = PullRequestStatusCheckRollupArraySchema.safeParse(value);
|
|
1197
|
+
if (!directContexts.success) {
|
|
1198
|
+
const legacyContexts = LegacyPullRequestStatusCheckRollupSchema.safeParse(value);
|
|
1199
|
+
if (!legacyContexts.success) {
|
|
1200
|
+
return [];
|
|
1201
|
+
}
|
|
1202
|
+
return parseStatusCheckRollup(legacyContexts.data.contexts);
|
|
1203
|
+
}
|
|
1204
|
+
const dedupedChecks = new Map();
|
|
1205
|
+
for (const entry of directContexts.data) {
|
|
1206
|
+
const parsed = PullRequestStatusCheckRollupNodeSchema.safeParse(entry);
|
|
1207
|
+
if (!parsed.success) {
|
|
1208
|
+
continue;
|
|
1209
|
+
}
|
|
1210
|
+
const check = buildPullRequestCheck(parsed.data);
|
|
1211
|
+
if (!check) {
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
const existing = dedupedChecks.get(check.name);
|
|
1215
|
+
if (!existing || check.recency > existing.recency) {
|
|
1216
|
+
dedupedChecks.set(check.name, check);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return Array.from(dedupedChecks.values(), ({ recency: _recency, ...check }) => check);
|
|
1220
|
+
}
|
|
1221
|
+
function buildPullRequestCheck(context) {
|
|
1222
|
+
if (context.__typename === "CheckRun") {
|
|
1223
|
+
return {
|
|
1224
|
+
name: context.name,
|
|
1225
|
+
status: mapCheckRunStatus(context.status, context.conclusion),
|
|
1226
|
+
url: typeof context.detailsUrl === "string" ? context.detailsUrl : null,
|
|
1227
|
+
...(typeof context.workflowName === "string" && context.workflowName.trim().length > 0
|
|
1228
|
+
? { workflow: context.workflowName }
|
|
1229
|
+
: {}),
|
|
1230
|
+
...formatCheckRunDuration(context),
|
|
1231
|
+
recency: getCheckRunRecency(context),
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
if (context.__typename === "StatusContext") {
|
|
1235
|
+
return {
|
|
1236
|
+
name: context.context,
|
|
1237
|
+
status: mapStatusContextState(context.state),
|
|
1238
|
+
url: typeof context.targetUrl === "string" ? context.targetUrl : null,
|
|
1239
|
+
recency: getStatusContextRecency(context),
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
return null;
|
|
1243
|
+
}
|
|
1244
|
+
function mapCheckRunStatus(status, conclusion) {
|
|
1245
|
+
if (status !== "COMPLETED") {
|
|
1246
|
+
return "pending";
|
|
1247
|
+
}
|
|
1248
|
+
switch (conclusion) {
|
|
1249
|
+
case "SUCCESS":
|
|
1250
|
+
return "success";
|
|
1251
|
+
case "FAILURE":
|
|
1252
|
+
case "TIMED_OUT":
|
|
1253
|
+
case "ACTION_REQUIRED":
|
|
1254
|
+
return "failure";
|
|
1255
|
+
case "CANCELLED":
|
|
1256
|
+
return "cancelled";
|
|
1257
|
+
case "SKIPPED":
|
|
1258
|
+
case "NEUTRAL":
|
|
1259
|
+
return "skipped";
|
|
1260
|
+
default:
|
|
1261
|
+
return "pending";
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function mapStatusContextState(state) {
|
|
1265
|
+
switch (state) {
|
|
1266
|
+
case "SUCCESS":
|
|
1267
|
+
return "success";
|
|
1268
|
+
case "FAILURE":
|
|
1269
|
+
case "ERROR":
|
|
1270
|
+
return "failure";
|
|
1271
|
+
case "EXPECTED":
|
|
1272
|
+
case "PENDING":
|
|
1273
|
+
return "pending";
|
|
1274
|
+
default:
|
|
1275
|
+
return "pending";
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
function getCheckRunRecency(context) {
|
|
1279
|
+
const workflowRunId = context.checkSuite?.workflowRun?.databaseId;
|
|
1280
|
+
if (typeof workflowRunId === "number") {
|
|
1281
|
+
return workflowRunId;
|
|
1282
|
+
}
|
|
1283
|
+
return parseOptionalTime(context.completedAt ?? context.startedAt ?? null);
|
|
1284
|
+
}
|
|
1285
|
+
function formatCheckRunDuration(context) {
|
|
1286
|
+
const startedAt = parseOptionalTime(context.startedAt ?? null);
|
|
1287
|
+
const completedAt = parseOptionalTime(context.completedAt ?? null);
|
|
1288
|
+
if (startedAt <= 0 || completedAt <= 0 || completedAt < startedAt) {
|
|
1289
|
+
return {};
|
|
1290
|
+
}
|
|
1291
|
+
const durationSeconds = Math.floor((completedAt - startedAt) / 1000);
|
|
1292
|
+
return { duration: formatDurationSeconds(durationSeconds) };
|
|
1293
|
+
}
|
|
1294
|
+
function formatDurationSeconds(totalSeconds) {
|
|
1295
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
1296
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
1297
|
+
const seconds = totalSeconds % 60;
|
|
1298
|
+
const parts = [];
|
|
1299
|
+
if (hours > 0) {
|
|
1300
|
+
parts.push(`${hours}h`);
|
|
1301
|
+
}
|
|
1302
|
+
if (minutes > 0) {
|
|
1303
|
+
parts.push(`${minutes}m`);
|
|
1304
|
+
}
|
|
1305
|
+
if (seconds > 0 || parts.length === 0) {
|
|
1306
|
+
parts.push(`${seconds}s`);
|
|
1307
|
+
}
|
|
1308
|
+
return parts.join(" ");
|
|
1309
|
+
}
|
|
1310
|
+
function getStatusContextRecency(context) {
|
|
1311
|
+
return parseOptionalTime(context.createdAt ?? null);
|
|
1312
|
+
}
|
|
1313
|
+
function parseOptionalTime(timestamp) {
|
|
1314
|
+
if (!timestamp) {
|
|
1315
|
+
return 0;
|
|
1316
|
+
}
|
|
1317
|
+
const time = Date.parse(timestamp);
|
|
1318
|
+
return Number.isNaN(time) ? 0 : time;
|
|
1319
|
+
}
|
|
1320
|
+
function computeChecksStatus(checks) {
|
|
1321
|
+
if (checks.length === 0) {
|
|
1322
|
+
return "none";
|
|
1323
|
+
}
|
|
1324
|
+
if (checks.some((check) => check.status === "failure")) {
|
|
1325
|
+
return "failure";
|
|
1326
|
+
}
|
|
1327
|
+
if (checks.some((check) => check.status === "pending")) {
|
|
1328
|
+
return "pending";
|
|
1329
|
+
}
|
|
1330
|
+
return "success";
|
|
1331
|
+
}
|
|
1332
|
+
function mapReviewDecision(value) {
|
|
1333
|
+
const reviewDecision = PullRequestReviewDecisionSchema.parse(value);
|
|
1334
|
+
if (reviewDecision === "APPROVED") {
|
|
1335
|
+
return "approved";
|
|
1336
|
+
}
|
|
1337
|
+
if (reviewDecision === "CHANGES_REQUESTED") {
|
|
1338
|
+
return "changes_requested";
|
|
1339
|
+
}
|
|
1340
|
+
if (reviewDecision === "REVIEW_REQUIRED") {
|
|
1341
|
+
return "pending";
|
|
1342
|
+
}
|
|
1343
|
+
return null;
|
|
1344
|
+
}
|
|
1345
|
+
export async function resolveGitHubRepo(cwd, options) {
|
|
1346
|
+
try {
|
|
1347
|
+
const remoteUrl = await options.workspaceGitService.resolveRepoRemoteUrl(cwd, options.readOptions);
|
|
1348
|
+
return parseGitHubRepoFromRemote(remoteUrl?.trim() ?? "");
|
|
1349
|
+
}
|
|
1350
|
+
catch {
|
|
1351
|
+
return null;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
function parseGitHubRepoFromRemote(url) {
|
|
1355
|
+
if (!url) {
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
let cleaned = url;
|
|
1359
|
+
if (cleaned.startsWith("git@github.com:")) {
|
|
1360
|
+
cleaned = cleaned.slice("git@github.com:".length);
|
|
1361
|
+
}
|
|
1362
|
+
else if (cleaned.startsWith("https://github.com/")) {
|
|
1363
|
+
cleaned = cleaned.slice("https://github.com/".length);
|
|
1364
|
+
}
|
|
1365
|
+
else if (cleaned.startsWith("http://github.com/")) {
|
|
1366
|
+
cleaned = cleaned.slice("http://github.com/".length);
|
|
1367
|
+
}
|
|
1368
|
+
else {
|
|
1369
|
+
const marker = "github.com/";
|
|
1370
|
+
const index = cleaned.indexOf(marker);
|
|
1371
|
+
if (index === -1) {
|
|
1372
|
+
return null;
|
|
1373
|
+
}
|
|
1374
|
+
cleaned = cleaned.slice(index + marker.length);
|
|
1375
|
+
}
|
|
1376
|
+
if (cleaned.endsWith(".git")) {
|
|
1377
|
+
cleaned = cleaned.slice(0, -".git".length);
|
|
1378
|
+
}
|
|
1379
|
+
return cleaned.includes("/") ? cleaned : null;
|
|
1380
|
+
}
|
|
1381
|
+
//# sourceMappingURL=github-service.js.map
|