@cat-factory/integrations 0.6.0

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.
Files changed (242) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +68 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +84 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/modules/datadog/DatadogClient.d.ts +54 -0
  7. package/dist/modules/datadog/DatadogClient.d.ts.map +1 -0
  8. package/dist/modules/datadog/DatadogClient.js +132 -0
  9. package/dist/modules/datadog/DatadogClient.js.map +1 -0
  10. package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts +30 -0
  11. package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts.map +1 -0
  12. package/dist/modules/datadog/DatadogReleaseHealthProvider.js +101 -0
  13. package/dist/modules/datadog/DatadogReleaseHealthProvider.js.map +1 -0
  14. package/dist/modules/datadog/datadog.logic.d.ts +37 -0
  15. package/dist/modules/datadog/datadog.logic.d.ts.map +1 -0
  16. package/dist/modules/datadog/datadog.logic.js +90 -0
  17. package/dist/modules/datadog/datadog.logic.js.map +1 -0
  18. package/dist/modules/documents/ConfluenceProvider.d.ts +29 -0
  19. package/dist/modules/documents/ConfluenceProvider.d.ts.map +1 -0
  20. package/dist/modules/documents/ConfluenceProvider.js +180 -0
  21. package/dist/modules/documents/ConfluenceProvider.js.map +1 -0
  22. package/dist/modules/documents/DocumentConnectionService.d.ts +30 -0
  23. package/dist/modules/documents/DocumentConnectionService.d.ts.map +1 -0
  24. package/dist/modules/documents/DocumentConnectionService.js +69 -0
  25. package/dist/modules/documents/DocumentConnectionService.js.map +1 -0
  26. package/dist/modules/documents/DocumentImportService.d.ts +34 -0
  27. package/dist/modules/documents/DocumentImportService.d.ts.map +1 -0
  28. package/dist/modules/documents/DocumentImportService.js +83 -0
  29. package/dist/modules/documents/DocumentImportService.js.map +1 -0
  30. package/dist/modules/documents/DocumentLinkService.d.ts +31 -0
  31. package/dist/modules/documents/DocumentLinkService.d.ts.map +1 -0
  32. package/dist/modules/documents/DocumentLinkService.js +75 -0
  33. package/dist/modules/documents/DocumentLinkService.js.map +1 -0
  34. package/dist/modules/documents/DocumentPlannerService.d.ts +23 -0
  35. package/dist/modules/documents/DocumentPlannerService.d.ts.map +1 -0
  36. package/dist/modules/documents/DocumentPlannerService.js +96 -0
  37. package/dist/modules/documents/DocumentPlannerService.js.map +1 -0
  38. package/dist/modules/documents/GitHubDocsProvider.d.ts +42 -0
  39. package/dist/modules/documents/GitHubDocsProvider.d.ts.map +1 -0
  40. package/dist/modules/documents/GitHubDocsProvider.js +86 -0
  41. package/dist/modules/documents/GitHubDocsProvider.js.map +1 -0
  42. package/dist/modules/documents/NotionProvider.d.ts +32 -0
  43. package/dist/modules/documents/NotionProvider.d.ts.map +1 -0
  44. package/dist/modules/documents/NotionProvider.js +221 -0
  45. package/dist/modules/documents/NotionProvider.js.map +1 -0
  46. package/dist/modules/documents/confluence.logic.d.ts +37 -0
  47. package/dist/modules/documents/confluence.logic.d.ts.map +1 -0
  48. package/dist/modules/documents/confluence.logic.js +133 -0
  49. package/dist/modules/documents/confluence.logic.js.map +1 -0
  50. package/dist/modules/documents/documents.logic.d.ts +22 -0
  51. package/dist/modules/documents/documents.logic.d.ts.map +1 -0
  52. package/dist/modules/documents/documents.logic.js +138 -0
  53. package/dist/modules/documents/documents.logic.js.map +1 -0
  54. package/dist/modules/documents/github-docs.logic.d.ts +52 -0
  55. package/dist/modules/documents/github-docs.logic.d.ts.map +1 -0
  56. package/dist/modules/documents/github-docs.logic.js +94 -0
  57. package/dist/modules/documents/github-docs.logic.js.map +1 -0
  58. package/dist/modules/documents/notion.logic.d.ts +31 -0
  59. package/dist/modules/documents/notion.logic.d.ts.map +1 -0
  60. package/dist/modules/documents/notion.logic.js +142 -0
  61. package/dist/modules/documents/notion.logic.js.map +1 -0
  62. package/dist/modules/email/EmailConnectionService.d.ts +34 -0
  63. package/dist/modules/email/EmailConnectionService.d.ts.map +1 -0
  64. package/dist/modules/email/EmailConnectionService.js +82 -0
  65. package/dist/modules/email/EmailConnectionService.js.map +1 -0
  66. package/dist/modules/email/adapters.d.ts +39 -0
  67. package/dist/modules/email/adapters.d.ts.map +1 -0
  68. package/dist/modules/email/adapters.js +79 -0
  69. package/dist/modules/email/adapters.js.map +1 -0
  70. package/dist/modules/environments/EnvironmentConnectionService.d.ts +42 -0
  71. package/dist/modules/environments/EnvironmentConnectionService.d.ts.map +1 -0
  72. package/dist/modules/environments/EnvironmentConnectionService.js +120 -0
  73. package/dist/modules/environments/EnvironmentConnectionService.js.map +1 -0
  74. package/dist/modules/environments/EnvironmentProvisioningService.d.ts +56 -0
  75. package/dist/modules/environments/EnvironmentProvisioningService.d.ts.map +1 -0
  76. package/dist/modules/environments/EnvironmentProvisioningService.js +153 -0
  77. package/dist/modules/environments/EnvironmentProvisioningService.js.map +1 -0
  78. package/dist/modules/environments/EnvironmentTeardownService.d.ts +24 -0
  79. package/dist/modules/environments/EnvironmentTeardownService.d.ts.map +1 -0
  80. package/dist/modules/environments/EnvironmentTeardownService.js +54 -0
  81. package/dist/modules/environments/EnvironmentTeardownService.js.map +1 -0
  82. package/dist/modules/environments/HttpEnvironmentProvider.d.ts +30 -0
  83. package/dist/modules/environments/HttpEnvironmentProvider.d.ts.map +1 -0
  84. package/dist/modules/environments/HttpEnvironmentProvider.js +316 -0
  85. package/dist/modules/environments/HttpEnvironmentProvider.js.map +1 -0
  86. package/dist/modules/environments/environments.logic.d.ts +50 -0
  87. package/dist/modules/environments/environments.logic.d.ts.map +1 -0
  88. package/dist/modules/environments/environments.logic.js +257 -0
  89. package/dist/modules/environments/environments.logic.js.map +1 -0
  90. package/dist/modules/github/GitHubInstallationService.d.ts +66 -0
  91. package/dist/modules/github/GitHubInstallationService.d.ts.map +1 -0
  92. package/dist/modules/github/GitHubInstallationService.js +143 -0
  93. package/dist/modules/github/GitHubInstallationService.js.map +1 -0
  94. package/dist/modules/github/GitHubService.d.ts +29 -0
  95. package/dist/modules/github/GitHubService.d.ts.map +1 -0
  96. package/dist/modules/github/GitHubService.js +61 -0
  97. package/dist/modules/github/GitHubService.js.map +1 -0
  98. package/dist/modules/github/GitHubSyncService.d.ts +97 -0
  99. package/dist/modules/github/GitHubSyncService.d.ts.map +1 -0
  100. package/dist/modules/github/GitHubSyncService.js +241 -0
  101. package/dist/modules/github/GitHubSyncService.js.map +1 -0
  102. package/dist/modules/github/RepoProvisioningService.d.ts +26 -0
  103. package/dist/modules/github/RepoProvisioningService.d.ts.map +1 -0
  104. package/dist/modules/github/RepoProvisioningService.js +36 -0
  105. package/dist/modules/github/RepoProvisioningService.js.map +1 -0
  106. package/dist/modules/github/WebhookService.d.ts +28 -0
  107. package/dist/modules/github/WebhookService.d.ts.map +1 -0
  108. package/dist/modules/github/WebhookService.js +156 -0
  109. package/dist/modules/github/WebhookService.js.map +1 -0
  110. package/dist/modules/github/projection.logic.d.ts +95 -0
  111. package/dist/modules/github/projection.logic.d.ts.map +1 -0
  112. package/dist/modules/github/projection.logic.js +94 -0
  113. package/dist/modules/github/projection.logic.js.map +1 -0
  114. package/dist/modules/github/provisioning.logic.d.ts +11 -0
  115. package/dist/modules/github/provisioning.logic.d.ts.map +1 -0
  116. package/dist/modules/github/provisioning.logic.js +18 -0
  117. package/dist/modules/github/provisioning.logic.js.map +1 -0
  118. package/dist/modules/incident/incident.logic.d.ts +16 -0
  119. package/dist/modules/incident/incident.logic.d.ts.map +1 -0
  120. package/dist/modules/incident/incident.logic.js +23 -0
  121. package/dist/modules/incident/incident.logic.js.map +1 -0
  122. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts +26 -0
  123. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts.map +1 -0
  124. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js +84 -0
  125. package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js.map +1 -0
  126. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts +27 -0
  127. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts.map +1 -0
  128. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js +65 -0
  129. package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js.map +1 -0
  130. package/dist/modules/providers/ApiKeyService.d.ts +73 -0
  131. package/dist/modules/providers/ApiKeyService.d.ts.map +1 -0
  132. package/dist/modules/providers/ApiKeyService.js +122 -0
  133. package/dist/modules/providers/ApiKeyService.js.map +1 -0
  134. package/dist/modules/providers/LocalModelEndpointService.d.ts +52 -0
  135. package/dist/modules/providers/LocalModelEndpointService.d.ts.map +1 -0
  136. package/dist/modules/providers/LocalModelEndpointService.js +131 -0
  137. package/dist/modules/providers/LocalModelEndpointService.js.map +1 -0
  138. package/dist/modules/providers/PersonalSubscriptionService.d.ts +94 -0
  139. package/dist/modules/providers/PersonalSubscriptionService.d.ts.map +1 -0
  140. package/dist/modules/providers/PersonalSubscriptionService.js +218 -0
  141. package/dist/modules/providers/PersonalSubscriptionService.js.map +1 -0
  142. package/dist/modules/providers/ProviderSubscriptionService.d.ts +75 -0
  143. package/dist/modules/providers/ProviderSubscriptionService.d.ts.map +1 -0
  144. package/dist/modules/providers/ProviderSubscriptionService.js +130 -0
  145. package/dist/modules/providers/ProviderSubscriptionService.js.map +1 -0
  146. package/dist/modules/providers/localModelUrl.d.ts +7 -0
  147. package/dist/modules/providers/localModelUrl.d.ts.map +1 -0
  148. package/dist/modules/providers/localModelUrl.js +67 -0
  149. package/dist/modules/providers/localModelUrl.js.map +1 -0
  150. package/dist/modules/providers/providers.logic.d.ts +23 -0
  151. package/dist/modules/providers/providers.logic.d.ts.map +1 -0
  152. package/dist/modules/providers/providers.logic.js +46 -0
  153. package/dist/modules/providers/providers.logic.js.map +1 -0
  154. package/dist/modules/runners/HttpRunnerPoolProvider.d.ts +51 -0
  155. package/dist/modules/runners/HttpRunnerPoolProvider.d.ts.map +1 -0
  156. package/dist/modules/runners/HttpRunnerPoolProvider.js +304 -0
  157. package/dist/modules/runners/HttpRunnerPoolProvider.js.map +1 -0
  158. package/dist/modules/runners/RunnerPoolConnectionService.d.ts +47 -0
  159. package/dist/modules/runners/RunnerPoolConnectionService.d.ts.map +1 -0
  160. package/dist/modules/runners/RunnerPoolConnectionService.js +98 -0
  161. package/dist/modules/runners/RunnerPoolConnectionService.js.map +1 -0
  162. package/dist/modules/runners/RunnerPoolTransport.d.ts +11 -0
  163. package/dist/modules/runners/RunnerPoolTransport.d.ts.map +1 -0
  164. package/dist/modules/runners/RunnerPoolTransport.js +61 -0
  165. package/dist/modules/runners/RunnerPoolTransport.js.map +1 -0
  166. package/dist/modules/runners/runners.logic.d.ts +16 -0
  167. package/dist/modules/runners/runners.logic.d.ts.map +1 -0
  168. package/dist/modules/runners/runners.logic.js +52 -0
  169. package/dist/modules/runners/runners.logic.js.map +1 -0
  170. package/dist/modules/slack/SlackApiClient.d.ts +67 -0
  171. package/dist/modules/slack/SlackApiClient.d.ts.map +1 -0
  172. package/dist/modules/slack/SlackApiClient.js +132 -0
  173. package/dist/modules/slack/SlackApiClient.js.map +1 -0
  174. package/dist/modules/slack/SlackConnectionService.d.ts +41 -0
  175. package/dist/modules/slack/SlackConnectionService.d.ts.map +1 -0
  176. package/dist/modules/slack/SlackConnectionService.js +136 -0
  177. package/dist/modules/slack/SlackConnectionService.js.map +1 -0
  178. package/dist/modules/slack/SlackMemberMappingService.d.ts +17 -0
  179. package/dist/modules/slack/SlackMemberMappingService.d.ts.map +1 -0
  180. package/dist/modules/slack/SlackMemberMappingService.js +28 -0
  181. package/dist/modules/slack/SlackMemberMappingService.js.map +1 -0
  182. package/dist/modules/slack/SlackNotificationChannel.d.ts +45 -0
  183. package/dist/modules/slack/SlackNotificationChannel.d.ts.map +1 -0
  184. package/dist/modules/slack/SlackNotificationChannel.js +84 -0
  185. package/dist/modules/slack/SlackNotificationChannel.js.map +1 -0
  186. package/dist/modules/slack/SlackSettingsService.d.ts +16 -0
  187. package/dist/modules/slack/SlackSettingsService.d.ts.map +1 -0
  188. package/dist/modules/slack/SlackSettingsService.js +41 -0
  189. package/dist/modules/slack/SlackSettingsService.js.map +1 -0
  190. package/dist/modules/slack/slack.logic.d.ts +55 -0
  191. package/dist/modules/slack/slack.logic.d.ts.map +1 -0
  192. package/dist/modules/slack/slack.logic.js +149 -0
  193. package/dist/modules/slack/slack.logic.js.map +1 -0
  194. package/dist/modules/tasks/GitHubIssuesProvider.d.ts +50 -0
  195. package/dist/modules/tasks/GitHubIssuesProvider.d.ts.map +1 -0
  196. package/dist/modules/tasks/GitHubIssuesProvider.js +92 -0
  197. package/dist/modules/tasks/GitHubIssuesProvider.js.map +1 -0
  198. package/dist/modules/tasks/JiraProvider.d.ts +29 -0
  199. package/dist/modules/tasks/JiraProvider.d.ts.map +1 -0
  200. package/dist/modules/tasks/JiraProvider.js +114 -0
  201. package/dist/modules/tasks/JiraProvider.js.map +1 -0
  202. package/dist/modules/tasks/TaskConnectionService.d.ts +30 -0
  203. package/dist/modules/tasks/TaskConnectionService.d.ts.map +1 -0
  204. package/dist/modules/tasks/TaskConnectionService.js +69 -0
  205. package/dist/modules/tasks/TaskConnectionService.js.map +1 -0
  206. package/dist/modules/tasks/TaskImportService.d.ts +34 -0
  207. package/dist/modules/tasks/TaskImportService.d.ts.map +1 -0
  208. package/dist/modules/tasks/TaskImportService.js +96 -0
  209. package/dist/modules/tasks/TaskImportService.js.map +1 -0
  210. package/dist/modules/tasks/TaskLinkService.d.ts +30 -0
  211. package/dist/modules/tasks/TaskLinkService.d.ts.map +1 -0
  212. package/dist/modules/tasks/TaskLinkService.js +56 -0
  213. package/dist/modules/tasks/TaskLinkService.js.map +1 -0
  214. package/dist/modules/tasks/github-issues.logic.d.ts +35 -0
  215. package/dist/modules/tasks/github-issues.logic.d.ts.map +1 -0
  216. package/dist/modules/tasks/github-issues.logic.js +67 -0
  217. package/dist/modules/tasks/github-issues.logic.js.map +1 -0
  218. package/dist/modules/tasks/jira.logic.d.ts +28 -0
  219. package/dist/modules/tasks/jira.logic.d.ts.map +1 -0
  220. package/dist/modules/tasks/jira.logic.js +151 -0
  221. package/dist/modules/tasks/jira.logic.js.map +1 -0
  222. package/dist/modules/tasks/tasks.logic.d.ts +12 -0
  223. package/dist/modules/tasks/tasks.logic.d.ts.map +1 -0
  224. package/dist/modules/tasks/tasks.logic.js +17 -0
  225. package/dist/modules/tasks/tasks.logic.js.map +1 -0
  226. package/dist/modules/tracker/TicketTrackerService.d.ts +45 -0
  227. package/dist/modules/tracker/TicketTrackerService.d.ts.map +1 -0
  228. package/dist/modules/tracker/TicketTrackerService.js +52 -0
  229. package/dist/modules/tracker/TicketTrackerService.js.map +1 -0
  230. package/dist/modules/tracker/base64.d.ts +2 -0
  231. package/dist/modules/tracker/base64.d.ts.map +1 -0
  232. package/dist/modules/tracker/base64.js +18 -0
  233. package/dist/modules/tracker/base64.js.map +1 -0
  234. package/dist/modules/tracker/github.create.logic.d.ts +16 -0
  235. package/dist/modules/tracker/github.create.logic.d.ts.map +1 -0
  236. package/dist/modules/tracker/github.create.logic.js +25 -0
  237. package/dist/modules/tracker/github.create.logic.js.map +1 -0
  238. package/dist/modules/tracker/jira.create.logic.d.ts +31 -0
  239. package/dist/modules/tracker/jira.create.logic.d.ts.map +1 -0
  240. package/dist/modules/tracker/jira.create.logic.js +59 -0
  241. package/dist/modules/tracker/jira.create.logic.js.map +1 -0
  242. package/package.json +36 -0
@@ -0,0 +1,50 @@
1
+ import type { EnvironmentAccessHandle, EnvironmentHandle, EnvironmentStatus } from '@cat-factory/kernel';
2
+ import type { EnvironmentRecord, UrlSafetyPolicy } from '@cat-factory/kernel';
3
+ /** The agent kind that triggers deterministic provisioning. */
4
+ export declare const DEPLOYER_AGENT_KIND = "deployer";
5
+ /** Board category for environment blocks (a deployer pipeline typically runs here). */
6
+ export declare const ENVIRONMENT_BLOCK_TYPE = "environment";
7
+ /**
8
+ * Whether a pipeline step should provision an environment deterministically.
9
+ * Keyed strictly on the `deployer` agent kind so that other steps in a pipeline
10
+ * on an `environment` block (e.g. a following `tester`) still run normally.
11
+ */
12
+ export declare function isDeployStep(agentKind: string): boolean;
13
+ /**
14
+ * Validate a URL before it is stored, fetched, or exposed. The default policy
15
+ * (STRICT_URL_SAFETY_POLICY) requires `https` and rejects internal/private hosts; a
16
+ * trusted operator-installed adapter can pass a widened policy to permit specific
17
+ * schemes/hosts (e.g. an internal env platform on a private/VPN host). Embedded
18
+ * credentials are forbidden regardless of policy. Parsed by hand (no `URL` global) so
19
+ * this stays in the platform-agnostic core.
20
+ */
21
+ export declare function assertSafeEnvironmentUrl(url: string, label?: string, policy?: UrlSafetyPolicy): void;
22
+ /** Variables available to manifest templates, in a bounded namespace. */
23
+ export interface InterpolationScope {
24
+ input: Record<string, string>;
25
+ provision: Record<string, string>;
26
+ }
27
+ /**
28
+ * Replace `{{ namespace.key }}` placeholders from the given scope. Unknown
29
+ * namespaces and missing keys resolve to an empty string, so a template can
30
+ * never reference arbitrary host state.
31
+ */
32
+ export declare function interpolateTemplate(template: string, scope: InterpolationScope): string;
33
+ /** Read a value from parsed JSON by a dot-path (e.g. `data.url`, `items.0.id`). */
34
+ export declare function extractByPath(json: unknown, path: string): unknown;
35
+ /** Extract a scalar as a string, or undefined if absent/non-scalar. */
36
+ export declare function extractString(json: unknown, path: string | undefined): string | undefined;
37
+ /**
38
+ * Map a provider status string onto our lifecycle states using the manifest's
39
+ * `statusMap`. Falls back to `fallback` (caller decides, e.g. 'ready' for a
40
+ * synchronous provisioner with no status polling).
41
+ */
42
+ export declare function mapStatus(raw: string | undefined, statusMap: {
43
+ from: string;
44
+ to: EnvironmentStatus;
45
+ }[] | undefined, fallback: EnvironmentStatus): EnvironmentStatus;
46
+ /** Project a stored record onto the wire handle, optionally with decrypted access. */
47
+ export declare function recordToHandle(record: EnvironmentRecord, access?: EnvironmentAccessHandle | null): EnvironmentHandle;
48
+ /** Coerce an extracted expiry (epoch-ms number, numeric string, or ISO) to ms. */
49
+ export declare function coerceExpiresAt(value: unknown): number | null;
50
+ //# sourceMappingURL=environments.logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environments.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/environments/environments.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAS7E,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,aAAa,CAAA;AAC7C,uFAAuF;AACvF,eAAO,MAAM,sBAAsB,gBAAgB,CAAA;AAEnD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvD;AAuGD;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,MAAM,EACX,KAAK,SAAQ,EACb,MAAM,GAAE,eAA0C,GACjD,IAAI,CAyBN;AAED,yEAAyE;AACzE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAWvF;AAED,mFAAmF;AACnF,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASlE;AAED,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAOzF;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,SAAS,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,iBAAiB,CAAA;CAAE,EAAE,GAAG,SAAS,EAChE,QAAQ,EAAE,iBAAiB,GAC1B,iBAAiB,CAMnB;AAED,sFAAsF;AACtF,wBAAgB,cAAc,CAC5B,MAAM,EAAE,iBAAiB,EACzB,MAAM,CAAC,EAAE,uBAAuB,GAAG,IAAI,GACtC,iBAAiB,CAenB;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAU7D"}
@@ -0,0 +1,257 @@
1
+ import { STRICT_URL_SAFETY_POLICY, ValidationError } from '@cat-factory/kernel';
2
+ // Pure helpers for the ephemeral-environment integration: SSRF validation of the
3
+ // URLs we fetch/expose, `{{var}}` interpolation over a bounded scope, dot-path
4
+ // extraction from an arbitrary self-rolled response, status mapping and expiry
5
+ // coercion. Keeping these pure makes the generic provider deterministic and
6
+ // testable without a live management API.
7
+ /** The agent kind that triggers deterministic provisioning. */
8
+ export const DEPLOYER_AGENT_KIND = 'deployer';
9
+ /** Board category for environment blocks (a deployer pipeline typically runs here). */
10
+ export const ENVIRONMENT_BLOCK_TYPE = 'environment';
11
+ /**
12
+ * Whether a pipeline step should provision an environment deterministically.
13
+ * Keyed strictly on the `deployer` agent kind so that other steps in a pipeline
14
+ * on an `environment` block (e.g. a following `tester`) still run normally.
15
+ */
16
+ export function isDeployStep(agentKind) {
17
+ return agentKind === DEPLOYER_AGENT_KIND;
18
+ }
19
+ /** Whether a decoded IPv4 address is loopback / link-local (metadata) / RFC1918. */
20
+ function isPrivateV4(parts) {
21
+ const [a, b] = parts;
22
+ if (a === 127 || a === 0 || a === 10)
23
+ return true;
24
+ if (a === 169 && b === 254)
25
+ return true;
26
+ if (a === 192 && b === 168)
27
+ return true;
28
+ if (a === 172 && b >= 16 && b <= 31)
29
+ return true;
30
+ return false;
31
+ }
32
+ /** Parse a plain dotted-decimal IPv4 literal (each octet 0-255), or null. */
33
+ function decimalV4(host) {
34
+ const m = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
35
+ if (!m)
36
+ return null;
37
+ const a = Number(m[1]);
38
+ const b = Number(m[2]);
39
+ const c = Number(m[3]);
40
+ const d = Number(m[4]);
41
+ if (a > 255 || b > 255 || c > 255 || d > 255)
42
+ return null;
43
+ return [a, b, c, d];
44
+ }
45
+ /** Extract the embedded IPv4 of an IPv4-mapped IPv6 literal (`::ffff:…`), or null. */
46
+ function mappedV4(host) {
47
+ // ::ffff:a.b.c.d
48
+ const dotted = host.match(/^::ffff:(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
49
+ if (dotted) {
50
+ const a = Number(dotted[1]);
51
+ const b = Number(dotted[2]);
52
+ const c = Number(dotted[3]);
53
+ const d = Number(dotted[4]);
54
+ if (a > 255 || b > 255 || c > 255 || d > 255)
55
+ return null;
56
+ return [a, b, c, d];
57
+ }
58
+ // ::ffff:hhhh:hhhh
59
+ const hex = host.match(/^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
60
+ if (hex) {
61
+ const hi = parseInt(hex[1] ?? '0', 16);
62
+ const lo = parseInt(hex[2] ?? '0', 16);
63
+ return [(hi >> 8) & 0xff, hi & 0xff, (lo >> 8) & 0xff, lo & 0xff];
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * Reject hostnames that point at the worker's own network rather than a public
69
+ * host. The generic provider fetches org-supplied URLs (and we surface the
70
+ * provisioned env URL to agents), so an unvalidated URL turns the worker into an
71
+ * SSRF proxy. Host-literal defence-in-depth: blocks loopback, link-local
72
+ * (incl. cloud metadata 169.254.x.x) and the RFC1918 private ranges — including
73
+ * the obfuscated encodings (bare integer, hex/octal octets, IPv4-mapped IPv6)
74
+ * that trivially bypass a naive dotted-decimal match.
75
+ */
76
+ function isBlockedHost(hostname) {
77
+ const host = hostname.toLowerCase().replace(/^\[|\]$/g, '');
78
+ if (host === '')
79
+ return true;
80
+ if (host === 'localhost' || host.endsWith('.localhost'))
81
+ return true;
82
+ if (host.endsWith('.internal') || host.endsWith('.local'))
83
+ return true;
84
+ // IPv6 literals (contain a colon).
85
+ if (host.includes(':')) {
86
+ if (host === '::1' || host === '::')
87
+ return true;
88
+ if (host.startsWith('fe80:') || host.startsWith('fc') || host.startsWith('fd'))
89
+ return true;
90
+ const mapped = mappedV4(host);
91
+ if (mapped)
92
+ return isPrivateV4(mapped);
93
+ return false;
94
+ }
95
+ // Obfuscated numeric IPv4 forms are never a legitimate public hostname.
96
+ // Bare integer (e.g. 2130706433 === 127.0.0.1).
97
+ if (/^\d+$/.test(host))
98
+ return true;
99
+ const labels = host.split('.');
100
+ for (const label of labels) {
101
+ if (/^0x[0-9a-f]+$/.test(label))
102
+ return true; // hex octet (0x7f)
103
+ if (/^0[0-9]+$/.test(label))
104
+ return true; // octal / leading-zero octet (0177)
105
+ }
106
+ // Standard dotted-decimal IPv4: public addresses pass, private ones blocked.
107
+ const v4 = decimalV4(host);
108
+ if (v4)
109
+ return isPrivateV4(v4);
110
+ // A purely numeric dotted host that is not a valid public dotted-decimal IPv4
111
+ // is some other IP encoding we cannot vouch for — reject when in doubt.
112
+ if (labels.length > 1 && labels.every((l) => /^\d+$/.test(l)))
113
+ return true;
114
+ return false;
115
+ }
116
+ /**
117
+ * Whether `host` is exempt from the private/internal-host block under `policy`.
118
+ * An allow-list entry matches the hostname case-insensitively, either exactly or as a
119
+ * dot suffix when it begins with `.` (`.internal` matches `a.b.internal`).
120
+ */
121
+ function hostExempt(host, policy) {
122
+ const h = host.toLowerCase().replace(/^\[|\]$/g, '');
123
+ return policy.allowHosts.some((entry) => {
124
+ const e = entry.toLowerCase();
125
+ return e.startsWith('.') ? h === e.slice(1) || h.endsWith(e) : h === e;
126
+ });
127
+ }
128
+ /**
129
+ * Validate a URL before it is stored, fetched, or exposed. The default policy
130
+ * (STRICT_URL_SAFETY_POLICY) requires `https` and rejects internal/private hosts; a
131
+ * trusted operator-installed adapter can pass a widened policy to permit specific
132
+ * schemes/hosts (e.g. an internal env platform on a private/VPN host). Embedded
133
+ * credentials are forbidden regardless of policy. Parsed by hand (no `URL` global) so
134
+ * this stays in the platform-agnostic core.
135
+ */
136
+ export function assertSafeEnvironmentUrl(url, label = 'URL', policy = STRICT_URL_SAFETY_POLICY) {
137
+ const invalid = () => new ValidationError(`Environment ${label} is not a valid URL: '${url}'`);
138
+ const match = url.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([^/?#]*)/);
139
+ if (!match)
140
+ throw invalid();
141
+ if (!policy.schemes.includes(match[1].toLowerCase())) {
142
+ const allowed = policy.schemes.join('/') || '(none)';
143
+ throw new ValidationError(`Environment ${label} must use ${allowed}`);
144
+ }
145
+ const authority = match[2];
146
+ if (authority.includes('@')) {
147
+ throw new ValidationError(`Environment ${label} must not contain credentials`);
148
+ }
149
+ let host;
150
+ if (authority.startsWith('[')) {
151
+ const end = authority.indexOf(']');
152
+ if (end === -1)
153
+ throw invalid();
154
+ host = authority.slice(1, end);
155
+ }
156
+ else {
157
+ host = authority.split(':')[0];
158
+ }
159
+ if (host === '')
160
+ throw invalid();
161
+ if (!hostExempt(host, policy) && isBlockedHost(host)) {
162
+ throw new ValidationError(`Environment ${label} must be a public host`);
163
+ }
164
+ }
165
+ /**
166
+ * Replace `{{ namespace.key }}` placeholders from the given scope. Unknown
167
+ * namespaces and missing keys resolve to an empty string, so a template can
168
+ * never reference arbitrary host state.
169
+ */
170
+ export function interpolateTemplate(template, scope) {
171
+ return template.replace(/\{\{\s*([a-zA-Z0-9_.]+)\s*\}\}/g, (_match, expr) => {
172
+ const dot = expr.indexOf('.');
173
+ if (dot === -1)
174
+ return '';
175
+ const ns = expr.slice(0, dot);
176
+ const key = expr.slice(dot + 1);
177
+ const bag = ns === 'input' ? scope.input : ns === 'provision' ? scope.provision : undefined;
178
+ if (!bag)
179
+ return '';
180
+ const value = bag[key];
181
+ return value === undefined ? '' : value;
182
+ });
183
+ }
184
+ /** Read a value from parsed JSON by a dot-path (e.g. `data.url`, `items.0.id`). */
185
+ export function extractByPath(json, path) {
186
+ if (!path)
187
+ return undefined;
188
+ let current = json;
189
+ for (const segment of path.split('.')) {
190
+ if (current === null || current === undefined)
191
+ return undefined;
192
+ if (typeof current !== 'object')
193
+ return undefined;
194
+ current = current[segment];
195
+ }
196
+ return current;
197
+ }
198
+ /** Extract a scalar as a string, or undefined if absent/non-scalar. */
199
+ export function extractString(json, path) {
200
+ if (!path)
201
+ return undefined;
202
+ const value = extractByPath(json, path);
203
+ if (value === null || value === undefined)
204
+ return undefined;
205
+ if (typeof value === 'string')
206
+ return value;
207
+ if (typeof value === 'number' || typeof value === 'boolean')
208
+ return String(value);
209
+ return undefined;
210
+ }
211
+ /**
212
+ * Map a provider status string onto our lifecycle states using the manifest's
213
+ * `statusMap`. Falls back to `fallback` (caller decides, e.g. 'ready' for a
214
+ * synchronous provisioner with no status polling).
215
+ */
216
+ export function mapStatus(raw, statusMap, fallback) {
217
+ if (raw !== undefined && statusMap) {
218
+ const hit = statusMap.find((m) => m.from.toLowerCase() === raw.toLowerCase());
219
+ if (hit)
220
+ return hit.to;
221
+ }
222
+ return fallback;
223
+ }
224
+ /** Project a stored record onto the wire handle, optionally with decrypted access. */
225
+ export function recordToHandle(record, access) {
226
+ return {
227
+ id: record.id,
228
+ workspaceId: record.workspaceId,
229
+ blockId: record.blockId,
230
+ executionId: record.executionId,
231
+ providerId: record.providerId,
232
+ externalId: record.externalId,
233
+ url: record.url,
234
+ status: record.status,
235
+ ...(access ? { access } : {}),
236
+ createdAt: record.createdAt,
237
+ expiresAt: record.expiresAt,
238
+ lastError: record.lastError,
239
+ };
240
+ }
241
+ /** Coerce an extracted expiry (epoch-ms number, numeric string, or ISO) to ms. */
242
+ export function coerceExpiresAt(value) {
243
+ if (value === null || value === undefined)
244
+ return null;
245
+ if (typeof value === 'number' && Number.isFinite(value))
246
+ return value;
247
+ if (typeof value === 'string') {
248
+ const trimmed = value.trim();
249
+ if (/^\d+$/.test(trimmed))
250
+ return Number(trimmed);
251
+ const parsed = Date.parse(trimmed);
252
+ if (!Number.isNaN(parsed))
253
+ return parsed;
254
+ }
255
+ return null;
256
+ }
257
+ //# sourceMappingURL=environments.logic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environments.logic.js","sourceRoot":"","sources":["../../../src/modules/environments/environments.logic.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import type { Clock } from '@cat-factory/kernel';
2
+ import type { GitHubClient } from '@cat-factory/kernel';
3
+ import type { GitHubInstallation, GitHubInstallationRepository } from '@cat-factory/kernel';
4
+ import type { GitHubConnection, GitHubInstallationOption } from '@cat-factory/kernel';
5
+ import type { WorkspaceRepository } from '@cat-factory/kernel';
6
+ export interface GitHubInstallationServiceDependencies {
7
+ githubClient: GitHubClient;
8
+ githubInstallationRepository: GitHubInstallationRepository;
9
+ workspaceRepository: WorkspaceRepository;
10
+ clock: Clock;
11
+ /**
12
+ * Whether cat-factory can create repos for an installation itself — true when
13
+ * its owning App is the privileged tier (ADR 0005). Surfaced on the connection
14
+ * so the UI can drop the manual "create on GitHub" step. Absent → always false.
15
+ */
16
+ canCreateRepos?: (installation: GitHubInstallation) => boolean;
17
+ /**
18
+ * Whether the installation actually granted `workflows: write` (read from the
19
+ * token's granted set). Surfaced so the UI can warn when agent pushes that touch
20
+ * `.github/workflows/*` would be rejected. Absent (or throwing) → false.
21
+ */
22
+ workflowsGranted?: (installation: GitHubInstallation) => Promise<boolean>;
23
+ }
24
+ export declare class GitHubInstallationService {
25
+ private readonly deps;
26
+ constructor(deps: GitHubInstallationServiceDependencies);
27
+ /**
28
+ * Bind a GitHub App installation to the workspace's account (idempotent on
29
+ * re-install). Once bound, every workspace in that account shares it.
30
+ */
31
+ connect(workspaceId: string, installationId: number): Promise<GitHubConnection>;
32
+ /** Whether the privileged App tier can create repos for this installation (ADR 0005). */
33
+ private canCreate;
34
+ /**
35
+ * Whether the installation granted `workflows: write`. Best-effort: any failure
36
+ * reading the granted set (token mint error, integration quirk) resolves to
37
+ * false rather than blocking the connection — the warning is advisory.
38
+ */
39
+ private canWorkflows;
40
+ /**
41
+ * Discover the App's installations so the connect UI can offer a pick instead
42
+ * of a manually typed installation id. Each is annotated with whether it's
43
+ * already bound — to THIS workspace, to ANOTHER (so connecting would be
44
+ * rejected by {@link connect}), or to NONE (free to connect). The `connected`
45
+ * computation mirrors that guard: a binding to another workspace counts as
46
+ * taken even when soft-deleted (the installation still lives on GitHub), while
47
+ * a soft-deleted binding to THIS workspace is re-connectable, so reported NONE.
48
+ */
49
+ listAvailableInstallations(workspaceId: string): Promise<GitHubInstallationOption[]>;
50
+ /**
51
+ * Resolve the workspace an installation is already bound to, or null if it's
52
+ * unknown (or tombstoned). Used by the setup callback to recover from GitHub's
53
+ * *stateless* redirects: saving a repo-access change from the App's
54
+ * installation settings page redirects back with `setup_action=update` but no
55
+ * signed `state`, so there's no workspace id to bind. We can only act on an
56
+ * installation that's ALREADY bound — binding a NEW one still requires a state.
57
+ */
58
+ resolveBoundWorkspace(installationId: number): Promise<string | null>;
59
+ /** The workspace's current connection, or null if not connected. */
60
+ getConnection(workspaceId: string): Promise<GitHubConnection | null>;
61
+ /** Resolve the live installation for a workspace, or throw if not connected. */
62
+ requireInstallation(workspaceId: string): Promise<GitHubInstallation>;
63
+ /** Disconnect a workspace from GitHub (tombstones the binding). */
64
+ disconnect(workspaceId: string): Promise<void>;
65
+ }
66
+ //# sourceMappingURL=GitHubInstallationService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GitHubInstallationService.d.ts","sourceRoot":"","sources":["../../../src/modules/github/GitHubInstallationService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,4BAA4B,EAAE,MAAM,qBAAqB,CAAA;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAA;AAGrF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAU9D,MAAM,WAAW,qCAAqC;IACpD,YAAY,EAAE,YAAY,CAAA;IAC1B,4BAA4B,EAAE,4BAA4B,CAAA;IAC1D,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,KAAK,EAAE,KAAK,CAAA;IACZ;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,OAAO,CAAA;IAC9D;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,kBAAkB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;CAC1E;AAiBD,qBAAa,yBAAyB;IACxB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,qCAAqC,EAAI;IAE5E;;;OAGG;IACG,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA6CpF;IAED,yFAAyF;IACzF,OAAO,CAAC,SAAS;IAIjB;;;;OAIG;YACW,YAAY;IAS1B;;;;;;;;OAQG;IACG,0BAA0B,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAuBzF;IAED;;;;;;;OAOG;IACG,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAK1E;IAED,oEAAoE;IAC9D,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAQzE;IAED,gFAAgF;IAC1E,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAM1E;IAED,mEAAmE;IAC7D,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnD;CACF"}
@@ -0,0 +1,143 @@
1
+ import { ConflictError } from '@cat-factory/kernel';
2
+ import { requireWorkspace } from '@cat-factory/kernel';
3
+ function toConnection(installation, canCreateRepos, canManageWorkflows) {
4
+ return {
5
+ installationId: installation.installationId,
6
+ accountLogin: installation.accountLogin,
7
+ targetType: installation.targetType,
8
+ connectedAt: installation.createdAt,
9
+ canCreateRepos,
10
+ canManageWorkflows,
11
+ };
12
+ }
13
+ export class GitHubInstallationService {
14
+ deps;
15
+ constructor(deps) {
16
+ this.deps = deps;
17
+ }
18
+ /**
19
+ * Bind a GitHub App installation to the workspace's account (idempotent on
20
+ * re-install). Once bound, every workspace in that account shares it.
21
+ */
22
+ async connect(workspaceId, installationId) {
23
+ await requireWorkspace(this.deps.workspaceRepository, workspaceId);
24
+ const accountId = (await this.deps.workspaceRepository.accountOf(workspaceId)) ?? null;
25
+ // Guard against binding an installation that already belongs to a DIFFERENT
26
+ // account. We reject regardless of the other binding's `deletedAt`: a
27
+ // previously disconnected/suspended installation is still installed on GitHub
28
+ // (its tokens still mint), so letting another tenant claim it would be an
29
+ // account-takeover primitive. Sharing WITHIN the same account is fine — that's
30
+ // the whole point — and the auth-disabled path (both accounts null) is
31
+ // unrestricted, exactly like the rest of the dev path.
32
+ const existing = await this.deps.githubInstallationRepository.getByInstallationId(installationId);
33
+ if (existing &&
34
+ existing.accountId !== null &&
35
+ accountId !== null &&
36
+ existing.accountId !== accountId) {
37
+ throw new ConflictError(`Installation ${installationId} is already connected to another account`);
38
+ }
39
+ const meta = await this.deps.githubClient.getInstallation(installationId);
40
+ const installation = {
41
+ installationId,
42
+ workspaceId,
43
+ accountId,
44
+ accountLogin: meta.accountLogin,
45
+ targetType: meta.targetType,
46
+ // The App that owns this installation (probed at connect), so every later
47
+ // token mint routes to the right App's key (ADR 0005).
48
+ appId: meta.appId,
49
+ cachedToken: null,
50
+ tokenExpiresAt: null,
51
+ createdAt: existing?.createdAt ?? this.deps.clock.now(),
52
+ deletedAt: null,
53
+ };
54
+ await this.deps.githubInstallationRepository.upsert(installation);
55
+ return toConnection(installation, this.canCreate(installation), await this.canWorkflows(installation));
56
+ }
57
+ /** Whether the privileged App tier can create repos for this installation (ADR 0005). */
58
+ canCreate(installation) {
59
+ return this.deps.canCreateRepos?.(installation) ?? false;
60
+ }
61
+ /**
62
+ * Whether the installation granted `workflows: write`. Best-effort: any failure
63
+ * reading the granted set (token mint error, integration quirk) resolves to
64
+ * false rather than blocking the connection — the warning is advisory.
65
+ */
66
+ async canWorkflows(installation) {
67
+ if (!this.deps.workflowsGranted)
68
+ return false;
69
+ try {
70
+ return await this.deps.workflowsGranted(installation);
71
+ }
72
+ catch {
73
+ return false;
74
+ }
75
+ }
76
+ /**
77
+ * Discover the App's installations so the connect UI can offer a pick instead
78
+ * of a manually typed installation id. Each is annotated with whether it's
79
+ * already bound — to THIS workspace, to ANOTHER (so connecting would be
80
+ * rejected by {@link connect}), or to NONE (free to connect). The `connected`
81
+ * computation mirrors that guard: a binding to another workspace counts as
82
+ * taken even when soft-deleted (the installation still lives on GitHub), while
83
+ * a soft-deleted binding to THIS workspace is re-connectable, so reported NONE.
84
+ */
85
+ async listAvailableInstallations(workspaceId) {
86
+ await requireWorkspace(this.deps.workspaceRepository, workspaceId);
87
+ const accountId = (await this.deps.workspaceRepository.accountOf(workspaceId)) ?? null;
88
+ const installations = await this.deps.githubClient.listInstallations();
89
+ return Promise.all(installations.map(async (i) => {
90
+ const existing = await this.deps.githubInstallationRepository.getByInstallationId(i.installationId);
91
+ let connected = 'none';
92
+ if (existing) {
93
+ const sameAccount = existing.accountId !== null && accountId !== null && existing.accountId === accountId;
94
+ if (existing.workspaceId === workspaceId || sameAccount) {
95
+ // Already available to this workspace (directly, or shared via account).
96
+ if (!existing.deletedAt || sameAccount)
97
+ connected = 'this';
98
+ }
99
+ else {
100
+ connected = 'other';
101
+ }
102
+ }
103
+ return { ...i, connected };
104
+ }));
105
+ }
106
+ /**
107
+ * Resolve the workspace an installation is already bound to, or null if it's
108
+ * unknown (or tombstoned). Used by the setup callback to recover from GitHub's
109
+ * *stateless* redirects: saving a repo-access change from the App's
110
+ * installation settings page redirects back with `setup_action=update` but no
111
+ * signed `state`, so there's no workspace id to bind. We can only act on an
112
+ * installation that's ALREADY bound — binding a NEW one still requires a state.
113
+ */
114
+ async resolveBoundWorkspace(installationId) {
115
+ const existing = await this.deps.githubInstallationRepository.getByInstallationId(installationId);
116
+ if (!existing || existing.deletedAt)
117
+ return null;
118
+ return existing.workspaceId;
119
+ }
120
+ /** The workspace's current connection, or null if not connected. */
121
+ async getConnection(workspaceId) {
122
+ const installation = await this.deps.githubInstallationRepository.getByWorkspace(workspaceId);
123
+ if (!installation || installation.deletedAt)
124
+ return null;
125
+ return toConnection(installation, this.canCreate(installation), await this.canWorkflows(installation));
126
+ }
127
+ /** Resolve the live installation for a workspace, or throw if not connected. */
128
+ async requireInstallation(workspaceId) {
129
+ const installation = await this.deps.githubInstallationRepository.getByWorkspace(workspaceId);
130
+ if (!installation || installation.deletedAt) {
131
+ throw new ConflictError(`Workspace '${workspaceId}' is not connected to GitHub`);
132
+ }
133
+ return installation;
134
+ }
135
+ /** Disconnect a workspace from GitHub (tombstones the binding). */
136
+ async disconnect(workspaceId) {
137
+ const installation = await this.deps.githubInstallationRepository.getByWorkspace(workspaceId);
138
+ if (!installation || installation.deletedAt)
139
+ return;
140
+ await this.deps.githubInstallationRepository.softDelete(installation.installationId, this.deps.clock.now());
141
+ }
142
+ }
143
+ //# sourceMappingURL=GitHubInstallationService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GitHubInstallationService.js","sourceRoot":"","sources":["../../../src/modules/github/GitHubInstallationService.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Clock } from '@cat-factory/kernel';
2
+ import type { GitHubClient } from '@cat-factory/kernel';
3
+ import type { BranchProjectionRepository, IssueProjectionRepository, PullRequestProjectionRepository, RepoProjectionRepository } from '@cat-factory/kernel';
4
+ import type { CommitFilesInput, GitHubBranch, GitHubIssue, GitHubPullRequest, GitHubRepo, MergePullRequestInput, OpenPullRequestInput } from '@cat-factory/kernel';
5
+ export interface GitHubServiceDependencies {
6
+ githubClient: GitHubClient;
7
+ repoProjectionRepository: RepoProjectionRepository;
8
+ branchProjectionRepository: BranchProjectionRepository;
9
+ pullRequestProjectionRepository: PullRequestProjectionRepository;
10
+ issueProjectionRepository: IssueProjectionRepository;
11
+ clock: Clock;
12
+ }
13
+ export declare class GitHubService {
14
+ private readonly deps;
15
+ constructor(deps: GitHubServiceDependencies);
16
+ listRepos(workspaceId: string): Promise<GitHubRepo[]>;
17
+ listBranches(workspaceId: string, repoGithubId: number): Promise<GitHubBranch[]>;
18
+ listPullRequests(workspaceId: string): Promise<GitHubPullRequest[]>;
19
+ listIssues(workspaceId: string): Promise<GitHubIssue[]>;
20
+ createBranch(workspaceId: string, repoGithubId: number, name: string, fromSha: string): Promise<GitHubBranch>;
21
+ commitFiles(workspaceId: string, repoGithubId: number, input: CommitFilesInput): Promise<{
22
+ sha: string;
23
+ }>;
24
+ openPullRequest(workspaceId: string, repoGithubId: number, input: OpenPullRequestInput): Promise<GitHubPullRequest>;
25
+ mergePullRequest(workspaceId: string, repoGithubId: number, number: number, input?: MergePullRequestInput): Promise<void>;
26
+ comment(workspaceId: string, repoGithubId: number, issueOrPrNumber: number, body: string): Promise<void>;
27
+ private resolve;
28
+ }
29
+ //# sourceMappingURL=GitHubService.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GitHubService.d.ts","sourceRoot":"","sources":["../../../src/modules/github/GitHubService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAiB,MAAM,qBAAqB,CAAA;AACtE,OAAO,KAAK,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,+BAA+B,EAC/B,wBAAwB,EACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,qBAAqB,CAAA;AAW5B,MAAM,WAAW,yBAAyB;IACxC,YAAY,EAAE,YAAY,CAAA;IAC1B,wBAAwB,EAAE,wBAAwB,CAAA;IAClD,0BAA0B,EAAE,0BAA0B,CAAA;IACtD,+BAA+B,EAAE,+BAA+B,CAAA;IAChE,yBAAyB,EAAE,yBAAyB,CAAA;IACpD,KAAK,EAAE,KAAK,CAAA;CACb;AAQD,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,yBAAyB,EAAI;IAIhE,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAEpD;IAED,YAAY,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAE/E;IAED,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAElE;IAED,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAEtD;IAIK,YAAY,CAChB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAYvB;IAEK,WAAW,CACf,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,gBAAgB,GACtB,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAG1B;IAEK,eAAe,CACnB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CAK5B;IAEK,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAGf;IAEK,OAAO,CACX,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAGf;YAEa,OAAO;CAYtB"}
@@ -0,0 +1,61 @@
1
+ import { assertFound } from '@cat-factory/kernel';
2
+ export class GitHubService {
3
+ deps;
4
+ constructor(deps) {
5
+ this.deps = deps;
6
+ }
7
+ // ---- projection reads ---------------------------------------------------
8
+ listRepos(workspaceId) {
9
+ return this.deps.repoProjectionRepository.list(workspaceId);
10
+ }
11
+ listBranches(workspaceId, repoGithubId) {
12
+ return this.deps.branchProjectionRepository.listByRepo(workspaceId, repoGithubId);
13
+ }
14
+ listPullRequests(workspaceId) {
15
+ return this.deps.pullRequestProjectionRepository.listByWorkspace(workspaceId);
16
+ }
17
+ listIssues(workspaceId) {
18
+ return this.deps.issueProjectionRepository.listByWorkspace(workspaceId);
19
+ }
20
+ // ---- writes -------------------------------------------------------------
21
+ async createBranch(workspaceId, repoGithubId, name, fromSha) {
22
+ const { installationId, ref } = await this.resolve(workspaceId, repoGithubId);
23
+ await this.deps.githubClient.createBranch(installationId, ref, name, fromSha);
24
+ const branch = {
25
+ repoGithubId,
26
+ name,
27
+ headSha: fromSha,
28
+ protected: false,
29
+ syncedAt: this.deps.clock.now(),
30
+ };
31
+ await this.deps.branchProjectionRepository.upsertMany(workspaceId, [branch]);
32
+ return branch;
33
+ }
34
+ async commitFiles(workspaceId, repoGithubId, input) {
35
+ const { installationId, ref } = await this.resolve(workspaceId, repoGithubId);
36
+ return this.deps.githubClient.commitFiles(installationId, ref, input);
37
+ }
38
+ async openPullRequest(workspaceId, repoGithubId, input) {
39
+ const { installationId, ref } = await this.resolve(workspaceId, repoGithubId);
40
+ const pr = await this.deps.githubClient.openPullRequest(installationId, ref, input);
41
+ await this.deps.pullRequestProjectionRepository.upsertMany(workspaceId, [pr]);
42
+ return pr;
43
+ }
44
+ async mergePullRequest(workspaceId, repoGithubId, number, input) {
45
+ const { installationId, ref } = await this.resolve(workspaceId, repoGithubId);
46
+ await this.deps.githubClient.mergePullRequest(installationId, ref, number, input);
47
+ }
48
+ async comment(workspaceId, repoGithubId, issueOrPrNumber, body) {
49
+ const { installationId, ref } = await this.resolve(workspaceId, repoGithubId);
50
+ await this.deps.githubClient.comment(installationId, ref, issueOrPrNumber, body);
51
+ }
52
+ async resolve(workspaceId, repoGithubId) {
53
+ const repo = assertFound(await this.deps.repoProjectionRepository.get(workspaceId, repoGithubId), 'GitHubRepo', String(repoGithubId));
54
+ return {
55
+ repo,
56
+ installationId: repo.installationId,
57
+ ref: { owner: repo.owner, repo: repo.name },
58
+ };
59
+ }
60
+ }
61
+ //# sourceMappingURL=GitHubService.js.map