@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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/datadog/DatadogClient.d.ts +54 -0
- package/dist/modules/datadog/DatadogClient.d.ts.map +1 -0
- package/dist/modules/datadog/DatadogClient.js +132 -0
- package/dist/modules/datadog/DatadogClient.js.map +1 -0
- package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts +30 -0
- package/dist/modules/datadog/DatadogReleaseHealthProvider.d.ts.map +1 -0
- package/dist/modules/datadog/DatadogReleaseHealthProvider.js +101 -0
- package/dist/modules/datadog/DatadogReleaseHealthProvider.js.map +1 -0
- package/dist/modules/datadog/datadog.logic.d.ts +37 -0
- package/dist/modules/datadog/datadog.logic.d.ts.map +1 -0
- package/dist/modules/datadog/datadog.logic.js +90 -0
- package/dist/modules/datadog/datadog.logic.js.map +1 -0
- package/dist/modules/documents/ConfluenceProvider.d.ts +29 -0
- package/dist/modules/documents/ConfluenceProvider.d.ts.map +1 -0
- package/dist/modules/documents/ConfluenceProvider.js +180 -0
- package/dist/modules/documents/ConfluenceProvider.js.map +1 -0
- package/dist/modules/documents/DocumentConnectionService.d.ts +30 -0
- package/dist/modules/documents/DocumentConnectionService.d.ts.map +1 -0
- package/dist/modules/documents/DocumentConnectionService.js +69 -0
- package/dist/modules/documents/DocumentConnectionService.js.map +1 -0
- package/dist/modules/documents/DocumentImportService.d.ts +34 -0
- package/dist/modules/documents/DocumentImportService.d.ts.map +1 -0
- package/dist/modules/documents/DocumentImportService.js +83 -0
- package/dist/modules/documents/DocumentImportService.js.map +1 -0
- package/dist/modules/documents/DocumentLinkService.d.ts +31 -0
- package/dist/modules/documents/DocumentLinkService.d.ts.map +1 -0
- package/dist/modules/documents/DocumentLinkService.js +75 -0
- package/dist/modules/documents/DocumentLinkService.js.map +1 -0
- package/dist/modules/documents/DocumentPlannerService.d.ts +23 -0
- package/dist/modules/documents/DocumentPlannerService.d.ts.map +1 -0
- package/dist/modules/documents/DocumentPlannerService.js +96 -0
- package/dist/modules/documents/DocumentPlannerService.js.map +1 -0
- package/dist/modules/documents/GitHubDocsProvider.d.ts +42 -0
- package/dist/modules/documents/GitHubDocsProvider.d.ts.map +1 -0
- package/dist/modules/documents/GitHubDocsProvider.js +86 -0
- package/dist/modules/documents/GitHubDocsProvider.js.map +1 -0
- package/dist/modules/documents/NotionProvider.d.ts +32 -0
- package/dist/modules/documents/NotionProvider.d.ts.map +1 -0
- package/dist/modules/documents/NotionProvider.js +221 -0
- package/dist/modules/documents/NotionProvider.js.map +1 -0
- package/dist/modules/documents/confluence.logic.d.ts +37 -0
- package/dist/modules/documents/confluence.logic.d.ts.map +1 -0
- package/dist/modules/documents/confluence.logic.js +133 -0
- package/dist/modules/documents/confluence.logic.js.map +1 -0
- package/dist/modules/documents/documents.logic.d.ts +22 -0
- package/dist/modules/documents/documents.logic.d.ts.map +1 -0
- package/dist/modules/documents/documents.logic.js +138 -0
- package/dist/modules/documents/documents.logic.js.map +1 -0
- package/dist/modules/documents/github-docs.logic.d.ts +52 -0
- package/dist/modules/documents/github-docs.logic.d.ts.map +1 -0
- package/dist/modules/documents/github-docs.logic.js +94 -0
- package/dist/modules/documents/github-docs.logic.js.map +1 -0
- package/dist/modules/documents/notion.logic.d.ts +31 -0
- package/dist/modules/documents/notion.logic.d.ts.map +1 -0
- package/dist/modules/documents/notion.logic.js +142 -0
- package/dist/modules/documents/notion.logic.js.map +1 -0
- package/dist/modules/email/EmailConnectionService.d.ts +34 -0
- package/dist/modules/email/EmailConnectionService.d.ts.map +1 -0
- package/dist/modules/email/EmailConnectionService.js +82 -0
- package/dist/modules/email/EmailConnectionService.js.map +1 -0
- package/dist/modules/email/adapters.d.ts +39 -0
- package/dist/modules/email/adapters.d.ts.map +1 -0
- package/dist/modules/email/adapters.js +79 -0
- package/dist/modules/email/adapters.js.map +1 -0
- package/dist/modules/environments/EnvironmentConnectionService.d.ts +42 -0
- package/dist/modules/environments/EnvironmentConnectionService.d.ts.map +1 -0
- package/dist/modules/environments/EnvironmentConnectionService.js +120 -0
- package/dist/modules/environments/EnvironmentConnectionService.js.map +1 -0
- package/dist/modules/environments/EnvironmentProvisioningService.d.ts +56 -0
- package/dist/modules/environments/EnvironmentProvisioningService.d.ts.map +1 -0
- package/dist/modules/environments/EnvironmentProvisioningService.js +153 -0
- package/dist/modules/environments/EnvironmentProvisioningService.js.map +1 -0
- package/dist/modules/environments/EnvironmentTeardownService.d.ts +24 -0
- package/dist/modules/environments/EnvironmentTeardownService.d.ts.map +1 -0
- package/dist/modules/environments/EnvironmentTeardownService.js +54 -0
- package/dist/modules/environments/EnvironmentTeardownService.js.map +1 -0
- package/dist/modules/environments/HttpEnvironmentProvider.d.ts +30 -0
- package/dist/modules/environments/HttpEnvironmentProvider.d.ts.map +1 -0
- package/dist/modules/environments/HttpEnvironmentProvider.js +316 -0
- package/dist/modules/environments/HttpEnvironmentProvider.js.map +1 -0
- package/dist/modules/environments/environments.logic.d.ts +50 -0
- package/dist/modules/environments/environments.logic.d.ts.map +1 -0
- package/dist/modules/environments/environments.logic.js +257 -0
- package/dist/modules/environments/environments.logic.js.map +1 -0
- package/dist/modules/github/GitHubInstallationService.d.ts +66 -0
- package/dist/modules/github/GitHubInstallationService.d.ts.map +1 -0
- package/dist/modules/github/GitHubInstallationService.js +143 -0
- package/dist/modules/github/GitHubInstallationService.js.map +1 -0
- package/dist/modules/github/GitHubService.d.ts +29 -0
- package/dist/modules/github/GitHubService.d.ts.map +1 -0
- package/dist/modules/github/GitHubService.js +61 -0
- package/dist/modules/github/GitHubService.js.map +1 -0
- package/dist/modules/github/GitHubSyncService.d.ts +97 -0
- package/dist/modules/github/GitHubSyncService.d.ts.map +1 -0
- package/dist/modules/github/GitHubSyncService.js +241 -0
- package/dist/modules/github/GitHubSyncService.js.map +1 -0
- package/dist/modules/github/RepoProvisioningService.d.ts +26 -0
- package/dist/modules/github/RepoProvisioningService.d.ts.map +1 -0
- package/dist/modules/github/RepoProvisioningService.js +36 -0
- package/dist/modules/github/RepoProvisioningService.js.map +1 -0
- package/dist/modules/github/WebhookService.d.ts +28 -0
- package/dist/modules/github/WebhookService.d.ts.map +1 -0
- package/dist/modules/github/WebhookService.js +156 -0
- package/dist/modules/github/WebhookService.js.map +1 -0
- package/dist/modules/github/projection.logic.d.ts +95 -0
- package/dist/modules/github/projection.logic.d.ts.map +1 -0
- package/dist/modules/github/projection.logic.js +94 -0
- package/dist/modules/github/projection.logic.js.map +1 -0
- package/dist/modules/github/provisioning.logic.d.ts +11 -0
- package/dist/modules/github/provisioning.logic.d.ts.map +1 -0
- package/dist/modules/github/provisioning.logic.js +18 -0
- package/dist/modules/github/provisioning.logic.js.map +1 -0
- package/dist/modules/incident/incident.logic.d.ts +16 -0
- package/dist/modules/incident/incident.logic.d.ts.map +1 -0
- package/dist/modules/incident/incident.logic.js +23 -0
- package/dist/modules/incident/incident.logic.js.map +1 -0
- package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts +26 -0
- package/dist/modules/incidentio/IncidentIoEnrichmentProvider.d.ts.map +1 -0
- package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js +84 -0
- package/dist/modules/incidentio/IncidentIoEnrichmentProvider.js.map +1 -0
- package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts +27 -0
- package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.d.ts.map +1 -0
- package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js +65 -0
- package/dist/modules/pagerduty/PagerDutyEnrichmentProvider.js.map +1 -0
- package/dist/modules/providers/ApiKeyService.d.ts +73 -0
- package/dist/modules/providers/ApiKeyService.d.ts.map +1 -0
- package/dist/modules/providers/ApiKeyService.js +122 -0
- package/dist/modules/providers/ApiKeyService.js.map +1 -0
- package/dist/modules/providers/LocalModelEndpointService.d.ts +52 -0
- package/dist/modules/providers/LocalModelEndpointService.d.ts.map +1 -0
- package/dist/modules/providers/LocalModelEndpointService.js +131 -0
- package/dist/modules/providers/LocalModelEndpointService.js.map +1 -0
- package/dist/modules/providers/PersonalSubscriptionService.d.ts +94 -0
- package/dist/modules/providers/PersonalSubscriptionService.d.ts.map +1 -0
- package/dist/modules/providers/PersonalSubscriptionService.js +218 -0
- package/dist/modules/providers/PersonalSubscriptionService.js.map +1 -0
- package/dist/modules/providers/ProviderSubscriptionService.d.ts +75 -0
- package/dist/modules/providers/ProviderSubscriptionService.d.ts.map +1 -0
- package/dist/modules/providers/ProviderSubscriptionService.js +130 -0
- package/dist/modules/providers/ProviderSubscriptionService.js.map +1 -0
- package/dist/modules/providers/localModelUrl.d.ts +7 -0
- package/dist/modules/providers/localModelUrl.d.ts.map +1 -0
- package/dist/modules/providers/localModelUrl.js +67 -0
- package/dist/modules/providers/localModelUrl.js.map +1 -0
- package/dist/modules/providers/providers.logic.d.ts +23 -0
- package/dist/modules/providers/providers.logic.d.ts.map +1 -0
- package/dist/modules/providers/providers.logic.js +46 -0
- package/dist/modules/providers/providers.logic.js.map +1 -0
- package/dist/modules/runners/HttpRunnerPoolProvider.d.ts +51 -0
- package/dist/modules/runners/HttpRunnerPoolProvider.d.ts.map +1 -0
- package/dist/modules/runners/HttpRunnerPoolProvider.js +304 -0
- package/dist/modules/runners/HttpRunnerPoolProvider.js.map +1 -0
- package/dist/modules/runners/RunnerPoolConnectionService.d.ts +47 -0
- package/dist/modules/runners/RunnerPoolConnectionService.d.ts.map +1 -0
- package/dist/modules/runners/RunnerPoolConnectionService.js +98 -0
- package/dist/modules/runners/RunnerPoolConnectionService.js.map +1 -0
- package/dist/modules/runners/RunnerPoolTransport.d.ts +11 -0
- package/dist/modules/runners/RunnerPoolTransport.d.ts.map +1 -0
- package/dist/modules/runners/RunnerPoolTransport.js +61 -0
- package/dist/modules/runners/RunnerPoolTransport.js.map +1 -0
- package/dist/modules/runners/runners.logic.d.ts +16 -0
- package/dist/modules/runners/runners.logic.d.ts.map +1 -0
- package/dist/modules/runners/runners.logic.js +52 -0
- package/dist/modules/runners/runners.logic.js.map +1 -0
- package/dist/modules/slack/SlackApiClient.d.ts +67 -0
- package/dist/modules/slack/SlackApiClient.d.ts.map +1 -0
- package/dist/modules/slack/SlackApiClient.js +132 -0
- package/dist/modules/slack/SlackApiClient.js.map +1 -0
- package/dist/modules/slack/SlackConnectionService.d.ts +41 -0
- package/dist/modules/slack/SlackConnectionService.d.ts.map +1 -0
- package/dist/modules/slack/SlackConnectionService.js +136 -0
- package/dist/modules/slack/SlackConnectionService.js.map +1 -0
- package/dist/modules/slack/SlackMemberMappingService.d.ts +17 -0
- package/dist/modules/slack/SlackMemberMappingService.d.ts.map +1 -0
- package/dist/modules/slack/SlackMemberMappingService.js +28 -0
- package/dist/modules/slack/SlackMemberMappingService.js.map +1 -0
- package/dist/modules/slack/SlackNotificationChannel.d.ts +45 -0
- package/dist/modules/slack/SlackNotificationChannel.d.ts.map +1 -0
- package/dist/modules/slack/SlackNotificationChannel.js +84 -0
- package/dist/modules/slack/SlackNotificationChannel.js.map +1 -0
- package/dist/modules/slack/SlackSettingsService.d.ts +16 -0
- package/dist/modules/slack/SlackSettingsService.d.ts.map +1 -0
- package/dist/modules/slack/SlackSettingsService.js +41 -0
- package/dist/modules/slack/SlackSettingsService.js.map +1 -0
- package/dist/modules/slack/slack.logic.d.ts +55 -0
- package/dist/modules/slack/slack.logic.d.ts.map +1 -0
- package/dist/modules/slack/slack.logic.js +149 -0
- package/dist/modules/slack/slack.logic.js.map +1 -0
- package/dist/modules/tasks/GitHubIssuesProvider.d.ts +50 -0
- package/dist/modules/tasks/GitHubIssuesProvider.d.ts.map +1 -0
- package/dist/modules/tasks/GitHubIssuesProvider.js +92 -0
- package/dist/modules/tasks/GitHubIssuesProvider.js.map +1 -0
- package/dist/modules/tasks/JiraProvider.d.ts +29 -0
- package/dist/modules/tasks/JiraProvider.d.ts.map +1 -0
- package/dist/modules/tasks/JiraProvider.js +114 -0
- package/dist/modules/tasks/JiraProvider.js.map +1 -0
- package/dist/modules/tasks/TaskConnectionService.d.ts +30 -0
- package/dist/modules/tasks/TaskConnectionService.d.ts.map +1 -0
- package/dist/modules/tasks/TaskConnectionService.js +69 -0
- package/dist/modules/tasks/TaskConnectionService.js.map +1 -0
- package/dist/modules/tasks/TaskImportService.d.ts +34 -0
- package/dist/modules/tasks/TaskImportService.d.ts.map +1 -0
- package/dist/modules/tasks/TaskImportService.js +96 -0
- package/dist/modules/tasks/TaskImportService.js.map +1 -0
- package/dist/modules/tasks/TaskLinkService.d.ts +30 -0
- package/dist/modules/tasks/TaskLinkService.d.ts.map +1 -0
- package/dist/modules/tasks/TaskLinkService.js +56 -0
- package/dist/modules/tasks/TaskLinkService.js.map +1 -0
- package/dist/modules/tasks/github-issues.logic.d.ts +35 -0
- package/dist/modules/tasks/github-issues.logic.d.ts.map +1 -0
- package/dist/modules/tasks/github-issues.logic.js +67 -0
- package/dist/modules/tasks/github-issues.logic.js.map +1 -0
- package/dist/modules/tasks/jira.logic.d.ts +28 -0
- package/dist/modules/tasks/jira.logic.d.ts.map +1 -0
- package/dist/modules/tasks/jira.logic.js +151 -0
- package/dist/modules/tasks/jira.logic.js.map +1 -0
- package/dist/modules/tasks/tasks.logic.d.ts +12 -0
- package/dist/modules/tasks/tasks.logic.d.ts.map +1 -0
- package/dist/modules/tasks/tasks.logic.js +17 -0
- package/dist/modules/tasks/tasks.logic.js.map +1 -0
- package/dist/modules/tracker/TicketTrackerService.d.ts +45 -0
- package/dist/modules/tracker/TicketTrackerService.d.ts.map +1 -0
- package/dist/modules/tracker/TicketTrackerService.js +52 -0
- package/dist/modules/tracker/TicketTrackerService.js.map +1 -0
- package/dist/modules/tracker/base64.d.ts +2 -0
- package/dist/modules/tracker/base64.d.ts.map +1 -0
- package/dist/modules/tracker/base64.js +18 -0
- package/dist/modules/tracker/base64.js.map +1 -0
- package/dist/modules/tracker/github.create.logic.d.ts +16 -0
- package/dist/modules/tracker/github.create.logic.d.ts.map +1 -0
- package/dist/modules/tracker/github.create.logic.js +25 -0
- package/dist/modules/tracker/github.create.logic.js.map +1 -0
- package/dist/modules/tracker/jira.create.logic.d.ts +31 -0
- package/dist/modules/tracker/jira.create.logic.d.ts.map +1 -0
- package/dist/modules/tracker/jira.create.logic.js +59 -0
- package/dist/modules/tracker/jira.create.logic.js.map +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Notion-specific pure logic, kept out of the worker so it is unit-testable
|
|
2
|
+
// without a live workspace: parsing/normalizing a page id out of user input, and
|
|
3
|
+
// converting Notion block JSON into the lightweight Markdown the generic planner
|
|
4
|
+
// consumes. The fetch itself (a single integration token, no per-site base URL —
|
|
5
|
+
// so no SSRF surface) lives in the worker's NotionProvider.
|
|
6
|
+
/** What the connect UI renders, and which credentials the provider needs. */
|
|
7
|
+
export const NOTION_DESCRIPTOR = {
|
|
8
|
+
source: 'notion',
|
|
9
|
+
label: 'Notion',
|
|
10
|
+
icon: 'i-lucide-notebook-text',
|
|
11
|
+
credentialFields: [
|
|
12
|
+
{
|
|
13
|
+
key: 'apiToken',
|
|
14
|
+
label: 'Internal integration token',
|
|
15
|
+
secret: true,
|
|
16
|
+
placeholder: 'ntn_… or secret_…',
|
|
17
|
+
help: 'Create an internal integration at notion.so/my-integrations, then share each page with it',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
refLabel: 'Page URL or ID',
|
|
21
|
+
refPlaceholder: 'https://notion.so/Title-abc123… or the page id',
|
|
22
|
+
searchable: true,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Map a Notion `/v1/search` response into lean hits, keeping only pages (the API
|
|
26
|
+
* also returns databases). The page title is read from its `properties` exactly
|
|
27
|
+
* as the single-page fetch does, so list + import titles agree.
|
|
28
|
+
*/
|
|
29
|
+
export function parseNotionSearchResults(json) {
|
|
30
|
+
const body = (json ?? {});
|
|
31
|
+
const out = [];
|
|
32
|
+
for (const row of Array.isArray(body.results) ? body.results : []) {
|
|
33
|
+
if (row.object && row.object !== 'page')
|
|
34
|
+
continue;
|
|
35
|
+
const id = row.id;
|
|
36
|
+
if (!id)
|
|
37
|
+
continue;
|
|
38
|
+
out.push({
|
|
39
|
+
source: 'notion',
|
|
40
|
+
externalId: formatNotionId(id.replace(/-/g, '')),
|
|
41
|
+
title: notionPageTitle(row.properties),
|
|
42
|
+
url: row.url ?? `https://www.notion.so/${id.replace(/-/g, '')}`,
|
|
43
|
+
excerpt: '',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
/** Format 32 hex chars as a canonical dashed Notion/UUID id. */
|
|
49
|
+
export function formatNotionId(hex32) {
|
|
50
|
+
const h = hex32.toLowerCase();
|
|
51
|
+
return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20, 32)}`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a Notion page id from raw user input: a bare id (dashed UUID or 32 hex
|
|
55
|
+
* chars), or any Notion URL whose last path segment ends in the id. Returns the
|
|
56
|
+
* canonical dashed id, or null if none is found.
|
|
57
|
+
*/
|
|
58
|
+
export function parseNotionRef(input) {
|
|
59
|
+
// Drop query/hash, then scan for a UUID-shaped (dashes optional) run; the page
|
|
60
|
+
// id is the last such run (workspace URLs may carry a leading slug or id).
|
|
61
|
+
const cleaned = input.trim().split(/[?#]/)[0];
|
|
62
|
+
const matches = cleaned.match(/[0-9a-fA-F]{8}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{4}-?[0-9a-fA-F]{12}/g);
|
|
63
|
+
if (!matches || matches.length === 0)
|
|
64
|
+
return null;
|
|
65
|
+
const hex = matches[matches.length - 1].replace(/-/g, '');
|
|
66
|
+
if (hex.length !== 32)
|
|
67
|
+
return null;
|
|
68
|
+
return formatNotionId(hex);
|
|
69
|
+
}
|
|
70
|
+
function richTextOf(block) {
|
|
71
|
+
const payload = block.type
|
|
72
|
+
? block[block.type]
|
|
73
|
+
: undefined;
|
|
74
|
+
const rich = payload?.rich_text;
|
|
75
|
+
if (!Array.isArray(rich))
|
|
76
|
+
return '';
|
|
77
|
+
return rich
|
|
78
|
+
.map((r) => r.plain_text ?? '')
|
|
79
|
+
.join('')
|
|
80
|
+
.trim();
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Convert a Notion page's top-level blocks into the lightweight Markdown the
|
|
84
|
+
* generic planner/excerpt logic consumes: headings become `#`/`##`/`###`, list
|
|
85
|
+
* items / to-dos become `- `, and other text blocks become plain lines.
|
|
86
|
+
*/
|
|
87
|
+
export function notionBlocksToMarkdown(blocks) {
|
|
88
|
+
const lines = [];
|
|
89
|
+
for (const block of blocks) {
|
|
90
|
+
const text = richTextOf(block);
|
|
91
|
+
switch (block.type) {
|
|
92
|
+
case 'heading_1':
|
|
93
|
+
lines.push(`# ${text}`);
|
|
94
|
+
break;
|
|
95
|
+
case 'heading_2':
|
|
96
|
+
lines.push(`## ${text}`);
|
|
97
|
+
break;
|
|
98
|
+
case 'heading_3':
|
|
99
|
+
lines.push(`### ${text}`);
|
|
100
|
+
break;
|
|
101
|
+
case 'bulleted_list_item':
|
|
102
|
+
case 'numbered_list_item':
|
|
103
|
+
case 'to_do':
|
|
104
|
+
if (text)
|
|
105
|
+
lines.push(`- ${text}`);
|
|
106
|
+
break;
|
|
107
|
+
case 'paragraph':
|
|
108
|
+
case 'quote':
|
|
109
|
+
case 'callout':
|
|
110
|
+
case 'toggle':
|
|
111
|
+
case 'code':
|
|
112
|
+
if (text)
|
|
113
|
+
lines.push(text);
|
|
114
|
+
break;
|
|
115
|
+
default:
|
|
116
|
+
if (text)
|
|
117
|
+
lines.push(text);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return lines
|
|
121
|
+
.join('\n')
|
|
122
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
123
|
+
.trim();
|
|
124
|
+
}
|
|
125
|
+
/** Extract a page title from a Notion page object's `properties`. */
|
|
126
|
+
export function notionPageTitle(properties) {
|
|
127
|
+
if (!properties)
|
|
128
|
+
return '(untitled)';
|
|
129
|
+
for (const value of Object.values(properties)) {
|
|
130
|
+
const prop = value;
|
|
131
|
+
if (prop?.type === 'title' && Array.isArray(prop.title)) {
|
|
132
|
+
const text = prop.title
|
|
133
|
+
.map((r) => r.plain_text ?? '')
|
|
134
|
+
.join('')
|
|
135
|
+
.trim();
|
|
136
|
+
if (text)
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return '(untitled)';
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=notion.logic.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notion.logic.js","sourceRoot":"","sources":["../../../src/modules/documents/notion.logic.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Clock, EmailConnectionRepository, EmailProviderKind, EmailSender, SecretCipher } from '@cat-factory/kernel';
|
|
2
|
+
/** Safe connection metadata exposed to clients (never the API key). */
|
|
3
|
+
export interface EmailConnection {
|
|
4
|
+
provider: EmailProviderKind;
|
|
5
|
+
fromAddress: string;
|
|
6
|
+
connectedAt: number;
|
|
7
|
+
}
|
|
8
|
+
export interface EmailConnectionServiceDependencies {
|
|
9
|
+
emailConnectionRepository: EmailConnectionRepository;
|
|
10
|
+
secretCipher: SecretCipher;
|
|
11
|
+
clock: Clock;
|
|
12
|
+
}
|
|
13
|
+
export declare class EmailConnectionService {
|
|
14
|
+
private readonly deps;
|
|
15
|
+
constructor(deps: EmailConnectionServiceDependencies);
|
|
16
|
+
/** Connect an account's email sender by storing the provider + sealed API key. */
|
|
17
|
+
connect(accountId: string, input: {
|
|
18
|
+
provider: EmailProviderKind;
|
|
19
|
+
apiKey: string;
|
|
20
|
+
fromAddress: string;
|
|
21
|
+
}): Promise<EmailConnection>;
|
|
22
|
+
/** The account's current connection (safe metadata), or null. */
|
|
23
|
+
getConnection(accountId: string): Promise<EmailConnection | null>;
|
|
24
|
+
/** Disconnect the account's email sender (tombstones the binding). */
|
|
25
|
+
disconnect(accountId: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Build a ready-to-send EmailSender for an account by decrypting its API key, or
|
|
28
|
+
* null when the account has no connection. The resolver the invitation flow uses.
|
|
29
|
+
*/
|
|
30
|
+
resolveSender(accountId: string): Promise<EmailSender | null>;
|
|
31
|
+
/** Send a test email through the account's configured sender (UI "send test"). */
|
|
32
|
+
sendTest(accountId: string, to: string): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=EmailConnectionService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/email/EmailConnectionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,KAAK,EAEL,yBAAyB,EAEzB,iBAAiB,EACjB,WAAW,EACX,YAAY,EACb,MAAM,qBAAqB,CAAA;AAU5B,uEAAuE;AACvE,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,kCAAkC;IACjD,yBAAyB,EAAE,yBAAyB,CAAA;IACpD,YAAY,EAAE,YAAY,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;CACb;AAUD,qBAAa,sBAAsB;IACrB,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,kCAAkC,EAAI;IAEzE,kFAAkF;IAC5E,OAAO,CACX,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE;QAAE,QAAQ,EAAE,iBAAiB,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,eAAe,CAAC,CAmB1B;IAED,iEAAiE;IAC3D,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAItE;IAED,sEAAsE;IAChE,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjD;IAED;;;OAGG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAUlE;IAED,kFAAkF;IAC5E,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAU3D;CACF"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ValidationError } from '@cat-factory/kernel';
|
|
2
|
+
import { createEmailSender } from './adapters.js';
|
|
3
|
+
function toConnection(record) {
|
|
4
|
+
return {
|
|
5
|
+
provider: record.provider,
|
|
6
|
+
fromAddress: record.fromAddress,
|
|
7
|
+
connectedAt: record.createdAt,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export class EmailConnectionService {
|
|
11
|
+
deps;
|
|
12
|
+
constructor(deps) {
|
|
13
|
+
this.deps = deps;
|
|
14
|
+
}
|
|
15
|
+
/** Connect an account's email sender by storing the provider + sealed API key. */
|
|
16
|
+
async connect(accountId, input) {
|
|
17
|
+
if (!input.apiKey.trim())
|
|
18
|
+
throw new ValidationError('API key is required');
|
|
19
|
+
if (!input.fromAddress.trim())
|
|
20
|
+
throw new ValidationError('From address is required');
|
|
21
|
+
// getByAccount already filters tombstones, so a present record is a live one whose
|
|
22
|
+
// original createdAt we preserve; a reconnect after disconnect starts a fresh one.
|
|
23
|
+
const existing = await this.deps.emailConnectionRepository.getByAccount(accountId);
|
|
24
|
+
const apiKeyCipher = await this.deps.secretCipher.encrypt(input.apiKey.trim());
|
|
25
|
+
const now = this.deps.clock.now();
|
|
26
|
+
const record = {
|
|
27
|
+
accountId,
|
|
28
|
+
provider: input.provider,
|
|
29
|
+
fromAddress: input.fromAddress.trim(),
|
|
30
|
+
apiKeyCipher,
|
|
31
|
+
createdAt: existing ? existing.createdAt : now,
|
|
32
|
+
updatedAt: now,
|
|
33
|
+
deletedAt: null,
|
|
34
|
+
};
|
|
35
|
+
await this.deps.emailConnectionRepository.upsert(record);
|
|
36
|
+
return toConnection(record);
|
|
37
|
+
}
|
|
38
|
+
/** The account's current connection (safe metadata), or null. */
|
|
39
|
+
async getConnection(accountId) {
|
|
40
|
+
const record = await this.deps.emailConnectionRepository.getByAccount(accountId);
|
|
41
|
+
if (!record || record.deletedAt)
|
|
42
|
+
return null;
|
|
43
|
+
return toConnection(record);
|
|
44
|
+
}
|
|
45
|
+
/** Disconnect the account's email sender (tombstones the binding). */
|
|
46
|
+
async disconnect(accountId) {
|
|
47
|
+
const record = await this.deps.emailConnectionRepository.getByAccount(accountId);
|
|
48
|
+
if (!record || record.deletedAt)
|
|
49
|
+
return;
|
|
50
|
+
await this.deps.emailConnectionRepository.softDelete(accountId, this.deps.clock.now());
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build a ready-to-send EmailSender for an account by decrypting its API key, or
|
|
54
|
+
* null when the account has no connection. The resolver the invitation flow uses.
|
|
55
|
+
*/
|
|
56
|
+
async resolveSender(accountId) {
|
|
57
|
+
const record = await this.deps.emailConnectionRepository.getByAccount(accountId);
|
|
58
|
+
if (!record || record.deletedAt)
|
|
59
|
+
return null;
|
|
60
|
+
const apiKey = await this.deps.secretCipher.decrypt(record.apiKeyCipher);
|
|
61
|
+
return createEmailSender({
|
|
62
|
+
provider: record.provider,
|
|
63
|
+
from: record.fromAddress,
|
|
64
|
+
sendgrid: record.provider === 'sendgrid' ? { apiKey } : undefined,
|
|
65
|
+
resend: record.provider === 'resend' ? { apiKey } : undefined,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** Send a test email through the account's configured sender (UI "send test"). */
|
|
69
|
+
async sendTest(accountId, to) {
|
|
70
|
+
const sender = await this.resolveSender(accountId);
|
|
71
|
+
if (!sender)
|
|
72
|
+
throw new ValidationError('No email sender is connected for this account');
|
|
73
|
+
const message = {
|
|
74
|
+
to,
|
|
75
|
+
subject: 'Cat Factory email test',
|
|
76
|
+
text: 'Your Cat Factory email sender is configured correctly.',
|
|
77
|
+
html: '<p>Your Cat Factory email sender is configured correctly.</p>',
|
|
78
|
+
};
|
|
79
|
+
await sender.send(message);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=EmailConnectionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EmailConnectionService.js","sourceRoot":"","sources":["../../../src/modules/email/EmailConnectionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EmailMessage, EmailSender } from '@cat-factory/kernel';
|
|
2
|
+
/** HKDF info string scoping the email-API-key cipher (mirrors SLACK_CIPHER_INFO). */
|
|
3
|
+
export declare const EMAIL_CIPHER_INFO = "cat-factory:email";
|
|
4
|
+
export interface SendGridConfig {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
from: string;
|
|
7
|
+
}
|
|
8
|
+
/** SendGrid v3 mail-send adapter. */
|
|
9
|
+
export declare class SendGridEmailSender implements EmailSender {
|
|
10
|
+
private readonly config;
|
|
11
|
+
constructor(config: SendGridConfig);
|
|
12
|
+
send(message: EmailMessage): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
export interface ResendConfig {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
from: string;
|
|
17
|
+
}
|
|
18
|
+
/** Resend email-send adapter. */
|
|
19
|
+
export declare class ResendEmailSender implements EmailSender {
|
|
20
|
+
private readonly config;
|
|
21
|
+
constructor(config: ResendConfig);
|
|
22
|
+
send(message: EmailMessage): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface EmailProviderConfig {
|
|
25
|
+
provider: 'sendgrid' | 'resend' | null;
|
|
26
|
+
from: string;
|
|
27
|
+
sendgrid?: {
|
|
28
|
+
apiKey: string;
|
|
29
|
+
};
|
|
30
|
+
resend?: {
|
|
31
|
+
apiKey: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the configured EmailSender, or null when no provider is set. The opt-in seam
|
|
36
|
+
* the facades use: unconfigured ⇒ the email-dependent features simply aren't wired.
|
|
37
|
+
*/
|
|
38
|
+
export declare function createEmailSender(config: EmailProviderConfig): EmailSender | null;
|
|
39
|
+
//# sourceMappingURL=adapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/modules/email/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAEpE,qFAAqF;AACrF,eAAO,MAAM,iBAAiB,sBAAsB,CAAA;AAMpD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED,qCAAqC;AACrC,qBAAa,mBAAoB,YAAW,WAAW;IACzC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,cAAc,EAAI;IAEjD,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoB/C;CACF;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED,iCAAiC;AACjC,qBAAa,iBAAkB,YAAW,WAAW;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAnC,YAA6B,MAAM,EAAE,YAAY,EAAI;IAE/C,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAkB/C;CACF;AAUD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,UAAU,GAAG,QAAQ,GAAG,IAAI,CAAA;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7B,MAAM,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5B;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,mBAAmB,GAAG,WAAW,GAAG,IAAI,CAQjF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** HKDF info string scoping the email-API-key cipher (mirrors SLACK_CIPHER_INFO). */
|
|
2
|
+
export const EMAIL_CIPHER_INFO = 'cat-factory:email';
|
|
3
|
+
/** SendGrid v3 mail-send adapter. */
|
|
4
|
+
export class SendGridEmailSender {
|
|
5
|
+
config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
async send(message) {
|
|
10
|
+
const content = [];
|
|
11
|
+
if (message.text)
|
|
12
|
+
content.push({ type: 'text/plain', value: message.text });
|
|
13
|
+
content.push({ type: 'text/html', value: message.html });
|
|
14
|
+
const res = await fetch('https://api.sendgrid.com/v3/mail/send', {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
authorization: `Bearer ${this.config.apiKey}`,
|
|
18
|
+
'content-type': 'application/json',
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
personalizations: [{ to: [{ email: message.to }] }],
|
|
22
|
+
from: { email: this.config.from },
|
|
23
|
+
subject: message.subject,
|
|
24
|
+
content,
|
|
25
|
+
}),
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
throw new Error(`SendGrid send failed (HTTP ${res.status}): ${await safeBody(res)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Resend email-send adapter. */
|
|
33
|
+
export class ResendEmailSender {
|
|
34
|
+
config;
|
|
35
|
+
constructor(config) {
|
|
36
|
+
this.config = config;
|
|
37
|
+
}
|
|
38
|
+
async send(message) {
|
|
39
|
+
const res = await fetch('https://api.resend.com/emails', {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
authorization: `Bearer ${this.config.apiKey}`,
|
|
43
|
+
'content-type': 'application/json',
|
|
44
|
+
},
|
|
45
|
+
body: JSON.stringify({
|
|
46
|
+
from: this.config.from,
|
|
47
|
+
to: message.to,
|
|
48
|
+
subject: message.subject,
|
|
49
|
+
html: message.html,
|
|
50
|
+
...(message.text ? { text: message.text } : {}),
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) {
|
|
54
|
+
throw new Error(`Resend send failed (HTTP ${res.status}): ${await safeBody(res)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function safeBody(res) {
|
|
59
|
+
try {
|
|
60
|
+
return (await res.text()).slice(0, 500);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build the configured EmailSender, or null when no provider is set. The opt-in seam
|
|
68
|
+
* the facades use: unconfigured ⇒ the email-dependent features simply aren't wired.
|
|
69
|
+
*/
|
|
70
|
+
export function createEmailSender(config) {
|
|
71
|
+
if (config.provider === 'sendgrid' && config.sendgrid?.apiKey) {
|
|
72
|
+
return new SendGridEmailSender({ apiKey: config.sendgrid.apiKey, from: config.from });
|
|
73
|
+
}
|
|
74
|
+
if (config.provider === 'resend' && config.resend?.apiKey) {
|
|
75
|
+
return new ResendEmailSender({ apiKey: config.resend.apiKey, from: config.from });
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../../src/modules/email/adapters.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Clock } from '@cat-factory/kernel';
|
|
2
|
+
import type { EnvironmentConnectionRecord, EnvironmentConnectionRepository } from '@cat-factory/kernel';
|
|
3
|
+
import type { SecretCipher } from '@cat-factory/kernel';
|
|
4
|
+
import type { SecretResolver, UrlSafetyPolicy } from '@cat-factory/kernel';
|
|
5
|
+
import type { EnvironmentConnection, EnvironmentManifest } from '@cat-factory/kernel';
|
|
6
|
+
import type { WorkspaceRepository } from '@cat-factory/kernel';
|
|
7
|
+
export interface EnvironmentConnectionServiceDependencies {
|
|
8
|
+
environmentConnectionRepository: EnvironmentConnectionRepository;
|
|
9
|
+
workspaceRepository: WorkspaceRepository;
|
|
10
|
+
secretCipher: SecretCipher;
|
|
11
|
+
clock: Clock;
|
|
12
|
+
/** URL/host safety policy applied to a registered manifest. Defaults to strict. */
|
|
13
|
+
urlPolicy?: UrlSafetyPolicy;
|
|
14
|
+
}
|
|
15
|
+
/** Collect every secret key a manifest's auth scheme references. */
|
|
16
|
+
export declare function referencedSecretKeys(manifest: EnvironmentManifest): string[];
|
|
17
|
+
export interface ResolvedConnection {
|
|
18
|
+
record: EnvironmentConnectionRecord;
|
|
19
|
+
manifest: EnvironmentManifest;
|
|
20
|
+
}
|
|
21
|
+
export declare class EnvironmentConnectionService {
|
|
22
|
+
private readonly deps;
|
|
23
|
+
constructor(deps: EnvironmentConnectionServiceDependencies);
|
|
24
|
+
/** Register (or replace) a workspace's environment provider. */
|
|
25
|
+
register(workspaceId: string, input: {
|
|
26
|
+
manifest: EnvironmentManifest;
|
|
27
|
+
secrets: Record<string, string>;
|
|
28
|
+
}): Promise<EnvironmentConnection>;
|
|
29
|
+
/** Rotate/replace the secret bundle without re-sending the manifest. */
|
|
30
|
+
updateSecrets(workspaceId: string, secrets: Record<string, string>): Promise<EnvironmentConnection>;
|
|
31
|
+
/** The workspace's current connection (safe metadata), or null. */
|
|
32
|
+
getConnection(workspaceId: string): Promise<EnvironmentConnection | null>;
|
|
33
|
+
/** Resolve the live connection + parsed manifest, or throw if not registered. */
|
|
34
|
+
requireConnection(workspaceId: string): Promise<ResolvedConnection>;
|
|
35
|
+
/** Build a secret resolver from the workspace's decrypted secret bundle. */
|
|
36
|
+
resolveSecrets(workspaceId: string): Promise<SecretResolver>;
|
|
37
|
+
/** Unregister the provider (tombstones the binding). */
|
|
38
|
+
unregister(workspaceId: string): Promise<void>;
|
|
39
|
+
private decryptSecrets;
|
|
40
|
+
private toConnection;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=EnvironmentConnectionService.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnvironmentConnectionService.d.ts","sourceRoot":"","sources":["../../../src/modules/environments/EnvironmentConnectionService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,KAAK,EACV,2BAA2B,EAC3B,+BAA+B,EAChC,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAGrF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAA;AAQ9D,MAAM,WAAW,wCAAwC;IACvD,+BAA+B,EAAE,+BAA+B,CAAA;IAChE,mBAAmB,EAAE,mBAAmB,CAAA;IACxC,YAAY,EAAE,YAAY,CAAA;IAC1B,KAAK,EAAE,KAAK,CAAA;IACZ,mFAAmF;IACnF,SAAS,CAAC,EAAE,eAAe,CAAA;CAC5B;AAED,oEAAoE;AACpE,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,EAAE,CAe5E;AAUD,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,2BAA2B,CAAA;IACnC,QAAQ,EAAE,mBAAmB,CAAA;CAC9B;AAED,qBAAa,4BAA4B;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAjC,YAA6B,IAAI,EAAE,wCAAwC,EAAI;IAE/E,gEAAgE;IAC1D,QAAQ,CACZ,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE;QAAE,QAAQ,EAAE,mBAAmB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GACxE,OAAO,CAAC,qBAAqB,CAAC,CA2BhC;IAED,wEAAwE;IAClE,aAAa,CACjB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,qBAAqB,CAAC,CAUhC;IAED,mEAAmE;IAC7D,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAK9E;IAED,iFAAiF;IAC3E,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAOxE;IAED,4EAA4E;IACtE,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAKjE;IAED,wDAAwD;IAClD,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAInD;YAEa,cAAc;IAQ5B,OAAO,CAAC,YAAY;CAYrB"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { ConflictError, STRICT_URL_SAFETY_POLICY, ValidationError } from '@cat-factory/kernel';
|
|
2
|
+
import { requireWorkspace } from '@cat-factory/kernel';
|
|
3
|
+
import { assertSafeEnvironmentUrl } from './environments.logic.js';
|
|
4
|
+
/** Collect every secret key a manifest's auth scheme references. */
|
|
5
|
+
export function referencedSecretKeys(manifest) {
|
|
6
|
+
const auth = manifest.auth;
|
|
7
|
+
switch (auth.type) {
|
|
8
|
+
case 'none':
|
|
9
|
+
return [];
|
|
10
|
+
case 'api_key':
|
|
11
|
+
case 'bearer':
|
|
12
|
+
return [auth.secretRef.key];
|
|
13
|
+
case 'basic':
|
|
14
|
+
return [auth.usernameSecretRef.key, auth.passwordSecretRef.key];
|
|
15
|
+
case 'oauth2_client_credentials':
|
|
16
|
+
return [auth.clientIdSecretRef.key, auth.clientSecretSecretRef.key];
|
|
17
|
+
case 'custom_headers':
|
|
18
|
+
return auth.headers.map((h) => h.secretRef.key);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Validate every URL a manifest will fetch (defence against SSRF). */
|
|
22
|
+
function assertManifestUrlsSafe(manifest, policy) {
|
|
23
|
+
assertSafeEnvironmentUrl(manifest.baseUrl, 'base URL', policy);
|
|
24
|
+
if (manifest.auth.type === 'oauth2_client_credentials') {
|
|
25
|
+
assertSafeEnvironmentUrl(manifest.auth.tokenUrl, 'OAuth token URL', policy);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class EnvironmentConnectionService {
|
|
29
|
+
deps;
|
|
30
|
+
constructor(deps) {
|
|
31
|
+
this.deps = deps;
|
|
32
|
+
}
|
|
33
|
+
/** Register (or replace) a workspace's environment provider. */
|
|
34
|
+
async register(workspaceId, input) {
|
|
35
|
+
await requireWorkspace(this.deps.workspaceRepository, workspaceId);
|
|
36
|
+
// The manifest is validated against the Valibot schema at the controller
|
|
37
|
+
// (jsonBody); here we enforce the additional SSRF + secret-completeness rules.
|
|
38
|
+
const manifest = input.manifest;
|
|
39
|
+
assertManifestUrlsSafe(manifest, this.deps.urlPolicy ?? STRICT_URL_SAFETY_POLICY);
|
|
40
|
+
// Every secret the manifest references must be supplied.
|
|
41
|
+
const missing = referencedSecretKeys(manifest).filter((key) => !(key in input.secrets));
|
|
42
|
+
if (missing.length) {
|
|
43
|
+
throw new ValidationError(`Missing secret values for: ${missing.join(', ')}`);
|
|
44
|
+
}
|
|
45
|
+
const existing = await this.deps.environmentConnectionRepository.getByWorkspace(workspaceId);
|
|
46
|
+
const secretsCipher = await this.deps.secretCipher.encrypt(JSON.stringify(input.secrets));
|
|
47
|
+
const record = {
|
|
48
|
+
workspaceId,
|
|
49
|
+
providerId: manifest.providerId,
|
|
50
|
+
label: manifest.label,
|
|
51
|
+
baseUrl: manifest.baseUrl,
|
|
52
|
+
manifestJson: JSON.stringify(manifest),
|
|
53
|
+
secretsCipher,
|
|
54
|
+
createdAt: existing?.createdAt ?? this.deps.clock.now(),
|
|
55
|
+
deletedAt: null,
|
|
56
|
+
};
|
|
57
|
+
await this.deps.environmentConnectionRepository.upsert(record);
|
|
58
|
+
return this.toConnection(record, Object.keys(input.secrets));
|
|
59
|
+
}
|
|
60
|
+
/** Rotate/replace the secret bundle without re-sending the manifest. */
|
|
61
|
+
async updateSecrets(workspaceId, secrets) {
|
|
62
|
+
const { record, manifest } = await this.requireConnection(workspaceId);
|
|
63
|
+
const missing = referencedSecretKeys(manifest).filter((key) => !(key in secrets));
|
|
64
|
+
if (missing.length) {
|
|
65
|
+
throw new ValidationError(`Missing secret values for: ${missing.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
const secretsCipher = await this.deps.secretCipher.encrypt(JSON.stringify(secrets));
|
|
68
|
+
const updated = { ...record, secretsCipher };
|
|
69
|
+
await this.deps.environmentConnectionRepository.upsert(updated);
|
|
70
|
+
return this.toConnection(updated, Object.keys(secrets));
|
|
71
|
+
}
|
|
72
|
+
/** The workspace's current connection (safe metadata), or null. */
|
|
73
|
+
async getConnection(workspaceId) {
|
|
74
|
+
const record = await this.deps.environmentConnectionRepository.getByWorkspace(workspaceId);
|
|
75
|
+
if (!record)
|
|
76
|
+
return null;
|
|
77
|
+
const keys = Object.keys(await this.decryptSecrets(record));
|
|
78
|
+
return this.toConnection(record, keys);
|
|
79
|
+
}
|
|
80
|
+
/** Resolve the live connection + parsed manifest, or throw if not registered. */
|
|
81
|
+
async requireConnection(workspaceId) {
|
|
82
|
+
const record = await this.deps.environmentConnectionRepository.getByWorkspace(workspaceId);
|
|
83
|
+
if (!record) {
|
|
84
|
+
throw new ConflictError(`Workspace '${workspaceId}' has no environment provider registered`);
|
|
85
|
+
}
|
|
86
|
+
const manifest = JSON.parse(record.manifestJson);
|
|
87
|
+
return { record, manifest };
|
|
88
|
+
}
|
|
89
|
+
/** Build a secret resolver from the workspace's decrypted secret bundle. */
|
|
90
|
+
async resolveSecrets(workspaceId) {
|
|
91
|
+
const record = await this.deps.environmentConnectionRepository.getByWorkspace(workspaceId);
|
|
92
|
+
if (!record)
|
|
93
|
+
return () => undefined;
|
|
94
|
+
const bundle = await this.decryptSecrets(record);
|
|
95
|
+
return (key) => bundle[key];
|
|
96
|
+
}
|
|
97
|
+
/** Unregister the provider (tombstones the binding). */
|
|
98
|
+
async unregister(workspaceId) {
|
|
99
|
+
const record = await this.deps.environmentConnectionRepository.getByWorkspace(workspaceId);
|
|
100
|
+
if (!record)
|
|
101
|
+
return;
|
|
102
|
+
await this.deps.environmentConnectionRepository.softDelete(workspaceId, this.deps.clock.now());
|
|
103
|
+
}
|
|
104
|
+
async decryptSecrets(record) {
|
|
105
|
+
if (!record.secretsCipher)
|
|
106
|
+
return {};
|
|
107
|
+
const parsed = JSON.parse(await this.deps.secretCipher.decrypt(record.secretsCipher));
|
|
108
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
109
|
+
}
|
|
110
|
+
toConnection(record, secretKeys) {
|
|
111
|
+
return {
|
|
112
|
+
providerId: record.providerId,
|
|
113
|
+
label: record.label,
|
|
114
|
+
baseUrl: record.baseUrl,
|
|
115
|
+
connectedAt: record.createdAt,
|
|
116
|
+
secretKeys,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=EnvironmentConnectionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EnvironmentConnectionService.js","sourceRoot":"","sources":["../../../src/modules/environments/EnvironmentConnectionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Clock, IdGenerator } from '@cat-factory/kernel';
|
|
2
|
+
import type { EnvironmentRegistryRepository } from '@cat-factory/kernel';
|
|
3
|
+
import type { EnvironmentProvider, ProvisionContext, UrlSafetyPolicy } from '@cat-factory/kernel';
|
|
4
|
+
import type { SecretCipher } from '@cat-factory/kernel';
|
|
5
|
+
import type { EnvironmentAccessHandle, EnvironmentHandle } from '@cat-factory/kernel';
|
|
6
|
+
import type { EnvironmentConnectionService } from './EnvironmentConnectionService.js';
|
|
7
|
+
export interface EnvironmentProvisioningServiceDependencies {
|
|
8
|
+
connectionService: EnvironmentConnectionService;
|
|
9
|
+
environmentProvider: EnvironmentProvider;
|
|
10
|
+
environmentRegistryRepository: EnvironmentRegistryRepository;
|
|
11
|
+
secretCipher: SecretCipher;
|
|
12
|
+
idGenerator: IdGenerator;
|
|
13
|
+
clock: Clock;
|
|
14
|
+
/** URL/host safety policy applied to the URL a provider returns. Defaults to strict. */
|
|
15
|
+
urlPolicy?: UrlSafetyPolicy;
|
|
16
|
+
}
|
|
17
|
+
export interface ProvisionArgs {
|
|
18
|
+
workspaceId: string;
|
|
19
|
+
blockId?: string | null;
|
|
20
|
+
executionId?: string | null;
|
|
21
|
+
inputs?: Record<string, string>;
|
|
22
|
+
/** Typed git/PR/repo context; passed to the provider and flattened into `inputs`. */
|
|
23
|
+
context?: ProvisionContext;
|
|
24
|
+
}
|
|
25
|
+
/** The compact env view injected into a downstream agent's run context. */
|
|
26
|
+
export interface ResolvedEnvironment {
|
|
27
|
+
url: string | null;
|
|
28
|
+
status: EnvironmentHandle['status'];
|
|
29
|
+
access: EnvironmentAccessHandle | null;
|
|
30
|
+
expiresAt: number | null;
|
|
31
|
+
}
|
|
32
|
+
export declare class EnvironmentProvisioningService {
|
|
33
|
+
private readonly deps;
|
|
34
|
+
constructor(deps: EnvironmentProvisioningServiceDependencies);
|
|
35
|
+
private get urlPolicy();
|
|
36
|
+
/** Provision an environment, persisting an encrypted record keyed by block/run. */
|
|
37
|
+
provision(args: ProvisionArgs): Promise<EnvironmentHandle>;
|
|
38
|
+
/** Re-poll the provider for an environment's status and persist any change. */
|
|
39
|
+
refreshStatus(workspaceId: string, id: string): Promise<EnvironmentHandle>;
|
|
40
|
+
/** List a workspace's environments (no creds). */
|
|
41
|
+
listHandles(workspaceId: string): Promise<EnvironmentHandle[]>;
|
|
42
|
+
/** A single environment handle (no creds), or null. */
|
|
43
|
+
getHandle(workspaceId: string, id: string): Promise<EnvironmentHandle | null>;
|
|
44
|
+
/** A single environment handle WITH decrypted access creds, or null. */
|
|
45
|
+
getHandleWithAccess(workspaceId: string, id: string): Promise<EnvironmentHandle | null>;
|
|
46
|
+
/**
|
|
47
|
+
* The live environment provisioned for a block, with decrypted access — the
|
|
48
|
+
* discovery entry point the execution engine calls to enrich tester context.
|
|
49
|
+
*/
|
|
50
|
+
resolveForBlock(workspaceId: string, blockId: string): Promise<ResolvedEnvironment | null>;
|
|
51
|
+
private resolveExpiry;
|
|
52
|
+
private encryptAccess;
|
|
53
|
+
private decryptAccess;
|
|
54
|
+
private decryptFields;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=EnvironmentProvisioningService.d.ts.map
|