@mastra/factory 0.0.0-esbuild-bundle-worker-20260807173433
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 +1008 -0
- package/LICENSE.md +30 -0
- package/README.md +27 -0
- package/dist/auth.d.ts +183 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +507 -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 +141 -0
- package/dist/capabilities/intake.d.ts.map +1 -0
- package/dist/capabilities/intake.js +1 -0
- package/dist/capabilities/version-control.d.ts +247 -0
- package/dist/capabilities/version-control.d.ts.map +1 -0
- package/dist/capabilities/version-control.js +1 -0
- package/dist/factory.d.ts +159 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +491 -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 +254 -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/integration.d.ts +220 -0
- package/dist/integrations/github/integration.d.ts.map +1 -0
- package/dist/integrations/github/integration.js +1162 -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/reconcile-worker.d.ts +41 -0
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -0
- package/dist/integrations/github/reconcile-worker.js +125 -0
- package/dist/integrations/github/reconcile-worker.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 +1335 -0
- package/dist/integrations/github/routes.js.map +1 -0
- package/dist/integrations/github/rules.d.ts +105 -0
- package/dist/integrations/github/rules.d.ts.map +1 -0
- package/dist/integrations/github/rules.js +551 -0
- package/dist/integrations/github/rules.js.map +1 -0
- package/dist/integrations/github/sandbox-release.d.ts +53 -0
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -0
- package/dist/integrations/github/sandbox-release.js +115 -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 +107 -0
- package/dist/integrations/github/webhook.d.ts.map +1 -0
- package/dist/integrations/github/webhook.js +371 -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 +235 -0
- package/dist/integrations/linear/integration.d.ts.map +1 -0
- package/dist/integrations/linear/integration.js +676 -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 +240 -0
- package/dist/integrations/linear/routes.js.map +1 -0
- package/dist/integrations/linear/rules.d.ts +44 -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 +44 -0
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
- package/dist/integrations/platform/github/event-worker.js +349 -0
- package/dist/integrations/platform/github/event-worker.js.map +1 -0
- package/dist/integrations/platform/github/integration.d.ts +64 -0
- package/dist/integrations/platform/github/integration.d.ts.map +1 -0
- package/dist/integrations/platform/github/integration.js +1006 -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/slack/connect-route.d.ts +46 -0
- package/dist/integrations/slack/connect-route.d.ts.map +1 -0
- package/dist/integrations/slack/connect-route.js +186 -0
- package/dist/integrations/slack/connect-route.js.map +1 -0
- package/dist/integrations/slack/integration.d.ts +62 -0
- package/dist/integrations/slack/integration.d.ts.map +1 -0
- package/dist/integrations/slack/integration.js +68 -0
- package/dist/integrations/slack/integration.js.map +1 -0
- package/dist/integrations/slack/slack.d.ts +200 -0
- package/dist/integrations/slack/slack.d.ts.map +1 -0
- package/dist/integrations/slack/slack.js +411 -0
- package/dist/integrations/slack/slack.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 +107 -0
- package/dist/routes/surface.d.ts.map +1 -0
- package/dist/routes/surface.js +297 -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 +322 -0
- package/dist/rules/defaults.js.map +1 -0
- package/dist/rules/dispatcher.d.ts +43 -0
- package/dist/rules/dispatcher.d.ts.map +1 -0
- package/dist/rules/dispatcher.js +547 -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 +175 -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 +59 -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 +261 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +43 -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/session/factory-session.d.ts +116 -0
- package/dist/session/factory-session.d.ts.map +1 -0
- package/dist/session/factory-session.js +162 -0
- package/dist/session/factory-session.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 +76 -0
- package/dist/storage/domains/audit/domain.d.ts.map +1 -0
- package/dist/storage/domains/audit/domain.js +269 -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 +360 -0
- package/dist/storage/domains/source-control/base.d.ts.map +1 -0
- package/dist/storage/domains/source-control/base.js +914 -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 +380 -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 +1320 -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 +287 -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 +157 -0
- package/factory-skills/factory-triage/SKILL.md +99 -0
- package/package.json +83 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import { changeRequestTargetKey } from "./subscriptions.js";
|
|
2
|
+
import { resolveFactoryGithubRule } from "../../rules/resolve.js";
|
|
3
|
+
import { validateFactoryRuleDecisions } from "../../rules/validation.js";
|
|
4
|
+
//#region src/integrations/github/rules.ts
|
|
5
|
+
const TRUSTED_PERMISSIONS = /* @__PURE__ */ new Set(["write", "admin"]);
|
|
6
|
+
const RULE_TIMEOUT_MS = 5e3;
|
|
7
|
+
const FACTORY_TRIAGE_COMMENT_MARKER = "<!-- mastra-factory-triage -->";
|
|
8
|
+
async function withRuleTimeout(promise) {
|
|
9
|
+
let timeout;
|
|
10
|
+
try {
|
|
11
|
+
return await Promise.race([promise, new Promise((_, reject) => {
|
|
12
|
+
timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("FACTORY_RULE_TIMEOUT")), RULE_TIMEOUT_MS);
|
|
13
|
+
})]);
|
|
14
|
+
} finally {
|
|
15
|
+
if (timeout) clearTimeout(timeout);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function object(value) {
|
|
19
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
20
|
+
}
|
|
21
|
+
function string(value) {
|
|
22
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
23
|
+
}
|
|
24
|
+
function number(value) {
|
|
25
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
26
|
+
}
|
|
27
|
+
function boolean(value) {
|
|
28
|
+
return typeof value === "boolean" ? value : void 0;
|
|
29
|
+
}
|
|
30
|
+
function actorLogins(value) {
|
|
31
|
+
if (!Array.isArray(value)) return [];
|
|
32
|
+
return value.flatMap((actor) => {
|
|
33
|
+
const login = string(object(actor)?.login);
|
|
34
|
+
return login ? [login] : [];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function eventName(parsed) {
|
|
38
|
+
const action = string(parsed.payload.action);
|
|
39
|
+
if (parsed.event === "issues" && action === "opened") return "issueOpened";
|
|
40
|
+
if (parsed.event === "issues" && action === "edited") {
|
|
41
|
+
const changes = object(parsed.payload.changes);
|
|
42
|
+
return object(changes?.title) || object(changes?.body) ? "issueEdited" : void 0;
|
|
43
|
+
}
|
|
44
|
+
if (parsed.event === "issues" && action === "closed") return "issueClosed";
|
|
45
|
+
if (parsed.event === "issue_comment") {
|
|
46
|
+
if (object(object(parsed.payload.issue)?.pull_request)) return void 0;
|
|
47
|
+
if (action === "created") return "issueCommentCreated";
|
|
48
|
+
if (action === "edited") return "issueCommentEdited";
|
|
49
|
+
if (action === "deleted") return "issueCommentDeleted";
|
|
50
|
+
}
|
|
51
|
+
if (parsed.event === "pull_request" && action === "opened") return "pullRequestOpened";
|
|
52
|
+
if (parsed.event === "pull_request" && action === "synchronize") return "pullRequestUpdated";
|
|
53
|
+
if (parsed.event === "pull_request" && action === "closed") return boolean(object(parsed.payload.pull_request)?.merged) ? "pullRequestMerged" : "pullRequestClosed";
|
|
54
|
+
if (parsed.event === "pull_request" && action === "review_requested") return "pullRequestReviewRequested";
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Canonical source keys (`github-issue:N`, `github-pr:N`) do not identify a
|
|
58
|
+
* repository, so a project linked to several repositories could bind repo A's
|
|
59
|
+
* event to repo B's same-numbered card. The card's intake-stamped URL is
|
|
60
|
+
* authoritative; the intake-stamped `githubRepositoryId` covers URL-less
|
|
61
|
+
* cards. A card with neither signal cannot be attributed by number alone.
|
|
62
|
+
*/
|
|
63
|
+
function cardBelongsToRepository(item, repositoryId, repositoryFullName) {
|
|
64
|
+
const url = item.externalSource?.url;
|
|
65
|
+
if (url) {
|
|
66
|
+
const match = /^https?:\/\/[^/]+\/(.+)\/(?:issues|pull)\/\d+(?:[/?#]|$)/.exec(url);
|
|
67
|
+
if (match && match[1] === repositoryFullName) return true;
|
|
68
|
+
}
|
|
69
|
+
return item.metadata?.githubRepositoryId === repositoryId;
|
|
70
|
+
}
|
|
71
|
+
function canonicalSourceKey(kind, itemNumber) {
|
|
72
|
+
return kind === "issue" ? `github-issue:${itemNumber}` : `github-pr:${itemNumber}`;
|
|
73
|
+
}
|
|
74
|
+
function legacySourceKey(repositoryId, kind, itemNumber) {
|
|
75
|
+
return `github:${repositoryId}:${kind}:${itemNumber}`;
|
|
76
|
+
}
|
|
77
|
+
function provenanceTarget(repositoryId, pullRequestNumber) {
|
|
78
|
+
return `factory-pr-provenance:${repositoryId}:${pullRequestNumber}`;
|
|
79
|
+
}
|
|
80
|
+
function workItemSource(item) {
|
|
81
|
+
if (!item.externalSource) return "manual";
|
|
82
|
+
return item.externalSource.type === "pull-request" ? "github-pr" : "github-issue";
|
|
83
|
+
}
|
|
84
|
+
function workItemSourceKey(item) {
|
|
85
|
+
return item.externalSource?.externalId ?? null;
|
|
86
|
+
}
|
|
87
|
+
async function githubActor(github, input) {
|
|
88
|
+
let trusted = false;
|
|
89
|
+
try {
|
|
90
|
+
const permission = await github.getRepositoryCollaboratorPermission(input.installationId, input.repository, input.login);
|
|
91
|
+
trusted = permission !== void 0 && TRUSTED_PERMISSIONS.has(permission);
|
|
92
|
+
} catch {
|
|
93
|
+
trusted = false;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
type: "github",
|
|
97
|
+
login: input.login,
|
|
98
|
+
trusted,
|
|
99
|
+
factoryAuthored: input.factoryAuthored
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function pullRequestProvenance(data) {
|
|
103
|
+
if (!data || data.kind !== "factory-pr-provenance" || typeof data.workItemId !== "string") return null;
|
|
104
|
+
return {
|
|
105
|
+
kind: "factory-pr-provenance",
|
|
106
|
+
workItemId: data.workItemId
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
var GithubRules = class {
|
|
110
|
+
options;
|
|
111
|
+
constructor(options) {
|
|
112
|
+
this.options = options;
|
|
113
|
+
}
|
|
114
|
+
async ingest(parsed) {
|
|
115
|
+
const event = eventName(parsed);
|
|
116
|
+
const repository = object(parsed.payload.repository);
|
|
117
|
+
const installationId = number(object(parsed.payload.installation)?.id);
|
|
118
|
+
const repositoryId = number(repository?.id);
|
|
119
|
+
const repositoryName = string(repository?.full_name);
|
|
120
|
+
const login = string(object(parsed.payload.sender)?.login);
|
|
121
|
+
if (!event || !installationId || !repositoryId || !repositoryName || !login) return { status: "ignored" };
|
|
122
|
+
const projects = await this.options.sourceControl.projectRepositories.listByExternalRepository({
|
|
123
|
+
installationExternalId: String(installationId),
|
|
124
|
+
repositoryExternalId: String(repositoryId)
|
|
125
|
+
});
|
|
126
|
+
if (projects.length === 0) return { status: "ignored" };
|
|
127
|
+
const results = [];
|
|
128
|
+
for (const project of projects) results.push(await this.#ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, project));
|
|
129
|
+
if (results.some((result) => result.status === "committed")) return { status: "committed" };
|
|
130
|
+
if (results.some((result) => result.status === "replayed")) return { status: "replayed" };
|
|
131
|
+
return results[0] ?? { status: "ignored" };
|
|
132
|
+
}
|
|
133
|
+
async #ingestProject(parsed, event, installationId, repositoryId, repositoryName, login, project) {
|
|
134
|
+
const factoryProject = await this.options.projects.get({
|
|
135
|
+
orgId: project.orgId,
|
|
136
|
+
id: project.factoryProjectId
|
|
137
|
+
});
|
|
138
|
+
if (!factoryProject) return { status: "missing" };
|
|
139
|
+
const issue = object(parsed.payload.issue);
|
|
140
|
+
const issueComment = object(parsed.payload.comment);
|
|
141
|
+
const changes = object(parsed.payload.changes);
|
|
142
|
+
const pullRequest = object(parsed.payload.pull_request);
|
|
143
|
+
const issueNumber = number(issue?.number);
|
|
144
|
+
const pullRequestNumber = number(pullRequest?.number);
|
|
145
|
+
const provenance = pullRequestNumber ? pullRequestProvenance((await this.options.integrationStorage.subscriptions.listByTarget(provenanceTarget(repositoryId, pullRequestNumber), { status: "active" })).find((subscription) => subscription.orgId === project.orgId)?.data) : null;
|
|
146
|
+
const reviewRequested = event === "pullRequestReviewRequested";
|
|
147
|
+
const requestedReviewer = string(object(parsed.payload.requested_reviewer)?.login);
|
|
148
|
+
const relatedItem = await this.#relatedItem(project.orgId, project.factoryProjectId, repositoryId, repositoryName, issueNumber, pullRequestNumber, string(object(pullRequest?.head)?.ref), reviewRequested ? null : provenance);
|
|
149
|
+
const actor = await githubActor(this.options.github, {
|
|
150
|
+
installationId,
|
|
151
|
+
repository: repositoryName,
|
|
152
|
+
login,
|
|
153
|
+
factoryAuthored: !reviewRequested && provenance !== null || login === `${this.options.github.slug}[bot]`
|
|
154
|
+
});
|
|
155
|
+
if (actor.type === "github" && actor.factoryAuthored && (event === "issueCommentCreated" || event === "issueCommentEdited") && string(issueComment?.body)?.includes(FACTORY_TRIAGE_COMMENT_MARKER)) return { status: "ignored" };
|
|
156
|
+
const context = {
|
|
157
|
+
tenant: {
|
|
158
|
+
orgId: project.orgId,
|
|
159
|
+
projectId: project.factoryProjectId
|
|
160
|
+
},
|
|
161
|
+
actor,
|
|
162
|
+
ingress: {
|
|
163
|
+
type: "github",
|
|
164
|
+
id: `${installationId}:${parsed.deliveryId}`
|
|
165
|
+
},
|
|
166
|
+
cause: `github.${event}`,
|
|
167
|
+
causalChain: [],
|
|
168
|
+
ruleSetVersion: this.options.rules.version,
|
|
169
|
+
...relatedItem ? {
|
|
170
|
+
item: {
|
|
171
|
+
id: relatedItem.id,
|
|
172
|
+
source: workItemSource(relatedItem),
|
|
173
|
+
sourceKey: workItemSourceKey(relatedItem),
|
|
174
|
+
parentWorkItemId: relatedItem.parentWorkItemId,
|
|
175
|
+
title: relatedItem.title,
|
|
176
|
+
url: relatedItem.externalSource?.url ?? null,
|
|
177
|
+
stages: relatedItem.stages
|
|
178
|
+
},
|
|
179
|
+
board: relatedItem.externalSource?.type === "pull-request" ? "review" : "work",
|
|
180
|
+
itemRevision: relatedItem.revision
|
|
181
|
+
} : {},
|
|
182
|
+
event,
|
|
183
|
+
deliveryId: parsed.deliveryId,
|
|
184
|
+
factory: { createdAt: factoryProject.createdAt.toISOString() },
|
|
185
|
+
repository: {
|
|
186
|
+
id: repositoryId,
|
|
187
|
+
fullName: repositoryName
|
|
188
|
+
},
|
|
189
|
+
...issueNumber && string(issue?.title) && string(issue?.html_url) ? { issue: {
|
|
190
|
+
number: issueNumber,
|
|
191
|
+
title: string(issue?.title),
|
|
192
|
+
url: string(issue?.html_url),
|
|
193
|
+
...string(issue?.created_at) ? { createdAt: string(issue?.created_at) } : {},
|
|
194
|
+
...string(issue?.updated_at) ? { updatedAt: string(issue?.updated_at) } : {},
|
|
195
|
+
assignees: actorLogins(issue?.assignees),
|
|
196
|
+
...string(issue?.state) === "closed" || string(issue?.state) === "open" ? { state: string(issue?.state) } : {},
|
|
197
|
+
...string(issue?.state_reason) ? { stateReason: string(issue?.state_reason) } : {}
|
|
198
|
+
} } : {},
|
|
199
|
+
...event === "issueEdited" ? { issueChange: {
|
|
200
|
+
title: Boolean(object(changes?.title)),
|
|
201
|
+
body: Boolean(object(changes?.body))
|
|
202
|
+
} } : {},
|
|
203
|
+
...number(issueComment?.id) ? { issueComment: {
|
|
204
|
+
id: number(issueComment?.id),
|
|
205
|
+
...string(issueComment?.body) ? { body: string(issueComment?.body) } : {},
|
|
206
|
+
...string(issueComment?.html_url) ? { url: string(issueComment?.html_url) } : {},
|
|
207
|
+
...string(object(issueComment?.user)?.login) ? { author: string(object(issueComment?.user)?.login) } : {},
|
|
208
|
+
...string(object(issueComment?.user)?.type) ? { authorType: string(object(issueComment?.user)?.type) } : {},
|
|
209
|
+
...string(issueComment?.created_at) ? { createdAt: string(issueComment?.created_at) } : {},
|
|
210
|
+
...string(issueComment?.updated_at) ? { updatedAt: string(issueComment?.updated_at) } : {}
|
|
211
|
+
} } : {},
|
|
212
|
+
...pullRequestNumber && string(pullRequest?.title) && string(pullRequest?.html_url) ? { pullRequest: {
|
|
213
|
+
number: pullRequestNumber,
|
|
214
|
+
title: string(pullRequest?.title),
|
|
215
|
+
url: string(pullRequest?.html_url),
|
|
216
|
+
...string(pullRequest?.created_at) ? { createdAt: string(pullRequest?.created_at) } : {},
|
|
217
|
+
state: string(pullRequest?.state) === "closed" ? "closed" : "open",
|
|
218
|
+
draft: boolean(pullRequest?.draft) ?? false,
|
|
219
|
+
merged: boolean(pullRequest?.merged) ?? false,
|
|
220
|
+
assignees: actorLogins(pullRequest?.assignees),
|
|
221
|
+
requestedReviewers: actorLogins(pullRequest?.requested_reviewers),
|
|
222
|
+
headBranch: string(object(pullRequest?.head)?.ref) ?? "",
|
|
223
|
+
baseBranch: string(object(pullRequest?.base)?.ref) ?? ""
|
|
224
|
+
} } : {},
|
|
225
|
+
...reviewRequested && requestedReviewer ? { reviewRequest: {
|
|
226
|
+
reviewer: requestedReviewer,
|
|
227
|
+
factoryReviewer: requestedReviewer === `${this.options.github.slug}[bot]`
|
|
228
|
+
} } : {},
|
|
229
|
+
...object(parsed.payload.review) ? { review: {
|
|
230
|
+
id: number(object(parsed.payload.review)?.id) ?? 0,
|
|
231
|
+
state: string(object(parsed.payload.review)?.state) ?? "unknown",
|
|
232
|
+
url: string(object(parsed.payload.review)?.html_url) ?? ""
|
|
233
|
+
} } : {}
|
|
234
|
+
};
|
|
235
|
+
const rule = resolveFactoryGithubRule(this.options.rules, event);
|
|
236
|
+
let decision;
|
|
237
|
+
let decisions = [];
|
|
238
|
+
let outcome = { status: "accepted" };
|
|
239
|
+
try {
|
|
240
|
+
decision = rule ? await withRuleTimeout(Promise.resolve(rule(Object.freeze(context)))) : void 0;
|
|
241
|
+
if (decision?.type === "reject") outcome = {
|
|
242
|
+
status: "rejected",
|
|
243
|
+
code: decision.code,
|
|
244
|
+
reason: decision.reason
|
|
245
|
+
};
|
|
246
|
+
else if (decision) decisions = validateFactoryRuleDecisions([decision]).map((entry) => ({ ...entry }));
|
|
247
|
+
} catch (error) {
|
|
248
|
+
const timedOut = error instanceof Error && error.message === "FACTORY_RULE_TIMEOUT";
|
|
249
|
+
outcome = {
|
|
250
|
+
status: "rejected",
|
|
251
|
+
code: timedOut ? "timeout" : "rule_error",
|
|
252
|
+
reason: timedOut ? "Factory rule evaluation timed out." : error instanceof Error ? error.message.slice(0, 2e3) : "Factory GitHub rule failed."
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return { status: (await this.options.storage.commitRuleEvaluation({
|
|
256
|
+
orgId: project.orgId,
|
|
257
|
+
factoryProjectId: project.factoryProjectId,
|
|
258
|
+
workItemId: relatedItem?.id ?? null,
|
|
259
|
+
ingress: {
|
|
260
|
+
identity: `${installationId}:${parsed.deliveryId}`,
|
|
261
|
+
triggerType: `github.${event}`
|
|
262
|
+
},
|
|
263
|
+
ruleSetVersion: this.options.rules.version,
|
|
264
|
+
expectedRevision: relatedItem?.revision ?? null,
|
|
265
|
+
actor: { ...actor },
|
|
266
|
+
outcome,
|
|
267
|
+
decisions,
|
|
268
|
+
causalChain: [],
|
|
269
|
+
now: /* @__PURE__ */ new Date()
|
|
270
|
+
})).status };
|
|
271
|
+
}
|
|
272
|
+
async #relatedItem(orgId, projectId, repositoryId, repositoryFullName, issueNumber, pullRequestNumber, pullRequestHeadBranch, provenance) {
|
|
273
|
+
const items = await this.options.storage.list({
|
|
274
|
+
orgId,
|
|
275
|
+
factoryProjectId: projectId
|
|
276
|
+
});
|
|
277
|
+
if (provenance) return items.find((item) => item.id === provenance.workItemId);
|
|
278
|
+
if (issueNumber) return items.find((item) => item.externalSource?.externalId === canonicalSourceKey("issue", issueNumber) && cardBelongsToRepository(item, repositoryId, repositoryFullName)) ?? items.find((item) => item.externalSource?.externalId === legacySourceKey(repositoryId, "issue", issueNumber));
|
|
279
|
+
if (pullRequestNumber) return items.find((item) => item.externalSource?.externalId === canonicalSourceKey("pull-request", pullRequestNumber) && cardBelongsToRepository(item, repositoryId, repositoryFullName)) ?? items.find((item) => item.externalSource?.externalId === legacySourceKey(repositoryId, "pull-request", pullRequestNumber)) ?? (pullRequestHeadBranch ? items.find((item) => item.externalSource?.type !== "pull-request" && Object.values(item.sessions).some((session) => session.branch === pullRequestHeadBranch)) : void 0);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
const RECONCILE_ERROR_SAMPLE_LIMIT = 5;
|
|
283
|
+
function sameStrings(left, right) {
|
|
284
|
+
if (right === void 0) return true;
|
|
285
|
+
if (!Array.isArray(left)) return false;
|
|
286
|
+
const leftValues = new Set(left.flatMap((value) => typeof value === "string" ? [value] : []));
|
|
287
|
+
const rightValues = new Set(right);
|
|
288
|
+
return leftValues.size === rightValues.size && [...leftValues].every((value) => rightValues.has(value));
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Extracts the PR number a work item tracks, but only when the item belongs
|
|
292
|
+
* to the given repository. Card URLs pin the repository unambiguously; the
|
|
293
|
+
* legacy source key embeds the repository id. Canonical keys (`github-pr:N`)
|
|
294
|
+
* carry no repository, so they are only trusted when the item has no URL —
|
|
295
|
+
* a project mapped to multiple repositories must not reconcile one repo's
|
|
296
|
+
* card against another repo's PR number.
|
|
297
|
+
*/
|
|
298
|
+
function reconcilablePullRequestNumber(item, repository) {
|
|
299
|
+
if (item.externalSource?.type !== "pull-request") return void 0;
|
|
300
|
+
const url = item.externalSource.url;
|
|
301
|
+
if (url) {
|
|
302
|
+
const match = /^https?:\/\/[^/]+\/(.+)\/pull\/(\d+)(?:[/?#]|$)/.exec(url);
|
|
303
|
+
if (!match) return void 0;
|
|
304
|
+
return match[1] === repository.fullName ? Number(match[2]) : void 0;
|
|
305
|
+
}
|
|
306
|
+
const externalId = item.externalSource.externalId;
|
|
307
|
+
const legacy = /^github:(\d+):pull-request:(\d+)$/.exec(externalId);
|
|
308
|
+
if (legacy) return Number(legacy[1]) === repository.id ? Number(legacy[2]) : void 0;
|
|
309
|
+
const canonical = /^github-pr:(\d+)$/.exec(externalId);
|
|
310
|
+
return canonical ? Number(canonical[1]) : void 0;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Extracts the issue number a work item tracks, but only when the item
|
|
314
|
+
* belongs to the given repository. Stricter than
|
|
315
|
+
* {@link reconcilablePullRequestNumber}: a canonical key with no URL is only
|
|
316
|
+
* trusted when the intake-stamped `githubRepositoryId` confirms the
|
|
317
|
+
* repository, because the sweep initiates closes on its own.
|
|
318
|
+
*/
|
|
319
|
+
function reconcilableIssueNumber(item, repository) {
|
|
320
|
+
if (item.externalSource?.type !== "issue") return void 0;
|
|
321
|
+
const url = item.externalSource.url;
|
|
322
|
+
if (url) {
|
|
323
|
+
const match = /^https?:\/\/[^/]+\/(.+)\/issues\/(\d+)(?:[/?#]|$)/.exec(url);
|
|
324
|
+
if (!match) return void 0;
|
|
325
|
+
return match[1] === repository.fullName || item.metadata?.githubRepositoryId === repository.id ? Number(match[2]) : void 0;
|
|
326
|
+
}
|
|
327
|
+
const externalId = item.externalSource.externalId;
|
|
328
|
+
const legacy = /^github:(\d+):issue:(\d+)$/.exec(externalId);
|
|
329
|
+
if (legacy) return Number(legacy[1]) === repository.id ? Number(legacy[2]) : void 0;
|
|
330
|
+
const canonical = /^github-issue:(\d+)$/.exec(externalId);
|
|
331
|
+
if (!canonical) return void 0;
|
|
332
|
+
return item.metadata?.githubRepositoryId === repository.id ? Number(canonical[1]) : void 0;
|
|
333
|
+
}
|
|
334
|
+
function reconciledIssueClosedEvent(repository, issueNumber, state) {
|
|
335
|
+
return {
|
|
336
|
+
event: "issues",
|
|
337
|
+
deliveryId: `reconcile:${repository.id}:issue:${issueNumber}:closed`,
|
|
338
|
+
payload: {
|
|
339
|
+
action: "closed",
|
|
340
|
+
installation: { id: repository.installationId },
|
|
341
|
+
repository: {
|
|
342
|
+
id: repository.id,
|
|
343
|
+
full_name: repository.fullName
|
|
344
|
+
},
|
|
345
|
+
sender: { login: state.author ?? "github" },
|
|
346
|
+
issue: {
|
|
347
|
+
number: issueNumber,
|
|
348
|
+
title: state.title,
|
|
349
|
+
html_url: state.url,
|
|
350
|
+
state: "closed",
|
|
351
|
+
...state.stateReason ? { state_reason: state.stateReason } : {},
|
|
352
|
+
...state.createdAt ? { created_at: state.createdAt } : {},
|
|
353
|
+
...state.updatedAt ? { updated_at: state.updatedAt } : {},
|
|
354
|
+
assignees: (state.assignees ?? []).map((login) => ({ login }))
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
function reconciledClosedEvent(repository, pullRequestNumber, state) {
|
|
360
|
+
return {
|
|
361
|
+
event: "pull_request",
|
|
362
|
+
deliveryId: `reconcile:${repository.id}:pull-request:${pullRequestNumber}:${state.merged ? "merged" : "closed"}`,
|
|
363
|
+
payload: {
|
|
364
|
+
action: "closed",
|
|
365
|
+
installation: { id: repository.installationId },
|
|
366
|
+
repository: {
|
|
367
|
+
id: repository.id,
|
|
368
|
+
full_name: repository.fullName
|
|
369
|
+
},
|
|
370
|
+
sender: { login: state.mergedBy ?? "github" },
|
|
371
|
+
pull_request: {
|
|
372
|
+
number: pullRequestNumber,
|
|
373
|
+
title: state.title,
|
|
374
|
+
html_url: state.url,
|
|
375
|
+
...state.createdAt ? { created_at: state.createdAt } : {},
|
|
376
|
+
state: "closed",
|
|
377
|
+
draft: state.draft,
|
|
378
|
+
merged: state.merged,
|
|
379
|
+
assignees: (state.assignees ?? []).map((login) => ({ login })),
|
|
380
|
+
requested_reviewers: (state.requestedReviewers ?? []).map((login) => ({ login })),
|
|
381
|
+
head: { ref: state.headBranch },
|
|
382
|
+
base: { ref: state.baseBranch }
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* The webhook handler retires subscriptions itself; the sweep replays only the
|
|
389
|
+
* rules ingress, so without this the thread's PR chip and the workspace row
|
|
390
|
+
* stay `open` forever on a deployment GitHub cannot reach.
|
|
391
|
+
*/
|
|
392
|
+
async function retireReconciledSubscriptions(storage, repository, pullRequestNumber, merged) {
|
|
393
|
+
const target = changeRequestTargetKey({
|
|
394
|
+
installationExternalId: String(repository.installationId),
|
|
395
|
+
repositoryExternalId: String(repository.id),
|
|
396
|
+
changeRequestId: String(pullRequestNumber)
|
|
397
|
+
});
|
|
398
|
+
const rows = await storage.subscriptions.listByTarget(target);
|
|
399
|
+
await Promise.all(rows.filter((row) => row.status === "open").map((row) => storage.subscriptions.updateStatus(row.id, merged ? "merged" : "closed")));
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* State-based safety net for merge signals: webhooks and event-log tailing
|
|
403
|
+
* can miss a merge (cursor gaps, downtime, terminally failed decisions), so
|
|
404
|
+
* this sweep compares still-open PR cards against actual GitHub state and
|
|
405
|
+
* replays the merge through the normal rules ingress when they disagree.
|
|
406
|
+
*/
|
|
407
|
+
function createGithubPullRequestReconciler(options, fetchPullRequest, fetchIssue) {
|
|
408
|
+
const rules = new GithubRules(options);
|
|
409
|
+
return async (repositories) => {
|
|
410
|
+
const summary = {
|
|
411
|
+
repositories: 0,
|
|
412
|
+
checked: 0,
|
|
413
|
+
merged: 0,
|
|
414
|
+
closed: 0,
|
|
415
|
+
issuesChecked: 0,
|
|
416
|
+
issuesClosed: 0,
|
|
417
|
+
failed: 0,
|
|
418
|
+
errors: []
|
|
419
|
+
};
|
|
420
|
+
const recordFailure = (repository, error, numbers) => {
|
|
421
|
+
summary.failed += 1;
|
|
422
|
+
if (summary.errors.length < RECONCILE_ERROR_SAMPLE_LIMIT) summary.errors.push({
|
|
423
|
+
repository: repository.fullName,
|
|
424
|
+
...numbers?.pullRequestNumber === void 0 ? {} : { pullRequestNumber: numbers.pullRequestNumber },
|
|
425
|
+
...numbers?.issueNumber === void 0 ? {} : { issueNumber: numbers.issueNumber },
|
|
426
|
+
error: error instanceof Error ? error.message : String(error)
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
const configured = new Set((await options.sourceControl.projectRepositories.listConfiguredExternalKeys()).map((key) => `${key.installationExternalId}\u0000${key.repositoryExternalId}`));
|
|
430
|
+
const scoped = repositories.filter((repository) => configured.has(`${repository.installationId}\u0000${repository.id}`));
|
|
431
|
+
summary.repositories = scoped.length;
|
|
432
|
+
for (const repository of scoped) {
|
|
433
|
+
let cardsByNumber;
|
|
434
|
+
const issueNumbers = /* @__PURE__ */ new Set();
|
|
435
|
+
try {
|
|
436
|
+
const projects = await options.sourceControl.projectRepositories.listByExternalRepository({
|
|
437
|
+
installationExternalId: String(repository.installationId),
|
|
438
|
+
repositoryExternalId: String(repository.id)
|
|
439
|
+
});
|
|
440
|
+
if (projects.length === 0) continue;
|
|
441
|
+
cardsByNumber = /* @__PURE__ */ new Map();
|
|
442
|
+
for (const project of projects) {
|
|
443
|
+
const items = await options.storage.list({
|
|
444
|
+
orgId: project.orgId,
|
|
445
|
+
factoryProjectId: project.factoryProjectId
|
|
446
|
+
});
|
|
447
|
+
for (const item of items) {
|
|
448
|
+
const stage = item.stages[0];
|
|
449
|
+
if (fetchIssue) {
|
|
450
|
+
const issueNumber = reconcilableIssueNumber(item, repository);
|
|
451
|
+
if (issueNumber && stage !== "done" && stage !== "canceled") issueNumbers.add(issueNumber);
|
|
452
|
+
}
|
|
453
|
+
const pullRequestNumber = reconcilablePullRequestNumber(item, repository);
|
|
454
|
+
if (!pullRequestNumber) continue;
|
|
455
|
+
const metadata = item.metadata ?? {};
|
|
456
|
+
const hasReconciledMetadata = (metadata.state === "open" || metadata.state === "closed") && typeof metadata.draft === "boolean" && typeof metadata.merged === "boolean" && typeof metadata.author === "string" && Array.isArray(metadata.assignees) && Array.isArray(metadata.requestedReviewers);
|
|
457
|
+
if ((stage === "done" || stage === "canceled") && hasReconciledMetadata) continue;
|
|
458
|
+
const cards = cardsByNumber.get(pullRequestNumber) ?? [];
|
|
459
|
+
cards.push(item);
|
|
460
|
+
cardsByNumber.set(pullRequestNumber, cards);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} catch (error) {
|
|
464
|
+
recordFailure(repository, error);
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
for (const [pullRequestNumber, cards] of cardsByNumber) try {
|
|
468
|
+
const state = await fetchPullRequest({
|
|
469
|
+
installationId: repository.installationId,
|
|
470
|
+
repository: repository.fullName,
|
|
471
|
+
number: pullRequestNumber
|
|
472
|
+
});
|
|
473
|
+
summary.checked += 1;
|
|
474
|
+
if (!state) continue;
|
|
475
|
+
for (const card of cards) {
|
|
476
|
+
const metadata = card.metadata ?? {};
|
|
477
|
+
const statusChanged = metadata.state !== state.state || metadata.draft !== state.draft || metadata.merged !== state.merged;
|
|
478
|
+
const authorChanged = state.author !== void 0 && metadata.author !== state.author;
|
|
479
|
+
const assigneesChanged = !sameStrings(metadata.assignees, state.assignees);
|
|
480
|
+
const reviewersChanged = !sameStrings(metadata.requestedReviewers, state.requestedReviewers);
|
|
481
|
+
if (!statusChanged && !authorChanged && !assigneesChanged && !reviewersChanged) continue;
|
|
482
|
+
try {
|
|
483
|
+
await options.storage.update({
|
|
484
|
+
orgId: card.orgId,
|
|
485
|
+
id: card.id,
|
|
486
|
+
userId: "factory-rule-dispatcher",
|
|
487
|
+
patch: { metadata: {
|
|
488
|
+
state: state.state,
|
|
489
|
+
draft: state.draft,
|
|
490
|
+
merged: state.merged,
|
|
491
|
+
...state.author ? { author: state.author } : {},
|
|
492
|
+
...state.assignees ? { assignees: state.assignees } : {},
|
|
493
|
+
...state.requestedReviewers ? { requestedReviewers: state.requestedReviewers } : {}
|
|
494
|
+
} }
|
|
495
|
+
});
|
|
496
|
+
} catch (error) {
|
|
497
|
+
recordFailure(repository, error, { pullRequestNumber });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
if (state.state !== "closed") continue;
|
|
501
|
+
await rules.ingest(reconciledClosedEvent(repository, pullRequestNumber, state));
|
|
502
|
+
await retireReconciledSubscriptions(options.integrationStorage, repository, pullRequestNumber, state.merged);
|
|
503
|
+
if (state.merged) summary.merged += 1;
|
|
504
|
+
else summary.closed += 1;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
recordFailure(repository, error, { pullRequestNumber });
|
|
507
|
+
}
|
|
508
|
+
if (!fetchIssue) continue;
|
|
509
|
+
for (const issueNumber of issueNumbers) try {
|
|
510
|
+
const state = await fetchIssue({
|
|
511
|
+
installationId: repository.installationId,
|
|
512
|
+
repository: repository.fullName,
|
|
513
|
+
number: issueNumber
|
|
514
|
+
});
|
|
515
|
+
summary.issuesChecked += 1;
|
|
516
|
+
if (!state || state.state !== "closed") continue;
|
|
517
|
+
await rules.ingest(reconciledIssueClosedEvent(repository, issueNumber, state));
|
|
518
|
+
summary.issuesClosed += 1;
|
|
519
|
+
} catch (error) {
|
|
520
|
+
recordFailure(repository, error, { issueNumber });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return summary;
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function githubRulesOptions(github, context) {
|
|
527
|
+
if (!context.rules) return void 0;
|
|
528
|
+
return {
|
|
529
|
+
github,
|
|
530
|
+
sourceControl: context.storage.sourceControl,
|
|
531
|
+
integrationStorage: context.storage.generic,
|
|
532
|
+
projects: context.storage.projects,
|
|
533
|
+
storage: context.rules.workItems,
|
|
534
|
+
rules: context.rules.config
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function attachGithubRules(github, context) {
|
|
538
|
+
const options = githubRulesOptions(github, context);
|
|
539
|
+
if (!options) return void 0;
|
|
540
|
+
const rules = new GithubRules(options);
|
|
541
|
+
return (event) => rules.ingest(event);
|
|
542
|
+
}
|
|
543
|
+
function attachGithubReconciler(github, context, fetchPullRequest, fetchIssue) {
|
|
544
|
+
const options = githubRulesOptions(github, context);
|
|
545
|
+
if (!options) return void 0;
|
|
546
|
+
return createGithubPullRequestReconciler(options, fetchPullRequest, fetchIssue);
|
|
547
|
+
}
|
|
548
|
+
//#endregion
|
|
549
|
+
export { GithubRules, attachGithubReconciler, attachGithubRules, createGithubPullRequestReconciler, reconciledClosedEvent, reconciledIssueClosedEvent };
|
|
550
|
+
|
|
551
|
+
//# sourceMappingURL=rules.js.map
|