Skip to content

Add typed JSON output to app bulk execute - #8783

Open
isaacroldan wants to merge 4 commits into
codex/app-bulk-cancel-jsonfrom
codex/app-bulk-execute-json
Open

isaacroldan wants to merge 4 commits into
codex/app-bulk-cancel-jsonfrom
codex/app-bulk-execute-json

Conversation

@isaacroldan

@isaacroldan isaacroldan commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

WHY are these changes introduced?

Expose a finite, typed JSON result for bulk execution while keeping the existing raw JSONL and file formats available.

WHAT is this pull request doing?

Add JSON output for app bulk execute on top of the earlier stack layers. Separate preparation, execution, and final presentation. Reuse the shared operation projection and diagnostic events.

Unwatched execution returns operation metadata. Watched JSON output includes native data in resultsJsonl; file output emits an absolute path/format receipt. Preserve the existing text-mode output and exact JSONL file contents. Treat query records as data and completed zero-match queries as successful empty exports.

Representative text output:

Bulk operation is running.
Monitor its progress with:
shopify app bulk status --id=123

Matching JSON output:

{
  "storeDomain": "shop.myshopify.com",
  "apiVersion": "2026-01",
  "status": "success",
  "operation": {
    "gid": "gid://shopify/BulkOperation/123",
    "type": "QUERY",
    "status": "RUNNING",
    "errorCode": null,
    "createdAt": "2026-09-01T00:00:00Z",
    "completedAt": null,
    "objectCount": "2",
    "url": null,
    "partialDataUrl": null
  }
}

How to manually test your changes?

Use a linked app installed on a development store.

  1. Run pnpm shopify app bulk execute --store <store.myshopify.com> --query 'query { products { edges { node { id } } } }', then repeat with --json.
  2. Add --watch. Without JSON mode, confirm stdout still contains raw JSONL. With JSON mode, confirm one result object contains resultsJsonl.
  3. Add --output-file ./results.jsonl to the watched JSON command. Confirm stdout contains only an absolute JSONL file receipt and the file contains native JSONL.
  4. Repeat the watched JSON file command with a query that matches no products, such as query { products(query: "id:0") { edges { node { id } } } }. Confirm exit 0, an empty file, and a file receipt.
  5. Select id errors: tags for products with tags. Confirm watched JSON output reports success rather than treating tags as errors.
  6. Run pnpm shopify app bulk execute --json-schema.

Checklist

  • I've considered possible cross-platform impacts (Mac, Linux, Windows)
  • I've considered possible documentation changes
  • I've considered analytics changes to measure impact
  • The change is user-facing; a minor changeset is included.

@isaacroldan
isaacroldan requested review from a team as code owners October 6, 2026 08:29
@github-actions github-actions Bot added shopify.dev preview Area: @shopify/cli @shopify/cli package issues labels Oct 6, 2026
@isaacroldan
isaacroldan added this pull request to stack #8784 October 6, 2026 08:30
@isaacroldan

Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm.

Built from d7aaae183d8f2aff3a7cd0bb625eff770b7b6397. Workflow run.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261006083344

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@isaacroldan
isaacroldan force-pushed the codex/app-bulk-execute-json branch from d7aaae1 to 2a27ac7 Compare October 6, 2026 10:00
@isaacroldan
isaacroldan removed this pull request from stack #8784 October 6, 2026 10:03
@isaacroldan
isaacroldan added this pull request to stack #8789 October 6, 2026 10:03
@isaacroldan

Copy link
Copy Markdown
Contributor Author

/snapit

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

🫰✨ Thanks @isaacroldan! Your snapshot has been published to npm.

Built from 2a27ac73f13ccd02dfcb8498619ea9ca59d82af6. Workflow run.

Test the snapshot by installing your package globally:

pnpm i -g --@shopify:registry=https://registry.npmjs.org @shopify/cli@0.0.0-snapshot-20261006100618

Caution

After installing, validate the version by running shopify version in your terminal.
If the versions don't match, you might have multiple global instances installed.
Use which shopify to find out which one you are running and uninstall it.

@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Differences in type declarations

We 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:

  • Some seemingly private modules might be re-exported through public modules.
  • If the branch is behind main you might see odd diffs, rebase main into this branch.

New type declarations

We found no new type declarations in this PR

Existing type declarations

packages/cli-kit/dist/private/node/json-error.d.ts
@@ -9,7 +9,6 @@ interface FatalErrorLike {
     command?: unknown;
     args?: unknown;
     details?: unknown;
-    code?: unknown;
 }
 /**
  * Writes the public JSON representation of a fatal error to stdout.
packages/cli-kit/dist/public/common/command-events.d.ts
@@ -120,6 +120,6 @@ export interface CommandEventChannelOptions<TEvent extends CommandEvent> {
  * Adapters validate events at their output boundary; the channel preserves domain-specific event fields.
  *
  * @param options - The event sink and clock used by the channel.
- * @returns A channel that adds a whole-second UTC timestamp before synchronously delivering each event.
+ * @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>;
\ No newline at end of file
packages/cli-kit/dist/public/node/json-output-schema.d.ts
@@ -1,6 +1,8 @@
+import { zod } from './schema.js';
 import { zodToJsonSchema } from 'zod-to-json-schema';
 import type { ZodTypeAny, z } from 'zod';
-export { formatJsonOutputTimestamp, jsonOutputTimestampSchema } from '../common/json-output-schema.js';
+/** UTC instants in CLI-owned results use whole seconds and the Z timezone marker. */
+export declare const jsonOutputTimestampSchema: zod.ZodString;
 interface JsonOutputSchemaDefinition<TSchema extends ZodTypeAny = ZodTypeAny> {
     readonly name: string;
     readonly schema: TSchema;
@@ -23,4 +25,5 @@ interface DefineJsonOutputSchemaOptions<TSchema extends ZodTypeAny> {
  * @param options - The root schema name, its Zod schema, and any named nested schemas.
  * @returns The complete JSON output contract.
  */
-export declare function defineJsonOutputSchema<TSchema extends ZodTypeAny>(options: DefineJsonOutputSchemaOptions<TSchema>): JsonOutputSchema<TSchema>;
\ No newline at end of file
+export declare function defineJsonOutputSchema<TSchema extends ZodTypeAny>(options: DefineJsonOutputSchemaOptions<TSchema>): JsonOutputSchema<TSchema>;
+export {};
\ No newline at end of file
packages/cli-kit/dist/public/node/error/index.d.ts
@@ -18,8 +18,6 @@ export declare abstract class FatalError extends Error {
     nextSteps?: TokenItem<InlineToken>[];
     formattedMessage?: TokenItem;
     customSections?: AlertCustomSection[];
-    /** A stable code for JSON consumers to identify this error, included only when known. */
-    code?: string;
     /** Selected JSON-serializable data to include in JSON errors. Never attach the raw error or request. */
     details?: unknown;
     skipOclifErrorHandling: boolean;
packages/cli-kit/dist/public/node/error/schema.d.ts
@@ -11,7 +11,6 @@ export declare const JsonErrorCustomSectionSchema: zod.ZodObject<{
 }>;
 export declare const JsonAbortErrorSchema: zod.ZodObject<{
     message: zod.ZodString;
-    code: zod.ZodOptional<zod.ZodString>;
     tryMessage: zod.ZodOptional<zod.ZodString>;
     nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
     customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -29,7 +28,6 @@ export declare const JsonAbortErrorSchema: zod.ZodObject<{
 }, "strict", zod.ZodTypeAny, {
     type: "abort";
     message: string;
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -40,7 +38,6 @@ export declare const JsonAbortErrorSchema: zod.ZodObject<{
 }, {
     type: "abort";
     message: string;
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -52,7 +49,6 @@ export declare const JsonAbortErrorSchema: zod.ZodObject<{
 export declare const JsonBugErrorSchema: zod.ZodObject<{
     stack: zod.ZodOptional<zod.ZodString>;
     message: zod.ZodString;
-    code: zod.ZodOptional<zod.ZodString>;
     tryMessage: zod.ZodOptional<zod.ZodString>;
     nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
     customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -70,7 +66,6 @@ export declare const JsonBugErrorSchema: zod.ZodObject<{
 }, "strict", zod.ZodTypeAny, {
     type: "bug";
     message: string;
-    code?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -82,7 +77,6 @@ export declare const JsonBugErrorSchema: zod.ZodObject<{
 }, {
     type: "bug";
     message: string;
-    code?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -96,7 +90,6 @@ export declare const JsonExternalErrorSchema: zod.ZodObject<{
     command: zod.ZodString;
     args: zod.ZodArray<zod.ZodString, "many">;
     message: zod.ZodString;
-    code: zod.ZodOptional<zod.ZodString>;
     tryMessage: zod.ZodOptional<zod.ZodString>;
     nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
     customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -116,7 +109,6 @@ export declare const JsonExternalErrorSchema: zod.ZodObject<{
     message: string;
     command: string;
     args: string[];
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -129,7 +121,6 @@ export declare const JsonExternalErrorSchema: zod.ZodObject<{
     message: string;
     command: string;
     args: string[];
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -140,7 +131,6 @@ export declare const JsonExternalErrorSchema: zod.ZodObject<{
 }>;
 export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     message: zod.ZodString;
-    code: zod.ZodOptional<zod.ZodString>;
     tryMessage: zod.ZodOptional<zod.ZodString>;
     nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
     customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -158,7 +148,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
 }, "strict", zod.ZodTypeAny, {
     type: "abort";
     message: string;
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -169,7 +158,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
 }, {
     type: "abort";
     message: string;
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -180,7 +168,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
 }>, zod.ZodObject<{
     stack: zod.ZodOptional<zod.ZodString>;
     message: zod.ZodString;
-    code: zod.ZodOptional<zod.ZodString>;
     tryMessage: zod.ZodOptional<zod.ZodString>;
     nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
     customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -198,7 +185,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
 }, "strict", zod.ZodTypeAny, {
     type: "bug";
     message: string;
-    code?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -210,7 +196,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
 }, {
     type: "bug";
     message: string;
-    code?: string | undefined;
     stack?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
@@ -223,7 +208,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     command: zod.ZodString;
     args: zod.ZodArray<zod.ZodString, "many">;
     message: zod.ZodString;
-    code: zod.ZodOptional<zod.ZodString>;
     tryMessage: zod.ZodOptional<zod.ZodString>;
     nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
     customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -243,7 +227,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     message: string;
     command: string;
     args: string[];
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -256,7 +239,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
     message: string;
     command: string;
     args: string[];
-    code?: string | undefined;
     nextSteps?: string[] | undefined;
     customSections?: {
         body: string | string[][];
@@ -268,7 +250,6 @@ export declare const JsonErrorSchema: zod.ZodUnion<[zod.ZodObject<{
 export declare const jsonErrorOutputSchema: import("../json-output-schema.js").JsonOutputSchema<zod.ZodObject<{
     error: zod.ZodUnion<[zod.ZodObject<{
         message: zod.ZodString;
-        code: zod.ZodOptional<zod.ZodString>;
         tryMessage: zod.ZodOptional<zod.ZodString>;
         nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
         customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -286,7 +267,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     }, "strict", zod.ZodTypeAny, {
         type: "abort";
         message: string;
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
@@ -297,7 +277,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     }, {
         type: "abort";
         message: string;
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
@@ -308,7 +287,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     }>, zod.ZodObject<{
         stack: zod.ZodOptional<zod.ZodString>;
         message: zod.ZodString;
-        code: zod.ZodOptional<zod.ZodString>;
         tryMessage: zod.ZodOptional<zod.ZodString>;
         nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
         customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -326,7 +304,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     }, "strict", zod.ZodTypeAny, {
         type: "bug";
         message: string;
-        code?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -338,7 +315,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     }, {
         type: "bug";
         message: string;
-        code?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -351,7 +327,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         command: zod.ZodString;
         args: zod.ZodArray<zod.ZodString, "many">;
         message: zod.ZodString;
-        code: zod.ZodOptional<zod.ZodString>;
         tryMessage: zod.ZodOptional<zod.ZodString>;
         nextSteps: zod.ZodOptional<zod.ZodArray<zod.ZodString, "many">>;
         customSections: zod.ZodOptional<zod.ZodArray<zod.ZodObject<{
@@ -371,7 +346,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         message: string;
         command: string;
         args: string[];
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
@@ -384,7 +358,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         message: string;
         command: string;
         args: string[];
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
@@ -397,7 +370,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     error: {
         type: "abort";
         message: string;
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
@@ -408,7 +380,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     } | {
         type: "bug";
         message: string;
-        code?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -422,7 +393,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         message: string;
         command: string;
         args: string[];
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
@@ -435,7 +405,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     error: {
         type: "abort";
         message: string;
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];
@@ -446,7 +415,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
     } | {
         type: "bug";
         message: string;
-        code?: string | undefined;
         stack?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
@@ -460,7 +428,6 @@ export declare const jsonErrorOutputSchema: import("../json-output-schema.js").J
         message: string;
         command: string;
         args: string[];
-        code?: string | undefined;
         nextSteps?: string[] | undefined;
         customSections?: {
             body: string | string[][];

@isaacroldan
isaacroldan force-pushed the codex/app-bulk-execute-json branch from bdb9e33 to 2816253 Compare October 6, 2026 16:48

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant