Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 56 additions & 6 deletions packages/core/src/triggers/catalog-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ export async function generateTriggerCatalog(repoRoot?: string): Promise<Trigger
const adaptersWithoutKnownTriggers: AdapterWithoutKnownTriggers[] = [];

for (const adapterPackage of packages) {
const mappingEvents = await readMappingWebhookEvents(repoRoot, adapterPackage);
if (mappingEvents.events.length > 0 && mappingEvents.source === "core.mapping.webhooks") {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The priority logic here is asymmetric: core.mapping.webhooks takes precedence over supportedEvents, whereas package.mapping.webhooks is subordinated to supportedEvents. While this is done to support Slack's specific configuration, adding a brief explanatory comment here would greatly improve maintainability and prevent future confusion for other developers.

    // Prioritize core mappings (e.g. Slack) over supportedEvents to ensure raw webhook event names are used.
    if (mappingEvents.events.length > 0 && mappingEvents.source === "core.mapping.webhooks") {

// Storage-bridge adapters can keep their webhook contract only in
// packages/core/mappings. Those keys are the runtime event names and
// intentionally override adapter supportedEvents() fallbacks.
entries.push({
provider: adapterPackage.provider,
events: mappingEvents.events,
});
sources.push({
packageName: adapterPackage.packageName,
packagePath: adapterPackage.relativePath,
provider: adapterPackage.provider,
source: "mapping.webhooks",
});
continue;
}

const supportedEvents = await readSupportedEvents(adapterPackage);
if (supportedEvents.events.length > 0) {
entries.push({
Expand All @@ -71,11 +89,10 @@ export async function generateTriggerCatalog(repoRoot?: string): Promise<Trigger
continue;
}

const mappingEvents = await readMappingWebhookEvents(adapterPackage.dir);
if (mappingEvents.length > 0) {
if (mappingEvents.events.length > 0) {
entries.push({
provider: adapterPackage.provider,
events: mappingEvents,
events: mappingEvents.events,
});
sources.push({
packageName: adapterPackage.packageName,
Expand Down Expand Up @@ -292,12 +309,45 @@ async function firstExistingPath(paths: string[]): Promise<string | undefined> {
return undefined;
}

async function readMappingWebhookEvents(packageDir: string): Promise<string[]> {
type MappingWebhookEvents = {
events: string[];
source?: "package.mapping.webhooks" | "core.mapping.webhooks";
};

async function readMappingWebhookEvents(
repoRoot: string,
adapterPackage: AdapterPackage
): Promise<MappingWebhookEvents> {
const packageEvents = await readMappingWebhookEventsFromDir(adapterPackage.dir);
if (packageEvents.length > 0) {
return { events: packageEvents, source: "package.mapping.webhooks" };
}

const coreMappingDir = join(repoRoot, "packages", "core", "mappings");
const coreEvents = await readMappingWebhookEventsFromFiles([
join(coreMappingDir, `${adapterPackage.provider}.mapping.yaml`),
]);
return {
events: coreEvents,
...(coreEvents.length > 0 ? { source: "core.mapping.webhooks" as const } : {}),
};
}

async function readMappingWebhookEventsFromDir(packageDir: string): Promise<string[]> {
const filenames = (await readdir(packageDir)).filter((name) => name.endsWith(".mapping.yaml")).sort();
return readMappingWebhookEventsFromFiles(filenames.map((filename) => join(packageDir, filename)));
}

async function readMappingWebhookEventsFromFiles(paths: string[]): Promise<string[]> {
const events = new Set<string>();

for (const filename of filenames) {
const contents = await readFile(join(packageDir, filename), "utf8");
for (const path of paths) {
let contents: string;
try {
contents = await readFile(path, "utf8");
} catch {
continue;
}
const parsed = YAML.parse(contents) as { webhooks?: unknown } | null;
if (!parsed || !isRecord(parsed.webhooks)) {
continue;
Expand Down
20 changes: 2 additions & 18 deletions packages/core/src/triggers/catalog.generated.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,24 +382,8 @@
"product.update"
],
"slack": [
"channel.archived",
"channel.created",
"channel.deleted",
"channel.member_joined",
"channel.member_left",
"channel.renamed",
"channel.unarchived",
"group.archived",
"group.deleted",
"group.renamed",
"group.unarchived",
"message.created",
"message.deleted",
"message.updated",
"reaction.added",
"reaction.removed",
"user.changed",
"user.joined"
"message",
"reaction_added"
],
"stripe": [
"charge.failed",
Expand Down
20 changes: 2 additions & 18 deletions packages/core/src/triggers/catalog.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,24 +390,8 @@ export const KNOWN_TRIGGER_CATALOG = {
"product.update"
],
"slack": [
"channel.archived",
"channel.created",
"channel.deleted",
"channel.member_joined",
"channel.member_left",
"channel.renamed",
"channel.unarchived",
"group.archived",
"group.deleted",
"group.renamed",
"group.unarchived",
"message.created",
"message.deleted",
"message.updated",
"reaction.added",
"reaction.removed",
"user.changed",
"user.joined"
"message",
"reaction_added"
],
"stripe": [
"charge.failed",
Expand Down
4 changes: 3 additions & 1 deletion packages/core/tests/triggers/catalog-generator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ test("catalog preserves provider-specific event names verbatim", () => {
assert.ok(KNOWN_TRIGGER_CATALOG.linear.includes("PermissionChange.teamAccessChanged"));
assert.ok(KNOWN_TRIGGER_CATALOG.linear.includes("OAuthApp.revoked"));
assert.ok(KNOWN_TRIGGER_CATALOG.salesforce.includes("Account.created"));
assert.ok(KNOWN_TRIGGER_CATALOG.slack.includes("message.created"));
assert.ok(KNOWN_TRIGGER_CATALOG.slack.includes("message"));
assert.ok(KNOWN_TRIGGER_CATALOG.slack.includes("reaction_added"));
assert.equal((KNOWN_TRIGGER_CATALOG.slack as readonly string[]).includes("message.created"), false);
assert.ok(KNOWN_TRIGGER_CATALOG.stripe.includes("invoice.paid"));
assert.ok(KNOWN_TRIGGER_CATALOG.fathom.includes("new-meeting-content-ready"));
assert.ok(KNOWN_TRIGGER_CATALOG.notion.includes("page.created"));
Expand Down
Loading