Add shopify store bulk commands#7897
Merged
Merged
Conversation
Contributor
Author
This stack of pull requests is managed by Graphite. Learn more about stacking. |
dcce44b to
23d1d2d
Compare
23d1d2d to
b146326
Compare
jordanverasamy
approved these changes
Jun 24, 2026
isaacroldan
reviewed
Jun 25, 2026
isaacroldan
reviewed
Jun 25, 2026
isaacroldan
reviewed
Jun 25, 2026
isaacroldan
reviewed
Jun 25, 2026
Add shopify store bulk {execute,status,cancel}, which run Admin API bulk
operations against a store using stored auth (shopify store auth) instead of
requiring an app. The commands consume the shared bulk-operation engine in
@shopify/cli-kit and add only store-specific orchestration: loading the
stored session into an AdminSession, store-only display, the
'shopify store bulk status' help text, and a --allow-mutations opt-in gate
(consistent with shopify store execute).
- Replace dynamic import of @shopify/cli-kit/node/ui in the execute test with a static import; the dynamic import marked the module lazy-loaded and tripped @nx/enforce-module-boundaries across the store package. - Drop the unused re-export of extractBulkOperationId and inline the BulkAdminContext return type so knip's unused-exports check passes. - Regenerate the e2e command-tree snapshot to include store bulk.
Mirror the app change after formatBulkOperationCancellationResult became command-agnostic: the store cancel command now appends its own 'shopify store bulk status' hint for in-progress cancellations.
- Use the shared (empty-file-safe) resultsContainUserErrors from cli-kit instead of a local copy. - prepareBulkAdminContext now returns the AdminSession directly since no caller used the stored session; update callers and tests. - Add --watch result-rendering tests covering completed (with results, empty results, and user errors) and failed operations.
- Use the shared cli-kit resolveBulkOperationQuery in store bulk execute instead of a duplicated read/validate helper. - Extract a shared --id flag (with GID-normalizing parse) into store flags; status uses the optional variant, cancel the required one, so neither command calls normalizeBulkOperationId directly. - Drop the normalizeBulkOperationId re-export from the store status service.
b146326 to
ac9f2a1
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/node/api/bulk-operations.d.ts/**
* Shared primitives for running Admin API bulk operations.
*
* These are auth-agnostic: callers provide an `AdminSession` (however they obtained it —
* app client credentials, a stored store session, etc.) and these helpers handle starting,
* watching, fetching, cancelling, downloading, and formatting bulk operations.
*/
export { BULK_OPERATIONS_MIN_API_VERSION } from './bulk-operations/constants.js';
export { normalizeBulkOperationId, extractBulkOperationId, isMutation, validateSingleOperation, resolveApiVersion, } from './bulk-operations/helpers.js';
export { resolveBulkOperationQuery } from './bulk-operations/query.js';
export { runBulkOperationQuery } from './bulk-operations/run-query.js';
export { runBulkOperationMutation } from './bulk-operations/run-mutation.js';
export { stageFile } from './bulk-operations/stage-file.js';
export { fetchBulkOperationById, fetchRecentBulkOperations } from './bulk-operations/fetch.js';
export { cancelBulkOperationRequest } from './bulk-operations/cancel.js';
export { watchBulkOperation, shortBulkOperationPoll, QUICK_WATCH_TIMEOUT_MS, QUICK_WATCH_POLL_INTERVAL_MS, type BulkOperation, } from './bulk-operations/watch-bulk-operation.js';
export { downloadBulkOperationResults, resultsContainUserErrors, } from './bulk-operations/download-bulk-operation-results.js';
export { formatBulkOperationStatus, renderBulkOperationUserErrors, formatBulkOperationCancellationResult, type BulkOperationCancellationResult, } from './bulk-operations/format-bulk-operation-status.js';
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/bulk-operation-cancel.d.tsimport * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type BulkOperationCancelMutationVariables = Types.Exact<{
id: Types.Scalars['ID']['input'];
}>;
export type BulkOperationCancelMutation = {
bulkOperationCancel?: {
bulkOperation?: {
completedAt?: unknown | null;
createdAt: unknown;
errorCode?: Types.BulkOperationErrorCode | null;
fileSize?: unknown | null;
id: string;
objectCount: unknown;
partialDataUrl?: string | null;
query: string;
rootObjectCount: unknown;
status: Types.BulkOperationStatus;
type: Types.BulkOperationType;
url?: string | null;
} | null;
userErrors: {
field?: string[] | null;
message: string;
}[];
} | null;
};
export declare const BulkOperationCancel: DocumentNode<BulkOperationCancelMutation, BulkOperationCancelMutationVariables>;
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutation.d.tsimport * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type BulkOperationRunMutationMutationVariables = Types.Exact<{
mutation: Types.Scalars['String']['input'];
stagedUploadPath: Types.Scalars['String']['input'];
clientIdentifier?: Types.InputMaybe<Types.Scalars['String']['input']>;
}>;
export type BulkOperationRunMutationMutation = {
bulkOperationRunMutation?: {
bulkOperation?: {
type: Types.BulkOperationType;
completedAt?: unknown | null;
createdAt: unknown;
errorCode?: Types.BulkOperationErrorCode | null;
id: string;
objectCount: unknown;
partialDataUrl?: string | null;
status: Types.BulkOperationStatus;
url?: string | null;
} | null;
userErrors: {
code?: Types.BulkMutationErrorCode | null;
field?: string[] | null;
message: string;
}[];
} | null;
};
export declare const BulkOperationRunMutation: DocumentNode<BulkOperationRunMutationMutation, BulkOperationRunMutationMutationVariables>;
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/bulk-operation-run-query.d.tsimport * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type BulkOperationRunQueryMutationVariables = Types.Exact<{
query: Types.Scalars['String']['input'];
}>;
export type BulkOperationRunQueryMutation = {
bulkOperationRunQuery?: {
bulkOperation?: {
type: Types.BulkOperationType;
completedAt?: unknown | null;
createdAt: unknown;
errorCode?: Types.BulkOperationErrorCode | null;
id: string;
objectCount: unknown;
partialDataUrl?: string | null;
status: Types.BulkOperationStatus;
url?: string | null;
} | null;
userErrors: {
code?: Types.BulkOperationUserErrorCode | null;
field?: string[] | null;
message: string;
}[];
} | null;
};
export declare const BulkOperationRunQuery: DocumentNode<BulkOperationRunQueryMutation, BulkOperationRunQueryMutationVariables>;
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.d.tsimport * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type GetBulkOperationByIdQueryVariables = Types.Exact<{
id: Types.Scalars['ID']['input'];
}>;
export type GetBulkOperationByIdQuery = {
bulkOperation?: {
type: Types.BulkOperationType;
completedAt?: unknown | null;
createdAt: unknown;
errorCode?: Types.BulkOperationErrorCode | null;
id: string;
objectCount: unknown;
partialDataUrl?: string | null;
status: Types.BulkOperationStatus;
url?: string | null;
} | null;
};
export declare const GetBulkOperationById: DocumentNode<GetBulkOperationByIdQuery, GetBulkOperationByIdQueryVariables>;
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/list-bulk-operations.d.tsimport * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type ListBulkOperationsQueryVariables = Types.Exact<{
query?: Types.InputMaybe<Types.Scalars['String']['input']>;
first: Types.Scalars['Int']['input'];
sortKey: Types.BulkOperationsSortKeys;
}>;
export type ListBulkOperationsQuery = {
bulkOperations: {
nodes: {
id: string;
status: Types.BulkOperationStatus;
errorCode?: Types.BulkOperationErrorCode | null;
objectCount: unknown;
createdAt: unknown;
completedAt?: unknown | null;
url?: string | null;
partialDataUrl?: string | null;
}[];
};
};
export declare const ListBulkOperations: DocumentNode<ListBulkOperationsQuery, ListBulkOperationsQueryVariables>;
packages/cli-kit/dist/cli/api/graphql/bulk-operations/generated/staged-uploads-create.d.tsimport * as Types from './types.js';
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
export type StagedUploadsCreateMutationVariables = Types.Exact<{
input: Types.StagedUploadInput[] | Types.StagedUploadInput;
}>;
export type StagedUploadsCreateMutation = {
stagedUploadsCreate?: {
stagedTargets?: {
url?: string | null;
resourceUrl?: string | null;
parameters: {
name: string;
value: string;
}[];
}[] | null;
userErrors: {
field?: string[] | null;
message: string;
}[];
} | null;
};
export declare const StagedUploadsCreate: DocumentNode<StagedUploadsCreateMutation, StagedUploadsCreateMutationVariables>;
packages/cli-kit/dist/public/node/api/bulk-operations/cancel.d.tsimport { AdminSession } from '../../session.js';
import { BulkOperationCancelMutation } from '../../../../cli/api/graphql/bulk-operations/generated/bulk-operation-cancel.js';
interface CancelBulkOperationOptions {
adminSession: AdminSession;
operationId: string;
version?: string;
}
/**
* Requests cancellation of a bulk operation.
*
* @param options - The admin session, operation ID, and optional API version.
* @returns The bulkOperationCancel result, including any user errors.
*/
export declare function cancelBulkOperationRequest(options: CancelBulkOperationOptions): Promise<BulkOperationCancelMutation['bulkOperationCancel']>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/constants.d.ts/**
* Minimum API version for bulk operations.
* This ensures bulk operation features work correctly across all operations.
*/
export declare const BULK_OPERATIONS_MIN_API_VERSION = "2026-01";
packages/cli-kit/dist/public/node/api/bulk-operations/download-bulk-operation-results.d.ts/**
* Downloads the results of a completed bulk operation.
*
* @param url - The results URL returned by the Admin API.
* @returns The raw JSONL results as a string.
*/
export declare function downloadBulkOperationResults(url: string): Promise<string>;
/**
* Checks whether any line of a JSONL bulk operation result reports GraphQL user errors.
*
* Blank result files, such as a completed operation that matched nothing, are treated as having
* no user errors instead of crashing the JSON parser.
*
* @param results - The raw JSONL results string.
* @returns True if any result line reports user errors.
*/
export declare function resultsContainUserErrors(results: string): boolean;
packages/cli-kit/dist/public/node/api/bulk-operations/fetch.d.tsimport { AdminSession } from '../../session.js';
import { GetBulkOperationByIdQuery } from '../../../../cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.js';
import { ListBulkOperationsQuery } from '../../../../cli/api/graphql/bulk-operations/generated/list-bulk-operations.js';
interface FetchBulkOperationByIdOptions {
adminSession: AdminSession;
operationId: string;
version?: string;
}
/**
* Fetches a single bulk operation by ID.
*
* @param options - The admin session, operation ID, and optional API version.
* @returns The bulk operation, or null if it doesn't exist.
*/
export declare function fetchBulkOperationById(options: FetchBulkOperationByIdOptions): Promise<GetBulkOperationByIdQuery['bulkOperation']>;
interface FetchRecentBulkOperationsOptions {
adminSession: AdminSession;
version?: string;
/** Number of days back to include. Defaults to 7. */
sinceDays?: number;
/** Maximum number of operations to return. Defaults to 100. */
first?: number;
}
/**
* Fetches recent bulk operations for the store.
*
* @param options - The admin session, optional API version, look-back window, and page size.
* @returns The list of bulk operation nodes, most recent first.
*/
export declare function fetchRecentBulkOperations(options: FetchRecentBulkOperationsOptions): Promise<ListBulkOperationsQuery['bulkOperations']['nodes']>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/format-bulk-operation-status.d.tsimport { GetBulkOperationByIdQuery } from '../../../../cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.js';
import { TokenizedString } from '../../output.js';
import { TokenItem } from '../../ui.js';
/**
* Produces a human-readable status line for a bulk operation.
*
* @param operation - The bulk operation.
* @returns A tokenized status string.
*/
export declare function formatBulkOperationStatus(operation: NonNullable<GetBulkOperationByIdQuery['bulkOperation']>): TokenizedString;
interface UserError {
field?: string[] | null;
message: string;
}
/**
* Renders a list of bulk operation user errors.
*
* @param userErrors - The user errors to render.
* @param headline - The headline for the error block.
*/
export declare function renderBulkOperationUserErrors(userErrors: UserError[], headline: string): void;
export interface BulkOperationCancellationResult {
headline: string;
body?: TokenItem;
customSections?: {
body: {
list: {
items: string[];
};
}[];
}[];
renderType: 'success' | 'warning' | 'info';
}
/**
* Classifies the outcome of a cancellation request into a renderable payload.
*
* The engine intentionally stays command-agnostic: for an in-progress cancellation it returns just
* the headline and render type, leaving each command to append its own "check status" hint (which
* references that command's own `bulk status` invocation).
*
* @param operation - The bulk operation after the cancel request.
* @returns The headline, body, sections, and render type to use.
*/
export declare function formatBulkOperationCancellationResult(operation: NonNullable<GetBulkOperationByIdQuery['bulkOperation']>): BulkOperationCancellationResult;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/helpers.d.tsimport { AdminSession } from '../../session.js';
/**
* Normalizes a bulk operation ID to a GID.
*
* @param id - A numeric ID or a full GID.
* @returns The GID form of the ID.
*/
export declare function normalizeBulkOperationId(id: string): string;
/**
* Extracts the numeric ID from a bulk operation GID.
*
* @param gid - A GID like "gid://shopify/BulkOperation/123".
* @returns The numeric ID, or the original string if it isn't a recognized GID.
*/
export declare function extractBulkOperationId(gid: string): string;
/**
* Validates that a GraphQL document contains exactly one operation definition.
*
* @param graphqlOperation - The GraphQL query or mutation string to validate.
* @throws AbortError if the document doesn't contain exactly one operation or has syntax errors.
*/
export declare function validateSingleOperation(graphqlOperation: string): void;
/**
* Checks if a GraphQL operation is a mutation.
*
* @param graphqlOperation - The GraphQL query or mutation string to check.
* @returns True if the operation is a mutation, false otherwise.
* @throws AbortError if the operation has invalid GraphQL syntax.
*/
export declare function isMutation(graphqlOperation: string): boolean;
/**
* Options for resolving an API version.
*/
interface ResolveApiVersionOptions {
/** Admin session containing store credentials. */
adminSession: AdminSession;
/** The API version specified by the user. */
userSpecifiedVersion?: string;
/** Optional minimum version to use as a fallback when no version is specified. */
minimumDefaultVersion?: string;
}
/**
* Determines the API version to use based on the user provided version and the available versions.
* The 'unstable' version is always allowed without validation.
*
* @param options - Options for resolving the API version.
* @returns The resolved API version.
* @throws AbortError if the provided version is not allowed.
*/
export declare function resolveApiVersion(options: ResolveApiVersionOptions): Promise<string>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/query.d.ts/**
* Inputs for resolving a bulk operation's GraphQL query.
*/
interface ResolveBulkOperationQueryInput {
/** Inline GraphQL operation string. */
query?: string;
/** Path to a file containing the GraphQL operation. */
queryFile?: string;
}
/**
* Resolves the GraphQL operation for a bulk command from either an inline `--query` value or a
* `--query-file` path, validating that it's non-empty and contains exactly one operation.
*
* Centralizes the read-and-validate logic shared by the app and store bulk execute commands.
*
* @param input - The inline query and/or the query file path (exactly one is expected).
* @returns The validated GraphQL operation string.
* @throws AbortError if the value/file is empty or missing, or the operation is invalid.
* @throws BugError if neither input was provided (oclif's exactlyOne constraint should prevent this).
*/
export declare function resolveBulkOperationQuery(input: ResolveBulkOperationQueryInput): Promise<string>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/run-mutation.d.tsimport { BulkOperationRunMutationMutation } from '../../../../cli/api/graphql/bulk-operations/generated/bulk-operation-run-mutation.js';
import { AdminSession } from '../../session.js';
interface BulkOperationRunMutationOptions {
adminSession: AdminSession;
query: string;
variablesJsonl?: string;
version?: string;
}
/**
* Stages a JSONL variables file then starts a bulk mutation operation on the store.
*
* @param options - The admin session, mutation, JSONL variables, and optional API version.
* @returns The bulkOperationRunMutation result, including the created operation and any user errors.
*/
export declare function runBulkOperationMutation(options: BulkOperationRunMutationOptions): Promise<BulkOperationRunMutationMutation['bulkOperationRunMutation']>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/run-query.d.tsimport { BulkOperationRunQueryMutation } from '../../../../cli/api/graphql/bulk-operations/generated/bulk-operation-run-query.js';
import { AdminSession } from '../../session.js';
interface BulkOperationRunQueryOptions {
adminSession: AdminSession;
query: string;
version?: string;
}
/**
* Starts a bulk query operation on the store.
*
* @param options - The admin session, query, and optional API version.
* @returns The bulkOperationRunQuery result, including the created operation and any user errors.
*/
export declare function runBulkOperationQuery(options: BulkOperationRunQueryOptions): Promise<BulkOperationRunQueryMutation['bulkOperationRunQuery']>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/stage-file.d.tsimport { AdminSession } from '../../session.js';
interface StageFileOptions {
adminSession: AdminSession;
variablesJsonl?: string;
}
/**
* Uploads bulk mutation variables to a staged upload target and returns the staged upload key.
*
* @param options - The admin session and JSONL variables.
* @returns The staged upload key used as `stagedUploadPath` for the bulk mutation.
*/
export declare function stageFile(options: StageFileOptions): Promise<string>;
export {};
packages/cli-kit/dist/public/node/api/bulk-operations/watch-bulk-operation.d.tsimport { GetBulkOperationByIdQuery } from '../../../../cli/api/graphql/bulk-operations/generated/get-bulk-operation-by-id.js';
import { AdminSession } from '../../session.js';
import { AbortSignal } from '../../abort.js';
export declare const QUICK_WATCH_TIMEOUT_MS = 3000;
export declare const QUICK_WATCH_POLL_INTERVAL_MS = 300;
export type BulkOperation = NonNullable<GetBulkOperationByIdQuery['bulkOperation']>;
/**
* Polls a bulk operation briefly to surface its initial state, returning quickly so the caller can
* keep working while the operation runs in the background.
*
* @param adminSession - The admin session.
* @param operationId - The bulk operation ID to poll.
* @returns The latest known operation state.
*/
export declare function shortBulkOperationPoll(adminSession: AdminSession, operationId: string): Promise<BulkOperation>;
/**
* Polls a bulk operation until it reaches a terminal state or is aborted, updating the rendered
* status as it progresses.
*
* @param adminSession - The admin session.
* @param operationId - The bulk operation ID to watch.
* @param abortSignal - Signal used to stop watching early.
* @param onAbort - Callback invoked when the user aborts.
* @returns The latest known operation state.
*/
export declare function watchBulkOperation(adminSession: AdminSession, operationId: string, abortSignal: AbortSignal, onAbort: () => void): Promise<BulkOperation>;
Existing type declarationspackages/cli-kit/dist/public/common/version.d.ts@@ -1 +1 @@
-export declare const CLI_KIT_VERSION = "4.3.0";
\ No newline at end of file
+export declare const CLI_KIT_VERSION = "4.2.0";
\ No newline at end of file
packages/cli-kit/dist/public/node/ui.d.ts@@ -81,7 +81,10 @@ To see a list of supported npm commands, run:
* │ │
* ╰──────────────────────────────────────────────────────────╯
* [1] https://shopify.dev
- * [2] https://www.google.com/search?q=jh56t9l34kpo35tw8s28hn7s9s2xvzla01d8cn6j7yq&rlz=1C1GCEU_enUS832US832&oq=jh56t9l34kpo35tw8s28hn7s9s2xvzla01d8cn6j7yq&aqs=chrome.0.35i39l2j0l4j46j69i60.2711j0j7&sourceid=chrome&ie=UTF-8
+ * [2] https://www.google.com/search?q=jh56t9l34kpo35tw8s28hn7s
+ * 9s2xvzla01d8cn6j7yq&rlz=1C1GCEU_enUS832US832&oq=jh56t9l34kpo
+ * 35tw8s28hn7s9s2xvzla01d8cn6j7yq&aqs=chrome.0.35i39l2j0l4j46j
+ * 69i60.2711j0j7&sourceid=chrome&ie=UTF-8
* [3] https://shopify.com
*
*/
@@ -109,7 +112,8 @@ export declare function renderInfo(options: RenderAlertOptions): string | undefi
* │ • See your deployment and set it live [1] │
* │ │
* ╰──────────────────────────────────────────────────────────╯
- * [1] https://partners.shopify.com/1797046/apps/4523695/deployments
+ * [1] https://partners.shopify.com/1797046/apps/4523695/deploy
+ * ments
*
*/
export declare function renderSuccess(options: RenderAlertOptions): string | undefined;
|
isaacroldan
approved these changes
Jun 25, 2026
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.

What
Adds
shopify store bulk execute,shopify store bulk status, andshopify store bulk cancel— the same bulk-operation workflow asshopify app bulk, but running against a store using stored auth (shopify store auth) instead of requiring an app.How
Consumes the shared
@shopify/cli-kit/node/api/bulk-operationsengine introduced in #7896. The store package adds only its own orchestration:prepareBulkAdminContext— loads the stored store session into anAdminSessionshopify store bulk statushelp text--allow-mutationsopt-in gate (consistent withshopify store execute) instead of app's dev-store check —store bulkonly has a stored session, so it can't infer the store typeNew:
commands/store/bulk/{execute,status,cancel}.ts,services/store/bulk/*,bulkOperationFlagsinflags.ts, and command registration insrc/index.ts.Testing