Add command side event infrastructure - #8403
Open
gonzaloriestra wants to merge 1 commit into
Open
Conversation
This was referenced Aug 26, 2026
gonzaloriestra
force-pushed
the
gonzalo/json-side-events-infrastructure
branch
2 times, most recently
from
August 27, 2026 13:47
405cbbd to
7e68be2
Compare
gonzaloriestra
force-pushed
the
gonzalo/json-side-events-infrastructure
branch
2 times, most recently
from
August 28, 2026 12:29
f29a89e to
f6e9d8f
Compare
gonzaloriestra
marked this pull request as ready for review
August 28, 2026 12:34
gonzaloriestra
force-pushed
the
gonzalo/json-side-events-infrastructure
branch
from
August 28, 2026 12:36
f6e9d8f to
540ae62
Compare
Contributor
Author
This was referenced Aug 31, 2026
gonzaloriestra
force-pushed
the
gonzalo/json-side-events-infrastructure
branch
4 times, most recently
from
August 31, 2026 14:45
3ecb289 to
2f61123
Compare
gonzaloriestra
force-pushed
the
gonzalo/json-side-events-infrastructure
branch
from
August 31, 2026 14:57
2f61123 to
34a5027
Compare
Contributor
Differences in type declarationsWe detected differences in the type declarations generated by Typescript for this branch compared to the baseline ('main' branch). Please, review them to ensure they are backward-compatible. Here are some important things to keep in mind:
New type declarationspackages/cli-kit/dist/public/common/command-events.d.tsimport { z } from 'zod';
/** Schema for a diagnostic emitted while a command executes. */
export declare const commandDiagnosticEventSchema: z.ZodObject<{
type: z.ZodLiteral<"diagnostic">;
timestamp: z.ZodString;
level: z.ZodEnum<["debug", "info", "warning"]>;
message: z.ZodString;
code: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type: "diagnostic";
message: string;
timestamp: string;
level: "info" | "debug" | "warning";
code?: string | undefined;
}, {
type: "diagnostic";
message: string;
timestamp: string;
level: "info" | "debug" | "warning";
code?: string | undefined;
}>;
/** Schema for a progress update emitted while a command executes. */
export declare const commandProgressEventSchema: z.ZodObject<{
type: z.ZodLiteral<"progress">;
timestamp: z.ZodString;
message: z.ZodString;
current: z.ZodOptional<z.ZodNumber>;
total: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
type: "progress";
message: string;
timestamp: string;
current?: number | undefined;
total?: number | undefined;
}, {
type: "progress";
message: string;
timestamp: string;
current?: number | undefined;
total?: number | undefined;
}>;
/** Schema for side events emitted while a command executes. */
export declare const commandEventSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
type: z.ZodLiteral<"diagnostic">;
timestamp: z.ZodString;
level: z.ZodEnum<["debug", "info", "warning"]>;
message: z.ZodString;
code: z.ZodOptional<z.ZodString>;
}, "strict", z.ZodTypeAny, {
type: "diagnostic";
message: string;
timestamp: string;
level: "info" | "debug" | "warning";
code?: string | undefined;
}, {
type: "diagnostic";
message: string;
timestamp: string;
level: "info" | "debug" | "warning";
code?: string | undefined;
}>, z.ZodObject<{
type: z.ZodLiteral<"progress">;
timestamp: z.ZodString;
message: z.ZodString;
current: z.ZodOptional<z.ZodNumber>;
total: z.ZodOptional<z.ZodNumber>;
}, "strict", z.ZodTypeAny, {
type: "progress";
message: string;
timestamp: string;
current?: number | undefined;
total?: number | undefined;
}, {
type: "progress";
message: string;
timestamp: string;
current?: number | undefined;
total?: number | undefined;
}>]>;
/** A diagnostic emitted while a command executes. */
export type CommandDiagnosticEvent = z.infer<typeof commandDiagnosticEventSchema>;
/** A progress update emitted while a command executes. */
export type CommandProgressEvent = z.infer<typeof commandProgressEventSchema>;
/** A side event emitted while a command executes. */
export type CommandEvent = z.infer<typeof commandEventSchema>;
/** An event before its emission timestamp is added. */
export type CommandEventInput<TEvent extends CommandEvent = CommandEvent> = TEvent extends unknown ? Omit<TEvent, 'timestamp'> : never;
/** Presentation details that are not included in the emitted event. */
export interface CommandEventEmissionOptions {
/** The event is already visible in the command's text UI. */
alreadyRendered?: boolean;
}
/** Receives one timestamped event from a command execution. */
export type CommandEventSink<TEvent extends CommandEvent = CommandEvent> = (event: TEvent, options?: CommandEventEmissionOptions) => void;
/** Emits timestamped side events from one command execution. */
export interface CommandEventChannel<TEvent extends CommandEvent = CommandEvent> {
emit: (event: CommandEventInput<TEvent>, options?: CommandEventEmissionOptions) => void;
}
/** Supplies the current time when an event is emitted. */
export type CommandEventClock = () => Date;
/** Options for a command event channel. */
export interface CommandEventChannelOptions<TEvent extends CommandEvent> {
sink?: CommandEventSink<TEvent>;
clock?: CommandEventClock;
}
/**
* Creates a synchronous, execution-scoped channel for command side events.
*
* @param options - The event sink and clock used by the channel.
* @returns A channel that adds an ISO timestamp before synchronously delivering each event.
*/
export declare function createCommandEventChannel<TEvent extends CommandEvent = CommandEvent>(options?: CommandEventChannelOptions<TEvent>): CommandEventChannel<TEvent>;
packages/cli-kit/dist/private/node/command-event-context.d.tsimport { type CommandEvent, type CommandEventChannelOptions, type CommandEventEmissionOptions, type CommandEventInput } from '../../public/common/command-events.js';
export type CommandEventOutputMode = 'text' | 'json';
interface RunWithCommandEventsOptions extends CommandEventChannelOptions<CommandEvent> {
outputMode?: CommandEventOutputMode;
}
/**
* Runs a command execution with an event channel available to all nested asynchronous work.
*
* @param options - The event sink, clock, and output mode used by the channel.
* @param execute - The command execution to run with the channel.
* @returns The result of the command execution.
*/
export declare function runWithCommandEvents<TResult>(options: RunWithCommandEventsOptions, execute: () => TResult): TResult;
/**
* Emits an event for the current command execution.
*
* Events emitted outside a command execution are ignored.
*
* @param event - The event to emit before its timestamp is added.
* @param options - Presentation details that are not included in the event.
*/
export declare function emitCommandEvent(event: CommandEventInput, options?: CommandEventEmissionOptions): void;
/**
* Returns how command events are presented for the current execution.
*
* @returns The current event output mode, or undefined outside a command event context.
*/
export declare function commandEventOutputMode(): CommandEventOutputMode | undefined;
export {};
packages/cli-kit/dist/private/node/command-event-output.d.tsimport type { CommandEvent } from '../../public/common/command-events.js';
/**
* Writes a command event as JSON without routing it back through the command event context.
*
* @param event - The event to write.
*/
export declare function outputCommandEventAsJson(event: CommandEvent): void;
packages/cli-kit/dist/public/node/command-events.d.tsimport { type CommandEvent, type CommandEventChannelOptions, type CommandEventEmissionOptions, type CommandEventInput } from '../common/command-events.js';
import { type CommandEventOutputMode } from '../../private/node/command-event-context.js';
export type { CommandEventOutputMode } from '../../private/node/command-event-context.js';
interface RunWithCommandEventsOptions extends CommandEventChannelOptions<CommandEvent> {
outputMode?: CommandEventOutputMode;
}
/**
* Runs a command execution with an event channel available to all nested asynchronous work.
*
* @param options - The event sink, clock, and output mode used by the channel.
* @param execute - The command execution to run with the channel.
* @returns The result of the command execution.
*/
export declare function runWithCommandEvents<TResult>(options: RunWithCommandEventsOptions, execute: () => TResult): TResult;
/**
* Runs the complete CLI lifecycle with the event presentation selected by its arguments.
*
* @param argv - The command arguments used to determine whether JSON output is enabled.
* @param execute - The command lifecycle to run.
* @returns The result of the command lifecycle.
*/
export declare function runWithCommandEventsForCommand<TResult>(argv: string[], execute: () => TResult): TResult;
/**
* Emits an event for the current command execution.
*
* Events emitted outside a command execution are ignored.
*
* @param event - The event to emit before its timestamp is added.
* @param options - Presentation details that are not included in the event.
*/
export declare function emitCommandEvent(event: CommandEventInput, options?: CommandEventEmissionOptions): void;
/**
* Returns how command events are presented for the current execution.
*
* @returns The current event output mode, or undefined outside a command event context.
*/
export declare function commandEventOutputMode(): CommandEventOutputMode | undefined;
/**
* Renders a command side event to stderr using the existing CLI output behavior.
*
* @param event - The event to render.
*/
export declare function renderCommandEvent(event: CommandEvent): void;
/**
* Renders a command side event as compact JSON to stderr.
*
* @param event - The event to render.
*/
export declare function renderCommandEventAsJson(event: CommandEvent): void;
Existing type declarationspackages/cli-kit/dist/public/node/base-command.d.ts@@ -18,6 +18,7 @@ declare abstract class BaseCommand extends Command {
catch(error: Error & {
skipOclifErrorHandling: boolean;
}): Promise<void>;
+ protected _run<T>(): Promise<T>;
protected init(): Promise<unknown>;
protected showNpmFlagWarning(): void;
protected exitWithTimestampWhenEnvVariablePresent(): void;
packages/cli-kit/dist/public/node/environment.d.ts@@ -42,9 +42,10 @@ export declare function getIdentityTokenInformation(): {
* Checks if the JSON output is enabled via flag (--json or -j) or environment variable (SHOPIFY_FLAG_JSON).
*
* @param environment - Process environment variables.
+ * @param argv - Command arguments to inspect for JSON flags.
* @returns True if the JSON output is enabled, false otherwise.
*/
-export declare function jsonOutputEnabled(environment?: NodeJS.ProcessEnv): boolean;
+export declare function jsonOutputEnabled(environment?: NodeJS.ProcessEnv, argv?: string[]): boolean;
/**
* If true, the CLI should not use the network level retry.
*
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

WHY are these changes introduced?
Closes shop/issues-develop#23661
Finite commands need structured diagnostics and progress that remain separate from their final result.
Based on prototypes:
WHAT is this pull request doing?
How to test your changes?
See #8415
Also:
pnpm shopify app info --json --verboseChecklist