@mastra/factory 0.0.0-fix-thread-subscription-multi-instance-redis-streams-20260730204150
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/CHANGELOG.md +695 -0
- package/LICENSE.md +30 -0
- package/README.md +27 -0
- package/dist/auth.d.ts +181 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +505 -0
- package/dist/auth.js.map +1 -0
- package/dist/capabilities/connection.d.ts +9 -0
- package/dist/capabilities/connection.d.ts.map +1 -0
- package/dist/capabilities/connection.js +1 -0
- package/dist/capabilities/intake.d.ts +140 -0
- package/dist/capabilities/intake.d.ts.map +1 -0
- package/dist/capabilities/intake.js +1 -0
- package/dist/capabilities/version-control.d.ts +245 -0
- package/dist/capabilities/version-control.d.ts.map +1 -0
- package/dist/capabilities/version-control.js +1 -0
- package/dist/factory.d.ts +156 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +483 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/integrations/base.d.ts +210 -0
- package/dist/integrations/base.d.ts.map +1 -0
- package/dist/integrations/base.js +1 -0
- package/dist/integrations/github/config.d.ts +60 -0
- package/dist/integrations/github/config.d.ts.map +1 -0
- package/dist/integrations/github/config.js +42 -0
- package/dist/integrations/github/config.js.map +1 -0
- package/dist/integrations/github/factory-session.d.ts +12 -0
- package/dist/integrations/github/factory-session.d.ts.map +1 -0
- package/dist/integrations/github/factory-session.js +37 -0
- package/dist/integrations/github/factory-session.js.map +1 -0
- package/dist/integrations/github/integration.d.ts +191 -0
- package/dist/integrations/github/integration.d.ts.map +1 -0
- package/dist/integrations/github/integration.js +1077 -0
- package/dist/integrations/github/integration.js.map +1 -0
- package/dist/integrations/github/issue-triage.d.ts +27 -0
- package/dist/integrations/github/issue-triage.d.ts.map +1 -0
- package/dist/integrations/github/issue-triage.js +82 -0
- package/dist/integrations/github/issue-triage.js.map +1 -0
- package/dist/integrations/github/pat.d.ts +38 -0
- package/dist/integrations/github/pat.d.ts.map +1 -0
- package/dist/integrations/github/pat.js +59 -0
- package/dist/integrations/github/pat.js.map +1 -0
- package/dist/integrations/github/provenance.d.ts +27 -0
- package/dist/integrations/github/provenance.d.ts.map +1 -0
- package/dist/integrations/github/provenance.js +75 -0
- package/dist/integrations/github/provenance.js.map +1 -0
- package/dist/integrations/github/routes.d.ts +69 -0
- package/dist/integrations/github/routes.d.ts.map +1 -0
- package/dist/integrations/github/routes.js +1324 -0
- package/dist/integrations/github/routes.js.map +1 -0
- package/dist/integrations/github/rules.d.ts +78 -0
- package/dist/integrations/github/rules.d.ts.map +1 -0
- package/dist/integrations/github/rules.js +362 -0
- package/dist/integrations/github/rules.js.map +1 -0
- package/dist/integrations/github/sandbox-release.d.ts +38 -0
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -0
- package/dist/integrations/github/sandbox-release.js +74 -0
- package/dist/integrations/github/sandbox-release.js.map +1 -0
- package/dist/integrations/github/sandbox.d.ts +281 -0
- package/dist/integrations/github/sandbox.d.ts.map +1 -0
- package/dist/integrations/github/sandbox.js +547 -0
- package/dist/integrations/github/sandbox.js.map +1 -0
- package/dist/integrations/github/session-subscriptions.d.ts +26 -0
- package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/session-subscriptions.js +192 -0
- package/dist/integrations/github/session-subscriptions.js.map +1 -0
- package/dist/integrations/github/subscriptions.d.ts +54 -0
- package/dist/integrations/github/subscriptions.d.ts.map +1 -0
- package/dist/integrations/github/subscriptions.js +62 -0
- package/dist/integrations/github/subscriptions.js.map +1 -0
- package/dist/integrations/github/token-refresh.d.ts +12 -0
- package/dist/integrations/github/token-refresh.d.ts.map +1 -0
- package/dist/integrations/github/token-refresh.js +24 -0
- package/dist/integrations/github/token-refresh.js.map +1 -0
- package/dist/integrations/github/webhook.d.ts +87 -0
- package/dist/integrations/github/webhook.d.ts.map +1 -0
- package/dist/integrations/github/webhook.js +397 -0
- package/dist/integrations/github/webhook.js.map +1 -0
- package/dist/integrations/linear/agent-tools.d.ts +34 -0
- package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
- package/dist/integrations/linear/agent-tools.js +85 -0
- package/dist/integrations/linear/agent-tools.js.map +1 -0
- package/dist/integrations/linear/integration.d.ts +233 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -0
- package/dist/integrations/linear/integration.js +672 -0
- package/dist/integrations/linear/integration.js.map +1 -0
- package/dist/integrations/linear/routes.d.ts +61 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -0
- package/dist/integrations/linear/routes.js +239 -0
- package/dist/integrations/linear/routes.js.map +1 -0
- package/dist/integrations/linear/rules.d.ts +42 -0
- package/dist/integrations/linear/rules.d.ts.map +1 -0
- package/dist/integrations/linear/rules.js +133 -0
- package/dist/integrations/linear/rules.js.map +1 -0
- package/dist/integrations/linear/storage.d.ts +65 -0
- package/dist/integrations/linear/storage.d.ts.map +1 -0
- package/dist/integrations/linear/storage.js +1 -0
- package/dist/integrations/platform/api-client.d.ts +26 -0
- package/dist/integrations/platform/api-client.d.ts.map +1 -0
- package/dist/integrations/platform/api-client.js +156 -0
- package/dist/integrations/platform/api-client.js.map +1 -0
- package/dist/integrations/platform/github/event-worker.d.ts +49 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +348 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -0
- package/dist/integrations/platform/github/integration.d.ts +50 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -0
- package/dist/integrations/platform/github/integration.js +961 -0
- package/dist/integrations/platform/github/integration.js.map +1 -0
- package/dist/integrations/platform/linear/integration.d.ts +46 -0
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
- package/dist/integrations/platform/linear/integration.js +483 -0
- package/dist/integrations/platform/linear/integration.js.map +1 -0
- package/dist/integrations/workos/integration.d.ts +39 -0
- package/dist/integrations/workos/integration.d.ts.map +1 -0
- package/dist/integrations/workos/integration.js +91 -0
- package/dist/integrations/workos/integration.js.map +1 -0
- package/dist/routes/config.d.ts +235 -0
- package/dist/routes/config.d.ts.map +1 -0
- package/dist/routes/config.js +972 -0
- package/dist/routes/config.js.map +1 -0
- package/dist/routes/custom-provider-source.d.ts +52 -0
- package/dist/routes/custom-provider-source.d.ts.map +1 -0
- package/dist/routes/custom-provider-source.js +124 -0
- package/dist/routes/custom-provider-source.js.map +1 -0
- package/dist/routes/fs.d.ts +151 -0
- package/dist/routes/fs.d.ts.map +1 -0
- package/dist/routes/fs.js +708 -0
- package/dist/routes/fs.js.map +1 -0
- package/dist/routes/intake.d.ts +23 -0
- package/dist/routes/intake.d.ts.map +1 -0
- package/dist/routes/intake.js +179 -0
- package/dist/routes/intake.js.map +1 -0
- package/dist/routes/oauth.d.ts +43 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +463 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/projects.d.ts +19 -0
- package/dist/routes/projects.d.ts.map +1 -0
- package/dist/routes/projects.js +461 -0
- package/dist/routes/projects.js.map +1 -0
- package/dist/routes/provider-credentials.d.ts +81 -0
- package/dist/routes/provider-credentials.d.ts.map +1 -0
- package/dist/routes/provider-credentials.js +86 -0
- package/dist/routes/provider-credentials.js.map +1 -0
- package/dist/routes/route.d.ts +48 -0
- package/dist/routes/route.d.ts.map +1 -0
- package/dist/routes/route.js +15 -0
- package/dist/routes/route.js.map +1 -0
- package/dist/routes/skills.d.ts +30 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +168 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/surface.d.ts +91 -0
- package/dist/routes/surface.d.ts.map +1 -0
- package/dist/routes/surface.js +260 -0
- package/dist/routes/surface.js.map +1 -0
- package/dist/routes/tenant-credentials.d.ts +74 -0
- package/dist/routes/tenant-credentials.d.ts.map +1 -0
- package/dist/routes/tenant-credentials.js +156 -0
- package/dist/routes/tenant-credentials.js.map +1 -0
- package/dist/routes/work-items.d.ts +40 -0
- package/dist/routes/work-items.d.ts.map +1 -0
- package/dist/routes/work-items.js +695 -0
- package/dist/routes/work-items.js.map +1 -0
- package/dist/rules/binding-context.d.ts +5 -0
- package/dist/rules/binding-context.d.ts.map +1 -0
- package/dist/rules/binding-context.js +28 -0
- package/dist/rules/binding-context.js.map +1 -0
- package/dist/rules/defaults.d.ts +9 -0
- package/dist/rules/defaults.d.ts.map +1 -0
- package/dist/rules/defaults.js +260 -0
- package/dist/rules/defaults.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +42 -0
- package/dist/rules/dispatcher.d.ts.map +1 -0
- package/dist/rules/dispatcher.js +518 -0
- package/dist/rules/dispatcher.js.map +1 -0
- package/dist/rules/index.d.ts +7 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +5 -0
- package/dist/rules/processor.d.ts +54 -0
- package/dist/rules/processor.d.ts.map +1 -0
- package/dist/rules/processor.js +378 -0
- package/dist/rules/processor.js.map +1 -0
- package/dist/rules/resolve.d.ts +17 -0
- package/dist/rules/resolve.d.ts.map +1 -0
- package/dist/rules/resolve.js +30 -0
- package/dist/rules/resolve.js.map +1 -0
- package/dist/rules/start-coordinator.d.ts +60 -0
- package/dist/rules/start-coordinator.d.ts.map +1 -0
- package/dist/rules/start-coordinator.js +194 -0
- package/dist/rules/start-coordinator.js.map +1 -0
- package/dist/rules/tools.d.ts +10 -0
- package/dist/rules/tools.d.ts.map +1 -0
- package/dist/rules/tools.js +58 -0
- package/dist/rules/tools.js.map +1 -0
- package/dist/rules/transition-service.d.ts +49 -0
- package/dist/rules/transition-service.d.ts.map +1 -0
- package/dist/rules/transition-service.js +232 -0
- package/dist/rules/transition-service.js.map +1 -0
- package/dist/rules/types.d.ts +234 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +38 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/rules/validation.d.ts +11 -0
- package/dist/rules/validation.d.ts.map +1 -0
- package/dist/rules/validation.js +308 -0
- package/dist/rules/validation.js.map +1 -0
- package/dist/sandbox/fleet.d.ts +222 -0
- package/dist/sandbox/fleet.d.ts.map +1 -0
- package/dist/sandbox/fleet.js +322 -0
- package/dist/sandbox/fleet.js.map +1 -0
- package/dist/sandbox/reattach.d.ts +3 -0
- package/dist/sandbox/reattach.d.ts.map +1 -0
- package/dist/sandbox/reattach.js +16 -0
- package/dist/sandbox/reattach.js.map +1 -0
- package/dist/server-error.d.ts +12 -0
- package/dist/server-error.d.ts.map +1 -0
- package/dist/server-error.js +16 -0
- package/dist/server-error.js.map +1 -0
- package/dist/skills/service.d.ts +48 -0
- package/dist/skills/service.d.ts.map +1 -0
- package/dist/skills/service.js +40 -0
- package/dist/skills/service.js.map +1 -0
- package/dist/spa-static.d.ts +33 -0
- package/dist/spa-static.d.ts.map +1 -0
- package/dist/spa-static.js +104 -0
- package/dist/spa-static.js.map +1 -0
- package/dist/state-signing.d.ts +59 -0
- package/dist/state-signing.d.ts.map +1 -0
- package/dist/state-signing.js +83 -0
- package/dist/state-signing.js.map +1 -0
- package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
- package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
- package/dist/storage/domains/audit/agent-audit.js +66 -0
- package/dist/storage/domains/audit/agent-audit.js.map +1 -0
- package/dist/storage/domains/audit/base.d.ts +140 -0
- package/dist/storage/domains/audit/base.d.ts.map +1 -0
- package/dist/storage/domains/audit/base.js +184 -0
- package/dist/storage/domains/audit/base.js.map +1 -0
- package/dist/storage/domains/audit/domain.d.ts +69 -0
- package/dist/storage/domains/audit/domain.d.ts.map +1 -0
- package/dist/storage/domains/audit/domain.js +167 -0
- package/dist/storage/domains/audit/domain.js.map +1 -0
- package/dist/storage/domains/channel-identity/base.d.ts +96 -0
- package/dist/storage/domains/channel-identity/base.d.ts.map +1 -0
- package/dist/storage/domains/channel-identity/base.js +149 -0
- package/dist/storage/domains/channel-identity/base.js.map +1 -0
- package/dist/storage/domains/credentials/base.d.ts +134 -0
- package/dist/storage/domains/credentials/base.d.ts.map +1 -0
- package/dist/storage/domains/credentials/base.js +273 -0
- package/dist/storage/domains/credentials/base.js.map +1 -0
- package/dist/storage/domains/custom-providers/base.d.ts +57 -0
- package/dist/storage/domains/custom-providers/base.d.ts.map +1 -0
- package/dist/storage/domains/custom-providers/base.js +145 -0
- package/dist/storage/domains/custom-providers/base.js.map +1 -0
- package/dist/storage/domains/intake/base.d.ts +38 -0
- package/dist/storage/domains/intake/base.d.ts.map +1 -0
- package/dist/storage/domains/intake/base.js +80 -0
- package/dist/storage/domains/intake/base.js.map +1 -0
- package/dist/storage/domains/integrations/base.d.ts +126 -0
- package/dist/storage/domains/integrations/base.d.ts.map +1 -0
- package/dist/storage/domains/integrations/base.js +317 -0
- package/dist/storage/domains/integrations/base.js.map +1 -0
- package/dist/storage/domains/memory-settings/base.d.ts +62 -0
- package/dist/storage/domains/memory-settings/base.d.ts.map +1 -0
- package/dist/storage/domains/memory-settings/base.js +126 -0
- package/dist/storage/domains/memory-settings/base.js.map +1 -0
- package/dist/storage/domains/model-packs/base.d.ts +49 -0
- package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
- package/dist/storage/domains/model-packs/base.js +96 -0
- package/dist/storage/domains/model-packs/base.js.map +1 -0
- package/dist/storage/domains/projects/base.d.ts +58 -0
- package/dist/storage/domains/projects/base.d.ts.map +1 -0
- package/dist/storage/domains/projects/base.js +111 -0
- package/dist/storage/domains/projects/base.js.map +1 -0
- package/dist/storage/domains/queue-health/base.d.ts +52 -0
- package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
- package/dist/storage/domains/queue-health/base.js +122 -0
- package/dist/storage/domains/queue-health/base.js.map +1 -0
- package/dist/storage/domains/source-control/base.d.ts +358 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -0
- package/dist/storage/domains/source-control/base.js +908 -0
- package/dist/storage/domains/source-control/base.js.map +1 -0
- package/dist/storage/domains/source-control/inmemory.d.ts +175 -0
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/source-control/inmemory.js +377 -0
- package/dist/storage/domains/source-control/inmemory.js.map +1 -0
- package/dist/storage/domains/work-items/base.d.ts +412 -0
- package/dist/storage/domains/work-items/base.d.ts.map +1 -0
- package/dist/storage/domains/work-items/base.js +1309 -0
- package/dist/storage/domains/work-items/base.js.map +1 -0
- package/dist/storage/domains/work-items/metrics.d.ts +101 -0
- package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
- package/dist/storage/domains/work-items/metrics.js +221 -0
- package/dist/storage/domains/work-items/metrics.js.map +1 -0
- package/dist/timing.d.ts +15 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +34 -0
- package/dist/timing.js.map +1 -0
- package/dist/workspace.d.ts +24 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +286 -0
- package/dist/workspace.js.map +1 -0
- package/factory-skills/configure-factory-rules/SKILL.md +67 -0
- package/factory-skills/factory-plan/SKILL.md +61 -0
- package/factory-skills/factory-review/SKILL.md +155 -0
- package/factory-skills/factory-triage/SKILL.md +77 -0
- package/package.json +81 -0
|
@@ -0,0 +1,961 @@
|
|
|
1
|
+
import { createGithubSubscriptionTools, parseCreatedPullRequest, subscribeCurrentSessionToPullRequest } from "../../github/session-subscriptions.js";
|
|
2
|
+
import { runGithubIssueTriage } from "../../github/issue-triage.js";
|
|
3
|
+
import { buildGithubRoutes } from "../../github/routes.js";
|
|
4
|
+
import { attachGithubReconciler, attachGithubRules } from "../../github/rules.js";
|
|
5
|
+
import { PlatformApiClient, PlatformApiError, logPlatformInfo, logPlatformWarn, platformApiClientConfigFromEnv } from "../api-client.js";
|
|
6
|
+
import { PlatformGithubEventWorker } from "./event-worker.js";
|
|
7
|
+
import { registerApiRoute } from "@mastra/core/server";
|
|
8
|
+
//#region src/integrations/platform/github/integration.ts
|
|
9
|
+
const PAGE_SIZE = 30;
|
|
10
|
+
const API_PREFIX = "/v1/server";
|
|
11
|
+
/**
|
|
12
|
+
* How long an installation's repository listing may be reused. The repos
|
|
13
|
+
* route and token minting both call it — often several times within one UI
|
|
14
|
+
* interaction — and each call is a full Platform round trip.
|
|
15
|
+
*/
|
|
16
|
+
const INSTALLATION_REPOS_CACHE_TTL_MS = 3e4;
|
|
17
|
+
/**
|
|
18
|
+
* How long a minted repository-scoped token may be reused. GitHub
|
|
19
|
+
* installation tokens live ~60 minutes; 5 minutes keeps a wide validity
|
|
20
|
+
* margin while collapsing the per-session-materialization mint round trip.
|
|
21
|
+
*/
|
|
22
|
+
const REPOSITORY_ACCESS_CACHE_TTL_MS = 5 * 6e4;
|
|
23
|
+
/**
|
|
24
|
+
* Upper bound for both TTL caches. Entries expire lazily on re-access, so
|
|
25
|
+
* without a hard cap keys that stop being queried would accumulate for the
|
|
26
|
+
* integration's (long) lifetime. Insertion-order eviction — matches the
|
|
27
|
+
* bounded verification cache in `@mastra/auth-studio`.
|
|
28
|
+
*/
|
|
29
|
+
const MAX_CACHE_ENTRIES = 1e3;
|
|
30
|
+
function setBounded(cache, key, value) {
|
|
31
|
+
cache.delete(key);
|
|
32
|
+
cache.set(key, value);
|
|
33
|
+
if (cache.size > MAX_CACHE_ENTRIES) {
|
|
34
|
+
const oldest = cache.keys().next().value;
|
|
35
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const REPOSITORY_TOKEN_PERMISSIONS = {
|
|
39
|
+
contents: "write",
|
|
40
|
+
issues: "write",
|
|
41
|
+
pull_requests: "write"
|
|
42
|
+
};
|
|
43
|
+
function loose(c) {
|
|
44
|
+
return c;
|
|
45
|
+
}
|
|
46
|
+
function routeBaseUrl(ctx, requestUrl) {
|
|
47
|
+
return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\/+$/, "");
|
|
48
|
+
}
|
|
49
|
+
var PlatformGithubIntegration = class {
|
|
50
|
+
id = "github";
|
|
51
|
+
#client;
|
|
52
|
+
#endpointHost;
|
|
53
|
+
#pollingEnabled;
|
|
54
|
+
#pollingIntervalMs;
|
|
55
|
+
#reconcileEnabled;
|
|
56
|
+
#storage;
|
|
57
|
+
#integrationStorage;
|
|
58
|
+
/** installationId → cached repository listing (TTL-bounded). */
|
|
59
|
+
#installationReposCache = /* @__PURE__ */ new Map();
|
|
60
|
+
/** `orgId:repositoryId` → cached repository access (TTL-bounded). */
|
|
61
|
+
#repositoryAccessCache = /* @__PURE__ */ new Map();
|
|
62
|
+
#runIssueTriage;
|
|
63
|
+
intake = {
|
|
64
|
+
resolveIntakeDispatch: (input) => this.#resolveIntakeDispatch(input),
|
|
65
|
+
listSources: async ({ orgId, userId }) => {
|
|
66
|
+
const usable = (await this.#client.request("GET", `${API_PREFIX}/github-app/installations`)).installations.filter((installation) => installation.usable && !installation.suspendedAt);
|
|
67
|
+
return (await Promise.all(usable.map(async (installation) => {
|
|
68
|
+
const storedInstallation = await this.versionControl.registerInstallation({
|
|
69
|
+
orgId,
|
|
70
|
+
userId,
|
|
71
|
+
installation: {
|
|
72
|
+
externalId: String(installation.installationId),
|
|
73
|
+
accountName: installation.accountLogin,
|
|
74
|
+
accountType: installation.accountType
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
const result = await this.#client.request("GET", `${API_PREFIX}/github-app/installations/${installation.installationId}/repositories`);
|
|
78
|
+
await this.versionControl.registerRepositories({
|
|
79
|
+
orgId,
|
|
80
|
+
installationId: storedInstallation.id,
|
|
81
|
+
repositories: result.repositories.map((repository) => ({
|
|
82
|
+
externalId: String(repository.id),
|
|
83
|
+
slug: repository.fullName,
|
|
84
|
+
defaultBranch: repository.defaultBranch,
|
|
85
|
+
metadata: {
|
|
86
|
+
private: repository.private,
|
|
87
|
+
url: repository.htmlUrl
|
|
88
|
+
}
|
|
89
|
+
}))
|
|
90
|
+
});
|
|
91
|
+
return result.repositories.map((repository) => ({
|
|
92
|
+
repository,
|
|
93
|
+
installation
|
|
94
|
+
}));
|
|
95
|
+
}))).flat().map(({ repository, installation }) => ({
|
|
96
|
+
id: repository.fullName,
|
|
97
|
+
name: repository.fullName,
|
|
98
|
+
type: "repository",
|
|
99
|
+
metadata: {
|
|
100
|
+
installationId: installation.installationId,
|
|
101
|
+
accountLogin: installation.accountLogin,
|
|
102
|
+
accountType: installation.accountType,
|
|
103
|
+
repositoryId: repository.id,
|
|
104
|
+
defaultBranch: repository.defaultBranch,
|
|
105
|
+
private: repository.private,
|
|
106
|
+
url: repository.htmlUrl
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
},
|
|
110
|
+
listItems: async ({ sourceIds, cursor }) => {
|
|
111
|
+
const page = parsePositiveCursor(cursor);
|
|
112
|
+
const pages = await Promise.all(sourceIds.map(async (sourceId) => {
|
|
113
|
+
const connection = {
|
|
114
|
+
type: "app-installation",
|
|
115
|
+
installationId: 1
|
|
116
|
+
};
|
|
117
|
+
const [issues, pullRequests] = await Promise.all([this.#listIssues(connection, sourceId, page), this.#listPullRequests({
|
|
118
|
+
connection,
|
|
119
|
+
sourceId,
|
|
120
|
+
includeDrafts: false,
|
|
121
|
+
cursor: String(page)
|
|
122
|
+
})]);
|
|
123
|
+
return {
|
|
124
|
+
items: [...issues.issues.map((issue) => ({
|
|
125
|
+
source: {
|
|
126
|
+
type: "issue",
|
|
127
|
+
externalId: `${sourceId}:${issue.id}`,
|
|
128
|
+
url: issue.url
|
|
129
|
+
},
|
|
130
|
+
sourceId,
|
|
131
|
+
title: issue.title,
|
|
132
|
+
status: issue.state ?? void 0,
|
|
133
|
+
labels: issue.labels,
|
|
134
|
+
assignee: issue.assignee,
|
|
135
|
+
createdAt: issue.createdAt,
|
|
136
|
+
updatedAt: issue.updatedAt,
|
|
137
|
+
metadata: {
|
|
138
|
+
repository: sourceId,
|
|
139
|
+
number: Number(issue.id),
|
|
140
|
+
author: issue.author
|
|
141
|
+
}
|
|
142
|
+
})), ...pullRequests.pullRequests.map((pullRequest) => ({
|
|
143
|
+
source: {
|
|
144
|
+
type: "pull-request",
|
|
145
|
+
externalId: `${sourceId}:${pullRequest.id}`,
|
|
146
|
+
url: pullRequest.url
|
|
147
|
+
},
|
|
148
|
+
sourceId,
|
|
149
|
+
title: pullRequest.title,
|
|
150
|
+
status: pullRequest.state,
|
|
151
|
+
createdAt: pullRequest.createdAt,
|
|
152
|
+
updatedAt: pullRequest.updatedAt,
|
|
153
|
+
metadata: {
|
|
154
|
+
repository: sourceId,
|
|
155
|
+
number: Number(pullRequest.id),
|
|
156
|
+
author: pullRequest.author,
|
|
157
|
+
baseBranch: pullRequest.baseBranch,
|
|
158
|
+
headBranch: pullRequest.headBranch
|
|
159
|
+
}
|
|
160
|
+
}))],
|
|
161
|
+
hasNextPage: issues.nextCursor !== null || pullRequests.nextCursor !== null
|
|
162
|
+
};
|
|
163
|
+
}));
|
|
164
|
+
return {
|
|
165
|
+
items: pages.flatMap((result) => result.items),
|
|
166
|
+
nextCursor: pages.some((result) => result.hasNextPage) ? String(page + 1) : null
|
|
167
|
+
};
|
|
168
|
+
},
|
|
169
|
+
listIssues: async (input) => {
|
|
170
|
+
requireGithubConnection(input.connection);
|
|
171
|
+
const sourceId = requireSingleSource(input.sourceIds, "GitHub Intake requires exactly one repository source.");
|
|
172
|
+
return this.#listIssues(input.connection, sourceId, parsePositiveCursor(input.cursor), input.labels);
|
|
173
|
+
},
|
|
174
|
+
getIssue: (input) => this.#getIssue(input.connection, input.sourceId, input.issueId),
|
|
175
|
+
createComment: (input) => this.#createIssueComment(input),
|
|
176
|
+
updateIssue: (input) => this.#updateIntakeIssue(input)
|
|
177
|
+
};
|
|
178
|
+
versionControl = {
|
|
179
|
+
initialize: ({ storage }) => {
|
|
180
|
+
this.#storage = storage;
|
|
181
|
+
},
|
|
182
|
+
registerInstallation: ({ orgId, userId, installation }) => this.storage.installations.upsert({
|
|
183
|
+
orgId,
|
|
184
|
+
connectedByUserId: userId,
|
|
185
|
+
externalId: installation.externalId,
|
|
186
|
+
accountName: installation.accountName,
|
|
187
|
+
accountType: installation.accountType,
|
|
188
|
+
providerMetadata: installation.metadata
|
|
189
|
+
}),
|
|
190
|
+
registerRepositories: ({ orgId, installationId, repositories }) => Promise.all(repositories.map((repository) => this.storage.repositories.upsert({
|
|
191
|
+
orgId,
|
|
192
|
+
input: {
|
|
193
|
+
installationId,
|
|
194
|
+
externalId: repository.externalId,
|
|
195
|
+
slug: repository.slug,
|
|
196
|
+
defaultBranch: repository.defaultBranch,
|
|
197
|
+
providerMetadata: repository.metadata
|
|
198
|
+
}
|
|
199
|
+
}))),
|
|
200
|
+
getRepositoryAccess: async ({ orgId, repositoryId }) => {
|
|
201
|
+
const cacheKey = `${orgId}:${repositoryId}`;
|
|
202
|
+
const cached = this.#repositoryAccessCache.get(cacheKey);
|
|
203
|
+
if (cached && cached.expiresAt > Date.now()) return cached.access;
|
|
204
|
+
this.#repositoryAccessCache.delete(cacheKey);
|
|
205
|
+
const repository = await this.storage.repositories.get({
|
|
206
|
+
orgId,
|
|
207
|
+
id: repositoryId
|
|
208
|
+
});
|
|
209
|
+
if (!repository) throw new Error("Version-control repository not found.");
|
|
210
|
+
const cloneUrl = `https://github.com/${repository.slug}.git`;
|
|
211
|
+
const installation = await this.storage.installations.get({
|
|
212
|
+
orgId,
|
|
213
|
+
id: repository.installationId
|
|
214
|
+
});
|
|
215
|
+
if (!installation) throw new Error("Version-control installation not found.");
|
|
216
|
+
const installationId = parsePositiveInteger(installation.externalId);
|
|
217
|
+
if (installationId === null) throw new Error("GitHub installation id is invalid.");
|
|
218
|
+
const repositoryName = splitRepository(repository.slug).repo;
|
|
219
|
+
try {
|
|
220
|
+
const access = {
|
|
221
|
+
cloneUrl,
|
|
222
|
+
authorization: {
|
|
223
|
+
scheme: "bearer",
|
|
224
|
+
token: (await this.#client.request("POST", `${API_PREFIX}/github-app/installations/${installationId}/token`, {
|
|
225
|
+
repositories: [repositoryName],
|
|
226
|
+
permissions: REPOSITORY_TOKEN_PERMISSIONS
|
|
227
|
+
})).token
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
setBounded(this.#repositoryAccessCache, cacheKey, {
|
|
231
|
+
access,
|
|
232
|
+
expiresAt: Date.now() + REPOSITORY_ACCESS_CACHE_TTL_MS
|
|
233
|
+
});
|
|
234
|
+
return access;
|
|
235
|
+
} catch (err) {
|
|
236
|
+
if (!isNotFound(err) || !installation.accountName) throw err;
|
|
237
|
+
const newInstallation = (await this.storage.installations.list({ orgId })).find((inst) => inst.accountName === installation.accountName && inst.id !== installation.id);
|
|
238
|
+
if (!newInstallation) throw err;
|
|
239
|
+
const newInstallationId = parsePositiveInteger(newInstallation.externalId);
|
|
240
|
+
if (newInstallationId === null) throw err;
|
|
241
|
+
await this.storage.repositories.migrateInstallation({
|
|
242
|
+
orgId,
|
|
243
|
+
id: repositoryId,
|
|
244
|
+
newInstallationId: newInstallation.id
|
|
245
|
+
});
|
|
246
|
+
const access = {
|
|
247
|
+
cloneUrl,
|
|
248
|
+
authorization: {
|
|
249
|
+
scheme: "bearer",
|
|
250
|
+
token: (await this.#client.request("POST", `${API_PREFIX}/github-app/installations/${newInstallationId}/token`, {
|
|
251
|
+
repositories: [repositoryName],
|
|
252
|
+
permissions: REPOSITORY_TOKEN_PERMISSIONS
|
|
253
|
+
})).token
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
setBounded(this.#repositoryAccessCache, cacheKey, {
|
|
257
|
+
access,
|
|
258
|
+
expiresAt: Date.now() + REPOSITORY_ACCESS_CACHE_TTL_MS
|
|
259
|
+
});
|
|
260
|
+
return access;
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
listPullRequests: (input) => this.#listPullRequests(input),
|
|
264
|
+
getPullRequest: (input) => this.#getPullRequest(input),
|
|
265
|
+
createPullRequest: (input) => this.#createPullRequest(input),
|
|
266
|
+
updatePullRequest: (input) => this.#updatePullRequest(input),
|
|
267
|
+
closePullRequest: (input) => this.#updatePullRequest({
|
|
268
|
+
...input,
|
|
269
|
+
state: "closed"
|
|
270
|
+
}),
|
|
271
|
+
mergePullRequest: (input) => this.#mergePullRequest(input),
|
|
272
|
+
listComments: (input) => this.#listComments(input),
|
|
273
|
+
createComment: (input) => this.#createComment(input),
|
|
274
|
+
updateComment: (input) => this.#updateComment(input),
|
|
275
|
+
deleteComment: (input) => this.#deleteComment(input),
|
|
276
|
+
listReviews: (input) => this.#listReviews(input),
|
|
277
|
+
getReview: (input) => this.#getReview(input),
|
|
278
|
+
createReview: (input) => this.#createReview(input),
|
|
279
|
+
updateReview: (input) => this.#updateReview(input),
|
|
280
|
+
submitReview: (input) => this.#submitReview(input),
|
|
281
|
+
dismissReview: (input) => this.#dismissReview(input),
|
|
282
|
+
deletePendingReview: (input) => this.#deletePendingReview(input),
|
|
283
|
+
listReviewComments: (input) => this.#listReviewComments(input),
|
|
284
|
+
createReviewComment: (input) => this.#createReviewComment(input),
|
|
285
|
+
updateReviewComment: (input) => this.#updateReviewComment(input),
|
|
286
|
+
deleteReviewComment: (input) => this.#deleteReviewComment(input),
|
|
287
|
+
listRequestedReviewers: (input) => this.#requestedReviewers("GET", input),
|
|
288
|
+
requestReviewers: (input) => this.#requestedReviewers("POST", input),
|
|
289
|
+
removeRequestedReviewers: (input) => this.#requestedReviewers("DELETE", input)
|
|
290
|
+
};
|
|
291
|
+
constructor(options = {}) {
|
|
292
|
+
const config = platformApiClientConfigFromEnv();
|
|
293
|
+
this.#client = new PlatformApiClient(config);
|
|
294
|
+
this.#endpointHost = new URL(config.baseUrl).host;
|
|
295
|
+
this.#pollingEnabled = process.env.MASTRA_PLATFORM_GITHUB_POLLING_ENABLED?.trim().toLowerCase() !== "false";
|
|
296
|
+
this.#pollingIntervalMs = optionalPositiveIntegerEnv("MASTRA_PLATFORM_GITHUB_POLLING_INTERVAL_MS");
|
|
297
|
+
this.#reconcileEnabled = process.env.MASTRA_PLATFORM_GITHUB_RECONCILE_ENABLED?.trim().toLowerCase() !== "false";
|
|
298
|
+
this.#runIssueTriage = options.runIssueTriage;
|
|
299
|
+
}
|
|
300
|
+
get storage() {
|
|
301
|
+
if (!this.#storage) throw new Error("PlatformGithubIntegration source-control storage has not been initialized.");
|
|
302
|
+
return this.#storage;
|
|
303
|
+
}
|
|
304
|
+
get sourceControlStorage() {
|
|
305
|
+
return this.storage;
|
|
306
|
+
}
|
|
307
|
+
get integrationStorage() {
|
|
308
|
+
if (!this.#integrationStorage) throw new Error("PlatformGithubIntegration generic storage has not been initialized.");
|
|
309
|
+
return this.#integrationStorage;
|
|
310
|
+
}
|
|
311
|
+
/** Resolve a stored GitHub locator without scanning installations or repositories. */
|
|
312
|
+
async #resolveIntakeDispatch({ orgId, externalSource }) {
|
|
313
|
+
const target = parseGithubExternalTarget(externalSource.externalId);
|
|
314
|
+
if (!target) return null;
|
|
315
|
+
const repository = target.repository.includes("/") ? target.repository : (await this.storage.repositories.findByExternalId({
|
|
316
|
+
orgId,
|
|
317
|
+
externalId: target.repository
|
|
318
|
+
}))?.slug;
|
|
319
|
+
if (!repository) return null;
|
|
320
|
+
return {
|
|
321
|
+
connection: {
|
|
322
|
+
type: "app-installation",
|
|
323
|
+
installationId: 1
|
|
324
|
+
},
|
|
325
|
+
sourceId: repository,
|
|
326
|
+
issueId: target.issueId
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
initialize({ storage }) {
|
|
330
|
+
this.#integrationStorage = storage;
|
|
331
|
+
logPlatformInfo("Platform GitHub integration initialized", {
|
|
332
|
+
endpointHost: this.#endpointHost,
|
|
333
|
+
pollingEnabled: this.#pollingEnabled,
|
|
334
|
+
pollingIntervalMs: this.#pollingIntervalMs,
|
|
335
|
+
reconcileEnabled: this.#reconcileEnabled
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
routes(ctx) {
|
|
339
|
+
const ingestFactoryEvent = attachGithubRules(this, ctx);
|
|
340
|
+
return [
|
|
341
|
+
this.#statusRoute(ctx),
|
|
342
|
+
this.#connectRoute(ctx),
|
|
343
|
+
this.#connectUserRoute(ctx),
|
|
344
|
+
...buildGithubRoutes({
|
|
345
|
+
auth: ctx.auth,
|
|
346
|
+
fleet: ctx.fleet,
|
|
347
|
+
storage: ctx.factoryStorage,
|
|
348
|
+
github: this,
|
|
349
|
+
stateSigner: ctx.stateSigner,
|
|
350
|
+
baseUrl: ctx.baseUrl,
|
|
351
|
+
controller: ctx.controller,
|
|
352
|
+
projects: ctx.storage.projects,
|
|
353
|
+
emitAudit: ctx.hooks?.emitAudit,
|
|
354
|
+
ingestFactoryEvent,
|
|
355
|
+
runIssueTriage: this.#runIssueTriage ?? (ctx.controller ? (input) => runGithubIssueTriage({
|
|
356
|
+
controller: ctx.controller,
|
|
357
|
+
input
|
|
358
|
+
}) : void 0)
|
|
359
|
+
}).filter((route) => route.path !== "/web/github/status" && route.path !== "/web/github/webhook" && !route.path.startsWith("/auth/github/"))
|
|
360
|
+
];
|
|
361
|
+
}
|
|
362
|
+
#statusRoute(ctx) {
|
|
363
|
+
return registerApiRoute("/web/github/status", {
|
|
364
|
+
method: "GET",
|
|
365
|
+
requiresAuth: false,
|
|
366
|
+
handler: async (c) => {
|
|
367
|
+
await ctx.auth.ensureUser(loose(c));
|
|
368
|
+
const tenant = ctx.auth.tenant(loose(c));
|
|
369
|
+
if (!tenant) return c.json({
|
|
370
|
+
error: "unauthorized",
|
|
371
|
+
reason: "auth_required"
|
|
372
|
+
}, 401);
|
|
373
|
+
if (!tenant.orgId) return c.json({
|
|
374
|
+
enabled: true,
|
|
375
|
+
sandboxEnabled: ctx.fleet.enabled,
|
|
376
|
+
organizationRequired: true,
|
|
377
|
+
connected: false,
|
|
378
|
+
installations: [],
|
|
379
|
+
userConnected: false,
|
|
380
|
+
userGithubUsername: null,
|
|
381
|
+
reason: "organization_required",
|
|
382
|
+
diagnostics: this.diagnostics()
|
|
383
|
+
});
|
|
384
|
+
const [installations, userConnection] = await Promise.all([this.#syncInstallations(tenant.orgId, tenant.userId), this.#fetchUserConnection(tenant.userId)]);
|
|
385
|
+
return c.json({
|
|
386
|
+
enabled: true,
|
|
387
|
+
sandboxEnabled: ctx.fleet.enabled,
|
|
388
|
+
connected: installations.length > 0,
|
|
389
|
+
installations: installations.map((installation) => ({
|
|
390
|
+
installationId: Number(installation.externalId),
|
|
391
|
+
accountLogin: installation.accountName,
|
|
392
|
+
accountType: installation.accountType
|
|
393
|
+
})),
|
|
394
|
+
userConnected: userConnection.connected,
|
|
395
|
+
userGithubUsername: userConnection.githubUsername,
|
|
396
|
+
reason: installations.length > 0 ? "ready" : "not_connected",
|
|
397
|
+
diagnostics: this.diagnostics()
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
#connectRoute(ctx) {
|
|
403
|
+
return registerApiRoute("/auth/github/connect", {
|
|
404
|
+
method: "GET",
|
|
405
|
+
requiresAuth: false,
|
|
406
|
+
handler: async (c) => {
|
|
407
|
+
await ctx.auth.ensureUser(loose(c));
|
|
408
|
+
const tenant = ctx.auth.tenant(loose(c));
|
|
409
|
+
if (!tenant?.orgId) return c.json({ error: "unauthorized" }, 401);
|
|
410
|
+
const redirectTo = c.req.query("redirectTo") || c.req.query("return_to") || "/";
|
|
411
|
+
const originator = routeBaseUrl(ctx, c.req.url);
|
|
412
|
+
logPlatformInfo("Starting Platform GitHub connect flow", {
|
|
413
|
+
orgId: tenant.orgId,
|
|
414
|
+
redirectTo,
|
|
415
|
+
originator
|
|
416
|
+
});
|
|
417
|
+
const query = new URLSearchParams({
|
|
418
|
+
action: "install",
|
|
419
|
+
redirectTo,
|
|
420
|
+
originator
|
|
421
|
+
});
|
|
422
|
+
const { url } = await this.#client.request("GET", `${API_PREFIX}/github-app/install-url?${query}`);
|
|
423
|
+
return c.redirect(url);
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
#connectUserRoute(ctx) {
|
|
428
|
+
return registerApiRoute("/auth/github/connect-user", {
|
|
429
|
+
method: "GET",
|
|
430
|
+
requiresAuth: false,
|
|
431
|
+
handler: async (c) => {
|
|
432
|
+
await ctx.auth.ensureUser(loose(c));
|
|
433
|
+
const tenant = ctx.auth.tenant(loose(c));
|
|
434
|
+
if (!tenant?.orgId) return c.json({ error: "unauthorized" }, 401);
|
|
435
|
+
const redirectTo = c.req.query("redirectTo") || c.req.query("return_to") || "/";
|
|
436
|
+
const originator = routeBaseUrl(ctx, c.req.url);
|
|
437
|
+
logPlatformInfo("Starting Platform GitHub user authorization flow", {
|
|
438
|
+
orgId: tenant.orgId,
|
|
439
|
+
redirectTo,
|
|
440
|
+
originator
|
|
441
|
+
});
|
|
442
|
+
const query = new URLSearchParams({
|
|
443
|
+
userId: tenant.userId,
|
|
444
|
+
redirectTo,
|
|
445
|
+
originator
|
|
446
|
+
});
|
|
447
|
+
const { url } = await this.#client.request("GET", `${API_PREFIX}/github-app/authenticate?${query}`);
|
|
448
|
+
return c.redirect(url);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Personal GitHub connection status for the acting user. Returns
|
|
454
|
+
* not-connected when the platform predates the user-connection endpoint.
|
|
455
|
+
*/
|
|
456
|
+
async #fetchUserConnection(userId) {
|
|
457
|
+
try {
|
|
458
|
+
const connection = await this.#client.request("GET", `${API_PREFIX}/github-app/user-connection?${new URLSearchParams({ userId })}`);
|
|
459
|
+
if (!connection.connected && connection.reason) logPlatformWarn("Platform GitHub user connection verification failed", {
|
|
460
|
+
userId,
|
|
461
|
+
reason: connection.reason
|
|
462
|
+
});
|
|
463
|
+
return connection;
|
|
464
|
+
} catch {
|
|
465
|
+
return {
|
|
466
|
+
connected: false,
|
|
467
|
+
githubUsername: null
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
async #syncInstallations(orgId, userId) {
|
|
472
|
+
const usableInstallations = (await this.#client.request("GET", `${API_PREFIX}/github-app/installations`)).installations.filter((installation) => installation.usable && !installation.suspendedAt);
|
|
473
|
+
return Promise.all(usableInstallations.map((installation) => this.versionControl.registerInstallation({
|
|
474
|
+
orgId,
|
|
475
|
+
userId,
|
|
476
|
+
installation: {
|
|
477
|
+
externalId: String(installation.installationId),
|
|
478
|
+
accountName: installation.accountLogin,
|
|
479
|
+
accountType: installation.accountType
|
|
480
|
+
}
|
|
481
|
+
})));
|
|
482
|
+
}
|
|
483
|
+
workers(ctx) {
|
|
484
|
+
if (!this.#pollingEnabled && !this.#reconcileEnabled) return [];
|
|
485
|
+
if (!ctx.controller) throw new Error("Platform GitHub event polling requires the mounted Mastra Code controller.");
|
|
486
|
+
return [new PlatformGithubEventWorker({
|
|
487
|
+
client: this.#client,
|
|
488
|
+
controller: ctx.controller,
|
|
489
|
+
github: this,
|
|
490
|
+
storage: ctx.storage.generic,
|
|
491
|
+
ingestFactoryEvent: attachGithubRules(this, ctx),
|
|
492
|
+
reconcileFactoryState: this.#reconcileEnabled ? attachGithubReconciler(this, ctx, (input) => this.fetchPullRequestState(input)) : void 0,
|
|
493
|
+
pollEventsEnabled: this.#pollingEnabled,
|
|
494
|
+
intervalMs: this.#pollingIntervalMs
|
|
495
|
+
})];
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Reads live PR state through the Platform GitHub proxy for the merge
|
|
499
|
+
* reconciler. Returns undefined when the PR cannot be resolved (missing,
|
|
500
|
+
* proxy error) so a sweep never fabricates a merge.
|
|
501
|
+
*/
|
|
502
|
+
async fetchPullRequestState(input) {
|
|
503
|
+
let repository;
|
|
504
|
+
try {
|
|
505
|
+
repository = splitRepository(input.repository);
|
|
506
|
+
} catch {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
const result = await this.#client.request("GET", `${API_PREFIX}/github/repos/${encodeURIComponent(repository.owner)}/${encodeURIComponent(repository.repo)}/pulls/${input.number}`);
|
|
511
|
+
return {
|
|
512
|
+
title: result.title ?? `PR ${input.number}`,
|
|
513
|
+
url: result.html_url ?? `https://github.com/${input.repository}/pull/${input.number}`,
|
|
514
|
+
state: result.state === "closed" ? "closed" : "open",
|
|
515
|
+
merged: result.merged === true,
|
|
516
|
+
headBranch: result.head?.ref ?? "",
|
|
517
|
+
baseBranch: result.base?.ref ?? "",
|
|
518
|
+
...result.created_at ? { createdAt: result.created_at } : {},
|
|
519
|
+
...result.merged_by?.login ? { mergedBy: result.merged_by.login } : {}
|
|
520
|
+
};
|
|
521
|
+
} catch {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
sessionTools({ requestContext }) {
|
|
526
|
+
return createGithubSubscriptionTools(requestContext, this);
|
|
527
|
+
}
|
|
528
|
+
async postToolObserver({ toolContext, requestContext }) {
|
|
529
|
+
const pullRequestUrl = parseCreatedPullRequest(toolContext);
|
|
530
|
+
if (!pullRequestUrl || !requestContext) return;
|
|
531
|
+
await subscribeCurrentSessionToPullRequest(requestContext, pullRequestUrl, "auto-gh-pr-create", this);
|
|
532
|
+
}
|
|
533
|
+
diagnostics() {
|
|
534
|
+
return {
|
|
535
|
+
mode: "platform",
|
|
536
|
+
endpointHost: this.#endpointHost,
|
|
537
|
+
polling: {
|
|
538
|
+
enabled: this.#pollingEnabled,
|
|
539
|
+
...this.#pollingIntervalMs === void 0 ? {} : { intervalMs: this.#pollingIntervalMs }
|
|
540
|
+
},
|
|
541
|
+
reconcile: { enabled: this.#reconcileEnabled }
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
async getRepositoryCollaboratorPermission(_installationId, repoFullName, username, signal) {
|
|
545
|
+
let repository;
|
|
546
|
+
try {
|
|
547
|
+
repository = splitRepository(repoFullName);
|
|
548
|
+
} catch {
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
return (await this.#client.request("GET", `${API_PREFIX}/github/repos/${encodeURIComponent(repository.owner)}/${encodeURIComponent(repository.repo)}/collaborators/${encodeURIComponent(username)}/permission`, void 0, { signal })).permission;
|
|
553
|
+
} catch {
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
async listInstallationRepos(installationId) {
|
|
558
|
+
const cached = this.#installationReposCache.get(installationId);
|
|
559
|
+
if (cached && cached.expiresAt > Date.now()) return cached.repos;
|
|
560
|
+
this.#installationReposCache.delete(installationId);
|
|
561
|
+
const repos = (await this.#client.request("GET", `${API_PREFIX}/github-app/installations/${installationId}/repositories`)).repositories.map((repository) => ({
|
|
562
|
+
...repository,
|
|
563
|
+
installationId
|
|
564
|
+
}));
|
|
565
|
+
setBounded(this.#installationReposCache, installationId, {
|
|
566
|
+
repos,
|
|
567
|
+
expiresAt: Date.now() + INSTALLATION_REPOS_CACHE_TTL_MS
|
|
568
|
+
});
|
|
569
|
+
return repos;
|
|
570
|
+
}
|
|
571
|
+
async mintInstallationToken(installationId) {
|
|
572
|
+
const repositories = await this.listInstallationRepos(installationId);
|
|
573
|
+
if (repositories.length === 0 || repositories.length > 10) throw new Error("Platform GitHub token minting requires between one and ten installation repositories.");
|
|
574
|
+
return (await this.#client.request("POST", `${API_PREFIX}/github-app/installations/${installationId}/token`, {
|
|
575
|
+
repositories: repositories.map((repository) => repository.name),
|
|
576
|
+
permissions: REPOSITORY_TOKEN_PERMISSIONS
|
|
577
|
+
})).token;
|
|
578
|
+
}
|
|
579
|
+
async addIssueLabels(_installationId, sourceId, issueNumber, labels) {
|
|
580
|
+
return (await this.#client.request("POST", repositoryPath(sourceId, `issues/${issueNumber}/labels`), { labels })).labels;
|
|
581
|
+
}
|
|
582
|
+
getInstallationOctokit(_installationId) {
|
|
583
|
+
return { pulls: { get: async ({ owner, repo, pull_number }) => {
|
|
584
|
+
return { data: { base: { repo: { id: (await this.#client.request("GET", repositoryPath(`${owner}/${repo}`, `pulls/${pull_number}`))).base.repo.id } } } };
|
|
585
|
+
} } };
|
|
586
|
+
}
|
|
587
|
+
async #listIssues(connection, sourceId, page, labels) {
|
|
588
|
+
requireGithubConnection(connection);
|
|
589
|
+
const path = repositoryPath(sourceId, "issues");
|
|
590
|
+
const query = new URLSearchParams({
|
|
591
|
+
state: "open",
|
|
592
|
+
page: String(page),
|
|
593
|
+
per_page: String(PAGE_SIZE)
|
|
594
|
+
});
|
|
595
|
+
const normalizedLabels = normalizeLabels(labels);
|
|
596
|
+
if (normalizedLabels.length > 0) query.set("label", normalizedLabels.join(","));
|
|
597
|
+
const result = await this.#client.request("GET", `${path}?${query}`);
|
|
598
|
+
return {
|
|
599
|
+
issues: result.issues.map((issue) => parseIntakeIssue(sourceId, issue)),
|
|
600
|
+
nextCursor: result.issues.length === PAGE_SIZE ? String(page + 1) : null
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
async #getIssue(connection, sourceId, issueId) {
|
|
604
|
+
requireGithubConnection(connection);
|
|
605
|
+
const repository = requireSource(sourceId, "GitHub Intake requires a repository source.");
|
|
606
|
+
const issueNumber = requirePositiveId(issueId, "issue");
|
|
607
|
+
try {
|
|
608
|
+
const [issue, comments] = await Promise.all([this.#client.request("GET", repositoryPath(repository, `issues/${issueNumber}`)), this.#client.request("GET", `${repositoryPath(repository, `issues/${issueNumber}/comments`)}?per_page=100`)]);
|
|
609
|
+
return parseIntakeIssueDetail(repository, issue, comments.comments);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
if (isNotFound(error)) return null;
|
|
612
|
+
throw error;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async #updateIntakeIssue(input) {
|
|
616
|
+
requireGithubConnection(input.connection);
|
|
617
|
+
const repository = requireSource(input.sourceId, "GitHub Intake requires a repository source.");
|
|
618
|
+
const issueNumber = requirePositiveId(input.issueId, "issue");
|
|
619
|
+
if (input.state.kind === "byName") {
|
|
620
|
+
logPlatformWarn(`Platform GitHub: updateIssue byName is not supported (name=${input.state.name}); ignoring.`);
|
|
621
|
+
return null;
|
|
622
|
+
}
|
|
623
|
+
const targetState = input.state.stateType === "unstarted" || input.state.stateType === "started" ? "open" : "closed";
|
|
624
|
+
const stateReason = targetState === "closed" ? input.state.stateType === "canceled" ? "not_planned" : "completed" : null;
|
|
625
|
+
try {
|
|
626
|
+
await this.#client.request("GET", repositoryPath(repository, `pulls/${issueNumber}`));
|
|
627
|
+
logPlatformWarn(`Platform GitHub: updateIssue rejected — target ${repository}#${issueNumber} is a pull request.`);
|
|
628
|
+
return null;
|
|
629
|
+
} catch (error) {
|
|
630
|
+
if (!isNotFound(error)) throw error;
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
633
|
+
return parseIntakeIssue(repository, await this.#client.request("PATCH", repositoryPath(repository, `issues/${issueNumber}`), {
|
|
634
|
+
state: targetState,
|
|
635
|
+
...stateReason ? { state_reason: stateReason } : {}
|
|
636
|
+
}, { actingUserId: input.actingUserId }));
|
|
637
|
+
} catch (error) {
|
|
638
|
+
if (isNotFound(error)) return null;
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
async #createIssueComment(input) {
|
|
643
|
+
requireGithubConnection(input.connection);
|
|
644
|
+
const repository = requireSource(input.sourceId, "GitHub Intake requires a repository source.");
|
|
645
|
+
const issueNumber = requirePositiveId(input.issueId, "issue");
|
|
646
|
+
try {
|
|
647
|
+
const comment = await this.#client.request("POST", repositoryPath(repository, `issues/${issueNumber}/comments`), { body: input.body }, { actingUserId: input.actingUserId });
|
|
648
|
+
return {
|
|
649
|
+
id: String(comment.id),
|
|
650
|
+
url: comment.htmlUrl
|
|
651
|
+
};
|
|
652
|
+
} catch (error) {
|
|
653
|
+
if (isNotFound(error)) return null;
|
|
654
|
+
throw error;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
async #listPullRequests(input) {
|
|
658
|
+
requireGithubConnection(input.connection);
|
|
659
|
+
const page = parsePositiveCursor(input.cursor);
|
|
660
|
+
const query = new URLSearchParams({
|
|
661
|
+
state: input.state ?? "open",
|
|
662
|
+
page: String(page),
|
|
663
|
+
per_page: String(PAGE_SIZE)
|
|
664
|
+
});
|
|
665
|
+
const result = await this.#client.request("GET", `${repositoryPath(input.sourceId, "pulls")}?${query}`);
|
|
666
|
+
return {
|
|
667
|
+
pullRequests: result.pullRequests.filter((pullRequest) => input.includeDrafts !== false || !pullRequest.draft).map(parsePullRequest),
|
|
668
|
+
nextCursor: result.pullRequests.length === PAGE_SIZE ? String(page + 1) : null
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
async #getPullRequest(input) {
|
|
672
|
+
try {
|
|
673
|
+
return parsePullRequest(await this.#client.request("GET", pullRequestPath(input, input.pullRequestId)));
|
|
674
|
+
} catch (error) {
|
|
675
|
+
if (isNotFound(error)) return null;
|
|
676
|
+
throw error;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async #createPullRequest(input) {
|
|
680
|
+
requireGithubConnection(input.connection);
|
|
681
|
+
return parsePullRequest(await this.#client.request("POST", repositoryPath(input.sourceId, "pulls"), {
|
|
682
|
+
head: input.headBranch,
|
|
683
|
+
base: input.baseBranch,
|
|
684
|
+
title: input.title,
|
|
685
|
+
body: input.body,
|
|
686
|
+
draft: input.draft
|
|
687
|
+
}, { actingUserId: input.actingUserId }));
|
|
688
|
+
}
|
|
689
|
+
async #updatePullRequest(input) {
|
|
690
|
+
return parsePullRequest(await this.#client.request("PATCH", pullRequestPath(input, input.pullRequestId), {
|
|
691
|
+
title: input.title,
|
|
692
|
+
body: input.body === null ? "" : input.body,
|
|
693
|
+
base: input.baseBranch,
|
|
694
|
+
state: input.state
|
|
695
|
+
}, { actingUserId: input.actingUserId }));
|
|
696
|
+
}
|
|
697
|
+
#mergePullRequest(input) {
|
|
698
|
+
return this.#client.request("PUT", `${pullRequestPath(input, input.pullRequestId)}/merge`, {
|
|
699
|
+
commitTitle: input.commitTitle,
|
|
700
|
+
commitMessage: input.commitMessage,
|
|
701
|
+
method: input.method
|
|
702
|
+
}, { actingUserId: input.actingUserId });
|
|
703
|
+
}
|
|
704
|
+
async #listComments(input) {
|
|
705
|
+
const page = parsePositiveCursor(input.cursor);
|
|
706
|
+
const result = await this.#client.request("GET", `${repositoryPath(input.sourceId, `issues/${requirePositiveId(input.pullRequestId, "pull request")}/comments`)}?page=${page}&per_page=${PAGE_SIZE}`);
|
|
707
|
+
return {
|
|
708
|
+
comments: result.comments.map(parseComment),
|
|
709
|
+
nextCursor: result.comments.length === PAGE_SIZE ? String(page + 1) : null
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
async #createComment(input) {
|
|
713
|
+
return parseComment(await this.#client.request("POST", repositoryPath(input.sourceId, `issues/${requirePositiveId(input.pullRequestId, "pull request")}/comments`), { body: input.body }, { actingUserId: input.actingUserId }));
|
|
714
|
+
}
|
|
715
|
+
async #updateComment(input) {
|
|
716
|
+
requireGithubConnection(input.connection);
|
|
717
|
+
return parseComment(await this.#client.request("PATCH", repositoryPath(input.sourceId, `issues/comments/${requirePositiveId(input.commentId, "comment")}`), { body: input.body }, { actingUserId: input.actingUserId }));
|
|
718
|
+
}
|
|
719
|
+
async #deleteComment(input) {
|
|
720
|
+
requireGithubConnection(input.connection);
|
|
721
|
+
await this.#client.request("DELETE", repositoryPath(input.sourceId, `issues/comments/${requirePositiveId(input.commentId, "comment")}`), void 0, { actingUserId: input.actingUserId });
|
|
722
|
+
}
|
|
723
|
+
async #listReviews(input) {
|
|
724
|
+
const page = parsePositiveCursor(input.cursor);
|
|
725
|
+
const result = await this.#client.request("GET", `${pullRequestPath(input, input.pullRequestId)}/reviews?page=${page}&per_page=${PAGE_SIZE}`);
|
|
726
|
+
return {
|
|
727
|
+
reviews: result.reviews.map(parseReview),
|
|
728
|
+
nextCursor: result.reviews.length === PAGE_SIZE ? String(page + 1) : null
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
async #getReview(input) {
|
|
732
|
+
try {
|
|
733
|
+
return parseReview(await this.#client.request("GET", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`));
|
|
734
|
+
} catch (error) {
|
|
735
|
+
if (isNotFound(error)) return null;
|
|
736
|
+
throw error;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
async #createReview(input) {
|
|
740
|
+
return parseReview(await this.#client.request("POST", `${pullRequestPath(input, input.pullRequestId)}/reviews`, {
|
|
741
|
+
body: input.body,
|
|
742
|
+
commitId: input.commitId,
|
|
743
|
+
event: input.event ? reviewEvent(input.event) : void 0
|
|
744
|
+
}, { actingUserId: input.actingUserId }));
|
|
745
|
+
}
|
|
746
|
+
async #updateReview(input) {
|
|
747
|
+
return parseReview(await this.#client.request("PUT", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`, { body: input.body }, { actingUserId: input.actingUserId }));
|
|
748
|
+
}
|
|
749
|
+
async #submitReview(input) {
|
|
750
|
+
return parseReview(await this.#client.request("POST", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}/events`, {
|
|
751
|
+
body: input.body,
|
|
752
|
+
event: reviewEvent(input.event)
|
|
753
|
+
}, { actingUserId: input.actingUserId }));
|
|
754
|
+
}
|
|
755
|
+
async #dismissReview(input) {
|
|
756
|
+
return parseReview(await this.#client.request("PUT", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}/dismissals`, { message: input.message }, { actingUserId: input.actingUserId }));
|
|
757
|
+
}
|
|
758
|
+
async #deletePendingReview(input) {
|
|
759
|
+
await this.#client.request("DELETE", `${pullRequestPath(input, input.pullRequestId)}/reviews/${requirePositiveId(input.reviewId, "review")}`, void 0, { actingUserId: input.actingUserId });
|
|
760
|
+
}
|
|
761
|
+
async #listReviewComments(input) {
|
|
762
|
+
const page = parsePositiveCursor(input.cursor);
|
|
763
|
+
const result = await this.#client.request("GET", `${pullRequestPath(input, input.pullRequestId)}/comments?page=${page}&per_page=${PAGE_SIZE}`);
|
|
764
|
+
return {
|
|
765
|
+
comments: result.comments.map(parseReviewComment),
|
|
766
|
+
nextCursor: result.comments.length === PAGE_SIZE ? String(page + 1) : null
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
async #createReviewComment(input) {
|
|
770
|
+
let body;
|
|
771
|
+
if (input.replyToId !== void 0) body = {
|
|
772
|
+
body: input.body,
|
|
773
|
+
replyToId: requirePositiveId(input.replyToId, "review comment")
|
|
774
|
+
};
|
|
775
|
+
else {
|
|
776
|
+
if (!input.commitId || !input.path || input.line === void 0 || !input.side) throw new Error("A review comment requires commitId, path, line, and side unless it is a reply.");
|
|
777
|
+
body = {
|
|
778
|
+
body: input.body,
|
|
779
|
+
commitId: input.commitId,
|
|
780
|
+
path: input.path,
|
|
781
|
+
line: input.line,
|
|
782
|
+
side: input.side.toUpperCase(),
|
|
783
|
+
startLine: input.startLine,
|
|
784
|
+
startSide: input.startSide?.toUpperCase()
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
return parseReviewComment(await this.#client.request("POST", `${pullRequestPath(input, input.pullRequestId)}/comments`, body, { actingUserId: input.actingUserId }));
|
|
788
|
+
}
|
|
789
|
+
async #updateReviewComment(input) {
|
|
790
|
+
requireGithubConnection(input.connection);
|
|
791
|
+
return parseReviewComment(await this.#client.request("PATCH", repositoryPath(input.sourceId, `pulls/comments/${requirePositiveId(input.commentId, "review comment")}`), { body: input.body }, { actingUserId: input.actingUserId }));
|
|
792
|
+
}
|
|
793
|
+
async #deleteReviewComment(input) {
|
|
794
|
+
requireGithubConnection(input.connection);
|
|
795
|
+
await this.#client.request("DELETE", repositoryPath(input.sourceId, `pulls/comments/${requirePositiveId(input.commentId, "review comment")}`), void 0, { actingUserId: input.actingUserId });
|
|
796
|
+
}
|
|
797
|
+
#requestedReviewers(method, input) {
|
|
798
|
+
requireGithubConnection(input.connection);
|
|
799
|
+
return this.#client.request(method, `${pullRequestPath(input, input.pullRequestId)}/requested-reviewers`, method === "GET" ? void 0 : {
|
|
800
|
+
users: input.users,
|
|
801
|
+
teams: input.teams
|
|
802
|
+
}, method === "GET" ? void 0 : { actingUserId: input.actingUserId });
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
function repositoryPath(sourceId, suffix) {
|
|
806
|
+
const { owner, repo } = splitRepository(sourceId);
|
|
807
|
+
return `${API_PREFIX}/github/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${suffix}`;
|
|
808
|
+
}
|
|
809
|
+
function pullRequestPath(input, pullRequestId) {
|
|
810
|
+
requireGithubConnection(input.connection);
|
|
811
|
+
return repositoryPath(input.sourceId, `pulls/${requirePositiveId(pullRequestId, "pull request")}`);
|
|
812
|
+
}
|
|
813
|
+
function splitRepository(sourceId) {
|
|
814
|
+
const slash = sourceId.indexOf("/");
|
|
815
|
+
if (slash <= 0 || slash === sourceId.length - 1) throw new Error("GitHub capabilities require an owner/repository source.");
|
|
816
|
+
return {
|
|
817
|
+
owner: sourceId.slice(0, slash),
|
|
818
|
+
repo: sourceId.slice(slash + 1)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
function parseIntakeIssue(sourceId, issue) {
|
|
822
|
+
return {
|
|
823
|
+
id: String(issue.number),
|
|
824
|
+
identifier: `#${issue.number}`,
|
|
825
|
+
title: issue.title,
|
|
826
|
+
url: issue.htmlUrl,
|
|
827
|
+
author: issue.user?.login ?? null,
|
|
828
|
+
state: issue.state,
|
|
829
|
+
stateType: issue.state,
|
|
830
|
+
priority: null,
|
|
831
|
+
assignee: issue.assignees[0] ?? null,
|
|
832
|
+
source: sourceId,
|
|
833
|
+
labels: issue.labels,
|
|
834
|
+
commentCount: issue.commentCount,
|
|
835
|
+
createdAt: issue.createdAt,
|
|
836
|
+
updatedAt: issue.updatedAt
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function parseIntakeIssueDetail(sourceId, issue, comments) {
|
|
840
|
+
return {
|
|
841
|
+
...parseIntakeIssue(sourceId, issue),
|
|
842
|
+
description: issue.body?.trim() ? issue.body : null,
|
|
843
|
+
comments: comments.map((comment) => ({
|
|
844
|
+
author: comment.user?.login ?? null,
|
|
845
|
+
body: comment.body,
|
|
846
|
+
createdAt: comment.createdAt
|
|
847
|
+
}))
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function parsePullRequest(pullRequest) {
|
|
851
|
+
return {
|
|
852
|
+
id: String(pullRequest.number),
|
|
853
|
+
title: pullRequest.title,
|
|
854
|
+
url: pullRequest.htmlUrl,
|
|
855
|
+
author: pullRequest.user?.login ?? null,
|
|
856
|
+
body: pullRequest.body?.trim() ? pullRequest.body : null,
|
|
857
|
+
state: pullRequest.state,
|
|
858
|
+
draft: pullRequest.draft,
|
|
859
|
+
merged: pullRequest.merged,
|
|
860
|
+
mergeable: pullRequest.mergeable,
|
|
861
|
+
baseBranch: pullRequest.base.ref,
|
|
862
|
+
headBranch: pullRequest.head.ref,
|
|
863
|
+
headSha: pullRequest.head.sha,
|
|
864
|
+
createdAt: pullRequest.createdAt,
|
|
865
|
+
updatedAt: pullRequest.updatedAt
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
function parseComment(comment) {
|
|
869
|
+
return {
|
|
870
|
+
id: String(comment.id),
|
|
871
|
+
url: comment.htmlUrl,
|
|
872
|
+
author: comment.user?.login ?? null,
|
|
873
|
+
body: comment.body,
|
|
874
|
+
createdAt: comment.createdAt,
|
|
875
|
+
updatedAt: comment.updatedAt
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
function parseReview(review) {
|
|
879
|
+
return {
|
|
880
|
+
id: String(review.id),
|
|
881
|
+
url: review.htmlUrl,
|
|
882
|
+
author: review.user?.login ?? null,
|
|
883
|
+
body: review.body?.trim() ? review.body : null,
|
|
884
|
+
state: {
|
|
885
|
+
PENDING: "pending",
|
|
886
|
+
COMMENTED: "commented",
|
|
887
|
+
APPROVED: "approved",
|
|
888
|
+
CHANGES_REQUESTED: "changes-requested",
|
|
889
|
+
DISMISSED: "dismissed"
|
|
890
|
+
}[review.state],
|
|
891
|
+
commitId: review.commitId,
|
|
892
|
+
submittedAt: review.submittedAt
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
function parseReviewComment(comment) {
|
|
896
|
+
return {
|
|
897
|
+
...parseComment(comment),
|
|
898
|
+
path: comment.path,
|
|
899
|
+
line: comment.line,
|
|
900
|
+
side: comment.side?.toLowerCase(),
|
|
901
|
+
commitId: comment.commitId,
|
|
902
|
+
replyToId: comment.replyToId === null ? null : String(comment.replyToId)
|
|
903
|
+
};
|
|
904
|
+
}
|
|
905
|
+
function requireGithubConnection(connection) {
|
|
906
|
+
if (connection.type !== "app-installation" && connection.type !== "oauth") throw new Error("GitHub capabilities require a GitHub connection.");
|
|
907
|
+
}
|
|
908
|
+
function requireSingleSource(sourceIds, message) {
|
|
909
|
+
if (sourceIds.length !== 1) throw new Error(message);
|
|
910
|
+
return sourceIds[0];
|
|
911
|
+
}
|
|
912
|
+
function requireSource(sourceId, message) {
|
|
913
|
+
if (!sourceId) throw new Error(message);
|
|
914
|
+
return sourceId;
|
|
915
|
+
}
|
|
916
|
+
function normalizeLabels(labels) {
|
|
917
|
+
return [...new Set((labels ?? []).map((label) => label.trim()).filter(Boolean))];
|
|
918
|
+
}
|
|
919
|
+
function parsePositiveCursor(cursor) {
|
|
920
|
+
if (cursor === void 0) return 1;
|
|
921
|
+
const parsed = parsePositiveInteger(cursor);
|
|
922
|
+
if (parsed === null) throw new Error("GitHub cursor must be a positive page number.");
|
|
923
|
+
return parsed;
|
|
924
|
+
}
|
|
925
|
+
function parsePositiveInteger(value) {
|
|
926
|
+
if (!/^\d+$/.test(value)) return null;
|
|
927
|
+
const parsed = Number(value);
|
|
928
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
|
|
929
|
+
}
|
|
930
|
+
function parseGithubExternalTarget(externalId) {
|
|
931
|
+
const match = externalId.match(/^(.+\/.+):(\d+)$/) ?? externalId.match(/^github:(\d+):(?:issue|pull-request):(\d+)$/) ?? externalId.match(/^(\d+):(\d+)$/);
|
|
932
|
+
if (!match?.[1] || !match[2] || parsePositiveInteger(match[2]) === null) return null;
|
|
933
|
+
return {
|
|
934
|
+
repository: match[1],
|
|
935
|
+
issueId: match[2]
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
function optionalPositiveIntegerEnv(name) {
|
|
939
|
+
const value = process.env[name]?.trim();
|
|
940
|
+
if (!value) return void 0;
|
|
941
|
+
const parsed = parsePositiveInteger(value);
|
|
942
|
+
if (parsed === null) throw new Error(`${name} must be a positive integer.`);
|
|
943
|
+
return parsed;
|
|
944
|
+
}
|
|
945
|
+
function requirePositiveId(value, resource) {
|
|
946
|
+
const parsed = parsePositiveInteger(value);
|
|
947
|
+
if (parsed === null) throw new Error(`GitHub ${resource} id must be a positive integer.`);
|
|
948
|
+
return parsed;
|
|
949
|
+
}
|
|
950
|
+
function reviewEvent(event) {
|
|
951
|
+
if (event === "approve") return "APPROVE";
|
|
952
|
+
if (event === "request-changes") return "REQUEST_CHANGES";
|
|
953
|
+
return "COMMENT";
|
|
954
|
+
}
|
|
955
|
+
function isNotFound(error) {
|
|
956
|
+
return error instanceof PlatformApiError && error.status === 404;
|
|
957
|
+
}
|
|
958
|
+
//#endregion
|
|
959
|
+
export { PlatformGithubIntegration };
|
|
960
|
+
|
|
961
|
+
//# sourceMappingURL=integration.js.map
|