Skip to content

Commit c9148ec

Browse files
giovaborgognoclaude
andcommitted
feat(events): phase 8.4 — CLI commands for events
Add `trigger events list` and `trigger events publish` commands: - events/index.ts: parent command registration - events/list.ts: list event definitions with subscriber count - events/publish.ts: publish event with JSON payload - apiClient.ts: listEvents() and publishEvent() methods Usage: trigger events list trigger events publish order.created --payload '{"orderId":"123"}' Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8510dbc commit c9148ec

File tree

5 files changed

+278
-0
lines changed

5 files changed

+278
-0
lines changed

packages/cli-v3/src/apiClient.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ import {
4040
ApiBranchListResponseBody,
4141
GenerateRegistryCredentialsResponseBody,
4242
RemoteBuildProviderStatusResponseBody,
43+
ListEventsResponseBody,
44+
PublishEventResponseBody,
4345
} from "@trigger.dev/core/v3";
4446
import {
4547
WorkloadDebugLogRequestBody,
@@ -547,6 +549,41 @@ export class CliApiClient {
547549
});
548550
}
549551

552+
async listEvents(projectRef: string) {
553+
if (!this.accessToken) {
554+
throw new Error("listEvents: No access token");
555+
}
556+
557+
return wrapZodFetch(ListEventsResponseBody, `${this.apiURL}/api/v1/events`, {
558+
method: "GET",
559+
headers: {
560+
...this.getHeaders(),
561+
"x-trigger-project-ref": projectRef,
562+
},
563+
});
564+
}
565+
566+
async publishEvent(projectRef: string, eventId: string, payload: unknown) {
567+
if (!this.accessToken) {
568+
throw new Error("publishEvent: No access token");
569+
}
570+
571+
const encodedEventId = encodeURIComponent(eventId);
572+
573+
return wrapZodFetch(
574+
PublishEventResponseBody,
575+
`${this.apiURL}/api/v1/events/${encodedEventId}/publish`,
576+
{
577+
method: "POST",
578+
headers: {
579+
...this.getHeaders(),
580+
"x-trigger-project-ref": projectRef,
581+
},
582+
body: JSON.stringify({ payload }),
583+
}
584+
);
585+
}
586+
550587
get dev() {
551588
return {
552589
config: this.devConfig.bind(this),

packages/cli-v3/src/cli/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { VERSION } from "../version.js";
1818
import { installExitHandler } from "./common.js";
1919
import { configureInstallMcpCommand } from "../commands/install-mcp.js";
2020
import { configureInstallRulesCommand } from "../commands/install-rules.js";
21+
import { configureEventsCommand } from "../commands/events/index.js";
2122

2223
export const program = new Command();
2324

@@ -42,5 +43,6 @@ configureAnalyzeCommand(program);
4243
configureMcpCommand(program);
4344
configureInstallMcpCommand(program);
4445
configureInstallRulesCommand(program);
46+
configureEventsCommand(program);
4547

4648
installExitHandler();
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Command } from "commander";
2+
import { configureEventsListCommand } from "./list.js";
3+
import { configureEventsPublishCommand } from "./publish.js";
4+
5+
export function configureEventsCommand(program: Command) {
6+
const events = program
7+
.command("events")
8+
.description("Manage pub/sub events");
9+
10+
configureEventsListCommand(events);
11+
configureEventsPublishCommand(events);
12+
13+
return events;
14+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { Command } from "commander";
2+
import { z } from "zod";
3+
import {
4+
CommonCommandOptions,
5+
commonOptions,
6+
handleTelemetry,
7+
wrapCommandAction,
8+
} from "../../cli/common.js";
9+
import { printInitialBanner } from "../../utilities/initialBanner.js";
10+
import { isLoggedIn } from "../../utilities/session.js";
11+
import { loadConfig } from "../../config.js";
12+
import { resolveLocalEnvVars } from "../../utilities/localEnvVars.js";
13+
import { CliApiClient } from "../../apiClient.js";
14+
import { intro, outro } from "@clack/prompts";
15+
import { spinner } from "../../utilities/windows.js";
16+
import { logger } from "../../utilities/logger.js";
17+
import { tryCatch } from "@trigger.dev/core";
18+
19+
const EventsListOptions = CommonCommandOptions.extend({
20+
config: z.string().optional(),
21+
projectRef: z.string().optional(),
22+
envFile: z.string().optional(),
23+
});
24+
25+
type EventsListOptions = z.infer<typeof EventsListOptions>;
26+
27+
export function configureEventsListCommand(program: Command) {
28+
return commonOptions(
29+
program
30+
.command("list")
31+
.description("List all event definitions in the project")
32+
.option("-c, --config <config file>", "The name of the config file")
33+
.option("-p, --project-ref <project ref>", "The project ref")
34+
.option("--env-file <env file>", "Path to the .env file")
35+
).action(async (options) => {
36+
await handleTelemetry(async () => {
37+
await printInitialBanner(false, options.profile);
38+
await eventsListCommand(options);
39+
});
40+
});
41+
}
42+
43+
async function eventsListCommand(options: unknown) {
44+
return await wrapCommandAction(
45+
"eventsListCommand",
46+
EventsListOptions,
47+
options,
48+
async (opts) => {
49+
return await _eventsListCommand(opts);
50+
}
51+
);
52+
}
53+
54+
async function _eventsListCommand(options: EventsListOptions) {
55+
intro("Listing event definitions");
56+
57+
const envVars = resolveLocalEnvVars(options.envFile);
58+
59+
const authentication = await isLoggedIn(options.profile);
60+
if (!authentication.ok) {
61+
outro(`Not logged in. Use \`trigger login\` first.`);
62+
return;
63+
}
64+
65+
const [configError, resolvedConfig] = await tryCatch(
66+
loadConfig({
67+
overrides: { project: options.projectRef ?? envVars.TRIGGER_PROJECT_REF },
68+
configFile: options.config,
69+
warn: false,
70+
})
71+
);
72+
73+
if (configError || !resolvedConfig?.project) {
74+
outro("Could not resolve project. Use --project-ref or configure trigger.config.ts.");
75+
return;
76+
}
77+
78+
const loadingSpinner = spinner();
79+
loadingSpinner.start("Fetching events...");
80+
81+
const apiClient = new CliApiClient(authentication.auth.apiUrl, authentication.auth.accessToken);
82+
const result = await apiClient.listEvents(resolvedConfig.project);
83+
84+
if (!result.success) {
85+
loadingSpinner.stop("Failed to fetch events");
86+
logger.error(result.error);
87+
return;
88+
}
89+
90+
loadingSpinner.stop(`Found ${result.data.data.length} event(s)`);
91+
92+
if (result.data.data.length === 0) {
93+
outro("No events defined yet. Define events with `event()` in your task files.");
94+
return;
95+
}
96+
97+
logger.table(
98+
result.data.data.map((evt) => ({
99+
id: evt.slug,
100+
version: evt.version,
101+
subscribers: String(evt.subscriberCount),
102+
schema: evt.hasSchema ? "yes" : "no",
103+
}))
104+
);
105+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import { Command } from "commander";
2+
import { z } from "zod";
3+
import {
4+
CommonCommandOptions,
5+
commonOptions,
6+
handleTelemetry,
7+
wrapCommandAction,
8+
} from "../../cli/common.js";
9+
import { printInitialBanner } from "../../utilities/initialBanner.js";
10+
import { isLoggedIn } from "../../utilities/session.js";
11+
import { loadConfig } from "../../config.js";
12+
import { resolveLocalEnvVars } from "../../utilities/localEnvVars.js";
13+
import { CliApiClient } from "../../apiClient.js";
14+
import { intro, outro } from "@clack/prompts";
15+
import { spinner } from "../../utilities/windows.js";
16+
import { logger } from "../../utilities/logger.js";
17+
import { tryCatch } from "@trigger.dev/core";
18+
19+
const EventsPublishOptions = CommonCommandOptions.extend({
20+
config: z.string().optional(),
21+
projectRef: z.string().optional(),
22+
envFile: z.string().optional(),
23+
payload: z.string(),
24+
});
25+
26+
type EventsPublishOptions = z.infer<typeof EventsPublishOptions>;
27+
28+
export function configureEventsPublishCommand(program: Command) {
29+
return commonOptions(
30+
program
31+
.command("publish <eventId>")
32+
.description("Publish an event with a JSON payload")
33+
.requiredOption("--payload <json>", "JSON payload to publish")
34+
.option("-c, --config <config file>", "The name of the config file")
35+
.option("-p, --project-ref <project ref>", "The project ref")
36+
.option("--env-file <env file>", "Path to the .env file")
37+
).action(async (eventId: string, options) => {
38+
await handleTelemetry(async () => {
39+
await printInitialBanner(false, options.profile);
40+
await eventsPublishCommand({ ...options, eventId });
41+
});
42+
});
43+
}
44+
45+
const EventsPublishCommandInput = EventsPublishOptions.extend({
46+
eventId: z.string(),
47+
});
48+
49+
type EventsPublishCommandInput = z.infer<typeof EventsPublishCommandInput>;
50+
51+
async function eventsPublishCommand(options: unknown) {
52+
return await wrapCommandAction(
53+
"eventsPublishCommand",
54+
EventsPublishCommandInput,
55+
options,
56+
async (opts) => {
57+
return await _eventsPublishCommand(opts);
58+
}
59+
);
60+
}
61+
62+
async function _eventsPublishCommand(options: EventsPublishCommandInput) {
63+
intro(`Publishing event "${options.eventId}"`);
64+
65+
// Parse JSON payload
66+
let payload: unknown;
67+
try {
68+
payload = JSON.parse(options.payload);
69+
} catch {
70+
outro("Invalid JSON payload. Provide valid JSON with --payload.");
71+
return;
72+
}
73+
74+
const envVars = resolveLocalEnvVars(options.envFile);
75+
76+
const authentication = await isLoggedIn(options.profile);
77+
if (!authentication.ok) {
78+
outro(`Not logged in. Use \`trigger login\` first.`);
79+
return;
80+
}
81+
82+
const [configError, resolvedConfig] = await tryCatch(
83+
loadConfig({
84+
overrides: { project: options.projectRef ?? envVars.TRIGGER_PROJECT_REF },
85+
configFile: options.config,
86+
warn: false,
87+
})
88+
);
89+
90+
if (configError || !resolvedConfig?.project) {
91+
outro("Could not resolve project. Use --project-ref or configure trigger.config.ts.");
92+
return;
93+
}
94+
95+
const loadingSpinner = spinner();
96+
loadingSpinner.start("Publishing event...");
97+
98+
const apiClient = new CliApiClient(authentication.auth.apiUrl, authentication.auth.accessToken);
99+
const result = await apiClient.publishEvent(resolvedConfig.project, options.eventId, payload);
100+
101+
if (!result.success) {
102+
loadingSpinner.stop("Failed to publish event");
103+
logger.error(result.error);
104+
return;
105+
}
106+
107+
loadingSpinner.stop("Event published");
108+
109+
logger.info(`Event ID: ${result.data.eventId}`);
110+
logger.info(`Triggered ${result.data.runs.length} run(s)`);
111+
112+
if (result.data.runs.length > 0) {
113+
logger.table(
114+
result.data.runs.map((run) => ({
115+
task: run.taskIdentifier,
116+
runId: run.runId,
117+
}))
118+
);
119+
}
120+
}

0 commit comments

Comments
 (0)