Skip to content

Handle Trust Battery challenges in theme commands - #8562

Open
graygilmore wants to merge 5 commits into
iain/theme-api-route-mutationsfrom
gg-theme-trust-battery-cli
Open

graygilmore wants to merge 5 commits into
iain/theme-api-route-mutationsfrom
gg-theme-trust-battery-cli

Conversation

@graygilmore

@graygilmore graygilmore commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #8897. That PR routes the theme mutations through requestThemeAdminDoc, so this PR only wraps that one function.

WHY are these changes introduced?

Core runs a Trust Battery session decision on every theme mutation (themeFilesUpsert, themeFilesDelete, themePublish, themeCreate, ...). It also runs it for theme file reads that pass a sessionId, but the CLI's read queries send no sessionId. When the session looks risky the decision returns an identity challenge. Admin Web can show that challenge; the CLI can't, so Core currently suppresses challenges for CLI callers. That leaves the CLI as a way around the check.

Core is going to start surfacing challenges to the CLI behind a shop flag (shop/world#2068823), gated on the CLI version from the User-Agent, so this can be dogfooded before release and old CLIs do not receive unsupported challenges. This PR is the CLI half.

WHAT is this pull request doing?

Theme Admin API requests now recognise a CHALLENGE_REQUIRED GraphQL error and:

  1. open the redirect_to challenge URL in the browser (or show the link if the browser can't be opened),
  2. show a banner and wait for Enter,
  3. retry the request once.

What it looks like mid-theme push:

Uploading files to remote theme [0%] ...
╭─ info ─────────────────────────────────────────────────────────╮
│                                                                │
│  Verify your identity to continue.                             │
│                                                                │
│  A verification page opened in your browser.                   │
│                                                                │
│  Next steps                                                    │
│    1. Complete the verification in your browser, logged in     │
│       with the same account you use in Shopify CLI.            │
│    2. Press Enter here once you have completed it.             │
│                                                                │
╰────────────────────────────────────────────────────────────────╯

then after Enter:

╭─ success ──────────────────────────────────────────────────────╮
│                                                                │
│  Continuing.                                                   │
│                                                                │
╰────────────────────────────────────────────────────────────────╯
Uploading files to remote theme [100%] ...

A second challenge on the retry aborts with the URL instead of looping. In CI, without a TTY, with --no-input, or with --json, the CLI aborts straight away with the URL and next steps.

Some details worth knowing:

  • Only mutations are challenged today. Core runs the decision for files/file reads only when the query passes sessionId (breadth_resolvers/online_store_theme/files.rb). getThemeFileChecksums and getThemeFileBodies send none, so fetchChecksums, the theme dev poll, and theme pull are not challenged. Reads still go through requestThemeAdminDoc (theme mutations moved there in Route theme mutations through requestThemeAdminDoc #8897), so a read challenge would get the same handling. That case is not tested end to end.
  • The browser must use the same account. Identity rejects a verification made with a different account (mismatched_identity_account), so the banner and the abort message both say to log in with the same account you use in Shopify CLI.
  • Non-interactive modes abort instead of hanging. --json (and SHOPIFY_FLAG_JSON=1) turns the banner into a single stderr JSON diagnostic and waitForEnter would block with no visible prompt, so JSON output mode now aborts with the URL like CI and non-TTY already did.
  • theme dev exits cleanly on Ctrl+C at the prompt. The prompt's Ctrl+C throws AbortSilentError, which the dev poll loop and upload catches were rendering as an empty "Error while polling for changes." / "Failed to upload file" banner. renderThrownError now returns early for AbortSilentError; theme dev's own Ctrl+C handler does the exit.
  • Scoped to theme requests. The handling lives in requestThemeAdminDoc in cli-kit/.../themes/api.ts. app dev with a theme app extension also reaches it: HostThemeManager.createHostTheme calls themeCreate, and Core runs SessionTrustedToCreateTheme on it. That retry loop caught every error, so Ctrl+C at the prompt or a failed verification started a new attempt, up to four prompts. The challenge aborts are now a TrustChallengeError, the themes API exports isTrustChallengeError, and the loop rethrows it and AbortSilentError. Other theme creation errors are still retried. Not verified: how the banner renders under app dev's renderTasks spinner.
  • One prompt for concurrent requests. theme push fires upload batches in parallel and they all get challenged together. A shared in-flight promise means the browser opens once, every waiting batch retries after the same Enter, and the success banner renders once. A request that starts while a challenge is pending (e.g. saving another file in theme dev) waits for it first, so it's sent once instead of being challenged and retried too. A batch that was already in flight when the user pressed Enter, and gets its challenge only after that, retries once without a second prompt: each request notes how many challenges were completed when it started.
  • Enter/Space only. theme dev keeps stdin in raw mode and binds t/p/e/g on the same stream. Instead of cli-kit's any-key keypress(), a small waitForEnter ignores other keys and leaves raw mode as it found it, so answering the prompt never fires a shortcut. Ctrl+C still aborts.
  • Everything is a banner, and keys are compared as text. Both because of Ink. When theme push's progress bar is running, Ink redraws its frame and erases plain lines written underneath it, so the "press Enter" instruction lives inside the info banner rather than a separate outputInfo. Ink's useInput also calls stdin.setEncoding('utf8'), so stdin emits strings instead of Buffers; waitForEnter normalises the chunk to text before matching. I found both of these tophatting, the first version used outputInfo and byte comparisons and looked frozen under theme push.
  • Only Admin-host URLs are honoured. redirect_to must be https:// on adminFqdn(); anything else falls through as a normal error, same as Admin Web.
  • Theme Access / custom-app tokens never get a challenge (no identity session on Core's side), so CI pushes using shptka_/shpat_ are unaffected.

Analytics: theme-api:trust-challenge:{required,browser-opened,browser-not-opened,acknowledged,retry-challenged,non-interactive} via recordEvent.

How to manually test your changes?

Local end-to-end, done 2026-10-14. Setup: local Trust Battery, Identity, admin-web, storefront, and Core on shop/world#2068823. Flag on for shop 1. CLI from this branch with packages/cli-kit/package.json set to 4.10.0 (not committed; rebuild with pnpm nx build cli --skip-nx-cache after each version change). A Trust Battery decision override forces challenge (identity_session_user_verification) on SessionTrustedToUpsertThemeFiles for the CLI session.

  1. Push with no override: succeeds.
  2. Override active, push: the browser opens https://admin.shop.dev/challenges/user_verification?token=… and the CLI shows the info banner above. Core logs:
    theme_trust_battery_challenge_surfaced: Trust Battery challenge surfaced to a challenge-capable theme API caller
    Attributes: api_version=2026-10, shop_id=1, api_client_id=4988523, mutation_name=ThemeFilesUpsert, cli_version=4.10.0
    
  3. Verification in the browser with the same account. The CLI session's battery then has identity_session_user_verified = success. Override off, Enter: the success banner, then "The theme … was pushed successfully."
  4. Override on again, Enter without verifying: "Shopify still needs to verify your identity before it can continue." with the URL. This run showed that the old success text ("Verification complete.") rendered before the failed retry, so the banner now says "Continuing."
  5. Override on:
    • --json: exit 1, no browser, the abort with the URL as a JSON error document on stdout.
    • CI=1 and --no-input: exit 1, the abort banner with the URL.
    • CLI built as 4.9.3: push succeeds. Core logs theme_trust_battery_challenge_suppressed with cli_version=4.9.3.
    • 4.10.0 with the flag off: push succeeds, suppressed with cli_version=4.10.0.
  6. theme dev, override on: the initial-sync upload (themeFilesUpsert) is challenged and the prompt shows under the preview links after about 4 s. t does not answer the prompt (theme dev's own t shortcut still opens the preview). Ctrl+C at the prompt exits with code 0 and no "Error while polling" / "Failed to upload" / "Failed to perform the initial theme synchronization" banner. Not verified: a challenge on a poll or a read. The poll reads send no sessionId, so Core does not challenge them.

Out of scope, seen on the base branch too: in --json mode, an upload-phase error prints its JSON document twice, and theme dev renders the initial-sync error banner twice.

Without local Core you can fake the response:

  1. In packages/cli-kit/src/public/node/api/admin.ts, temporarily make adminRequestDoc reject once with:
    throw new ClientError(
      {status: 200, errors: [{message: 'Challenge Required', extensions: {code: 'CHALLENGE_REQUIRED', redirect_to: 'https://admin.shopify.com/challenges/user_verification?token=test'}}] as any},
      {query: ''},
    )
  2. pnpm shopify theme push --store <store> --development (Identity login, not --password).
  3. Expect: a browser tab opens on admin.shopify.com/challenges/... and the info banner above appears. Press t — nothing happens. Press Enter — the success banner appears and the push completes.
  4. Run with CI=1 (or --no-input, or --json) and expect an immediate abort listing the challenge URL as a next step.

Post-release steps

Core PR shop/world#2068823 pins MIN_CHALLENGE_CAPABLE_CLI_VERSION to the first release that contains this PR's merge commit (expected 4.10.0). If this ships under a different version, that constant needs to change before the Core PR merges.

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 — I've identified the correct bump type (patch for bug fixes · minor for new features · major for breaking changes) and added a changeset with pnpm changeset add

@github-actions github-actions Bot added the Area: @shopify/cli @shopify/cli package issues label Sep 16, 2026
@graygilmore
graygilmore force-pushed the gg-theme-trust-battery-cli branch 4 times, most recently from 7d6db8a to 945684a Compare September 24, 2026 00:45
@iaineh
iaineh force-pushed the gg-theme-trust-battery-cli branch from 945684a to de3151f Compare October 14, 2026 19:23
@iaineh iaineh changed the title Handle Trust Battery challenges for theme mutations Handle Trust Battery challenges in theme commands Oct 14, 2026
@iaineh
iaineh changed the base branch from main to iain/theme-api-route-mutations October 14, 2026 19:23

@iaineh iaineh left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Notes on the larger decisions in this PR, for reviewers.

): Promise<TResult> {
try {
const response = await adminRequestDoc(options)
const response = await withTrustChallengeRetry(() => adminRequestDoc(options))

@iaineh iaineh Oct 14, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is the only api.ts change in this PR. #8897 moves the six theme mutations to requestThemeAdminDoc, so theme reads and mutations all go through this one wrapper.

Today Core challenges only the mutations. It runs the decision for files/file reads only when the query passes sessionId (breadth_resolvers/online_store_theme/files.rb), and getThemeFileChecksums / getThemeFileBodies send none. So fetchChecksums, the theme dev poll, and theme pull are not challenged. If Core starts to challenge them, they get the same handling here. That read case is not tested end to end.

Edited: an earlier version of this comment said that Core challenges reads. That was wrong.

Comment on lines +126 to +135
if (isCI() || !isTTY() || commandEventOutputMode() === 'json') {
recordEvent('theme-api:trust-challenge:non-interactive')
throw new AbortError('Shopify needs to verify your identity before it can continue.', undefined, [
[
'Open',
{link: {url: challengeUrl}},
"in a browser where you're logged in to Shopify with the same account you use in Shopify CLI, complete the verification, then run the command again.",
],
])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JSON output mode aborts, the same as CI and no TTY. With --json, -j, or SHOPIFY_FLAG_JSON=1, renderInfo becomes one stderr JSON line and waitForEnter blocks with no visible prompt. The command looks hung. A script can only use an abort with the URL.

--no-input needs no extra check: isTTY() is false then (terminalSupportsPrompting).

Test: "aborts with the challenge URL instead of prompting in JSON output mode".

Comment on lines +142 to +158
renderInfo({
headline: 'Verify your identity to continue.',
body: opened
? 'A verification page opened in your browser.'
: 'Shopify needs to verify your identity before it can continue.',
link: opened ? undefined : {label: 'Open the verification page', url: challengeUrl},
nextSteps: [
['Complete the verification in your browser, logged in with the same account you use in Shopify CLI.'],
['Press', {userInput: 'Enter'}, 'here once you have completed it.'],
],
orderedNextSteps: true,
})

await waitForEnter(options.stdin ?? process.stdin)

recordEvent('theme-api:trust-challenge:acknowledged')
renderSuccess({headline: 'Verification complete. Continuing.'})

@iaineh iaineh Oct 14, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copy changes:

  1. "apply this theme change" is now "continue". The text no longer names the operation, so it stays correct for any theme request. Today only mutations get the challenge.
  2. The text says to use the same account in the browser as in the CLI. Identity stops with mismatched_identity_account if the accounts differ.

Edited: an earlier version said the reason was that Core also challenges reads. That was wrong: the CLI reads send no sessionId, so Core does not challenge them.

* @param error - The error to render.
*/
export function renderThrownError(headline: string, error: Error | AbortError) {
if (error instanceof AbortSilentError) return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ctrl+C at the prompt during theme dev rejects with AbortSilentError. That class extends FatalError, not AbortError, so this function rendered an empty "Error while polling for changes." or "Failed to upload file" banner. The early return covers the poll loop, the initial-sync abort, and the file-save catches. theme dev's own Ctrl+C handler (createKeypressHandler → onClose) does the exit.

Test: "renders nothing for AbortSilentError".

@iaineh
iaineh marked this pull request as ready for review October 15, 2026 12:22
@iaineh
iaineh requested review from a team as code owners October 15, 2026 12:22
@iaineh
iaineh force-pushed the gg-theme-trust-battery-cli branch 3 times, most recently from 8ab28d3 to 45a041a Compare October 15, 2026 13:10

const TRUST_CHALLENGE_ERROR_CODE = 'CHALLENGE_REQUIRED'

interface TrustChallengeOptions {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: TrustChallengeOptions.stdin is only used by one test, and that test mocks waitForEnter anyway. isTTY() also ignores it, so passing a custom stdin wouldn't change the TTY check the way throwInNonTTY({stdin}) does elsewhere. I'd drop the option and let waitForEnter default to process.stdin.

stdin?: typeof process.stdin
}

let pendingChallenge: Promise<void> | undefined

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: a one-line comment on why pendingChallenge can be process-wide would help: only Identity sessions get challenged, and every multi-environment theme command requires --password, so a process that can be challenged only ever has one Identity session.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

One shared prompt per process is safe. Core challenges only Identity sessions, and every multi-environment theme command needs --password. So a process that can get a challenge has only one Identity session.

const challengeUrl = await trustChallengeUrl(error)
if (!challengeUrl) throw error

await completeTrustChallenge(challengeUrl, options)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One ordering the concurrency test misses: two batches are in flight, the first gets challenged and the user presses Enter, and the second batch's challenge response only arrives after pendingChallenge is cleared. That opens a second tab and prompt for a request that was sent before the verification. Probably rare, but large upload batches make it more likely. One fix: keep a counter of completed challenges, note it when a request starts, and if a challenge comes back for a request that started before the last completed challenge, retry it once without prompting.

const challengeUrl = await trustChallengeUrl(error)
if (!challengeUrl) throw error

recordEvent('theme-api:trust-challenge:retry-challenged')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not blocking: if the retry is challenged again, we abort partway through a theme push. When the verification just hasn't caught up yet, that leaves a half-uploaded theme. Did you consider showing the prompt again (say up to 3 times) before aborting? Fine as is if a second challenge usually means something is actually wrong.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A second challenge right after Enter usually means the verification is not complete. One retry and then an abort with the URL keeps the behaviour clear. The abort message tells the user to run the command again.

): Promise<TResult> {
try {
const response = await adminRequestDoc(options)
const response = await withTrustChallengeRetry(() => adminRequestDoc(options))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

app dev with a theme app extension goes through this too: HostThemeManager.createHostTheme calls themeCreate, and Core runs SessionTrustedToCreateTheme on it. Its retry loop catches everything, so a Ctrl+C at the prompt (AbortSilentError) or the "still needs to verify" abort gets swallowed and retried. That can mean up to four browser tabs and prompts before it gives up, all inside a renderTasks spinner. I think the loop should rethrow AbortError/AbortSilentError (or at least the challenge ones), and the description should drop "App commands are unchanged". Could you also check how the banner renders under the spinner?

graygilmore and others added 4 commits October 16, 2026 14:19
Core runs a Trust Battery session decision on every theme
mutation. Admin Web can present the resulting identity challenge,
but the CLI cannot, so Core currently suppresses challenges for CLI
callers. That leaves the CLI as a way around the check, and Core will
start surfacing challenges to CLI versions that can handle them.

Theme Admin API requests now recognise a CHALLENGE_REQUIRED GraphQL
error, open the challenge URL in the browser, wait for the user
to press Enter once they have completed it, and retry the request
once. A second challenge on the retry aborts with the URL instead of
looping. Non-interactive runs (CI or no TTY) abort immediately with
the URL and next steps.

Concurrent requests, such as the parallel upload batches of `theme
push`, share one prompt so the browser opens once and every waiting
batch retries after the same verification. A request that starts while
a challenge is still pending waits for it first, so it is sent once
instead of being challenged and retried too.

The prompt accepts Enter or Space only. `theme dev` keeps stdin in
raw mode and binds single-letter shortcuts on the same stream; the
dedicated wait ignores other keys and leaves raw mode as it found it,
so answering the prompt never fires a shortcut.

Only challenge URLs on the Admin host are honoured; anything else
falls through to normal error handling. The handling is intentionally
scoped to the theme API layer; app commands are unchanged.
- Abort instead of prompting in JSON output mode
- Use copy that does not name the operation, and say that the browser must use the same account as the CLI
- Render nothing for AbortSilentError in theme dev polling and upload errors
- Cover @shopify/theme with the challenge changeset
The banner renders before the retry. The retry can still get a challenge. "Continuing." does not say that the verification is complete.
trustChallengeUrl now reads the errors with extractGraphQLErrors instead of its own copy.
@iaineh
iaineh force-pushed the gg-theme-trust-battery-cli branch from 45a041a to 3a65a93 Compare October 16, 2026 12:22
@iaineh
iaineh force-pushed the iain/theme-api-route-mutations branch from 1a3a3cd to 309f40b Compare October 16, 2026 12:22
- A request that was already in flight when a challenge was completed
  retries without a second prompt. A counter of completed challenges is
  noted when each request starts, so a late challenge response for an
  earlier request does not open another browser tab.
- Remove `TrustChallengeOptions.stdin`. Only one test used it, and
  `isTTY()` did not read it. `waitForEnter` uses its `process.stdin` default.
- The two challenge aborts are a `TrustChallengeError` (an `AbortError`),
  and the themes API exports `isTrustChallengeError`. The `app dev` host
  theme retry loop rethrows it and `AbortSilentError`, so Ctrl+C or a failed
  verification stops theme creation instead of prompting up to four times.
  Other theme creation errors are still retried.
@github-actions

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

packages/cli-kit/dist/private/node/themes/trust-challenge.d.ts
import { AbortError } from '../../../public/node/error.js';
/**
 * The abort that ends a Trust Battery challenge: the session cannot answer it, or
 * the request was challenged again after the verification.
 */
export declare class TrustChallengeError extends AbortError {
}
/**
 * Runs a theme Admin API request and, when Core answers with a Trust Battery
 * challenge, waits for the user to complete it in the browser before retrying once.
 *
 * A request that starts while another request's challenge is still being completed
 * waits for that challenge first, so it is sent once instead of being challenged
 * itself and then retried. A request that was already in flight when a challenge
 * was completed retries without a second prompt.
 *
 * @param request - The request to run and possibly retry.
 * @returns The result of the request.
 */
export declare function withTrustChallengeRetry<T>(request: () => Promise<T>): Promise<T>;
/**
 * Extracts the challenge URL from a Trust Battery `CHALLENGE_REQUIRED` GraphQL error.
 *
 * Only URLs on the Admin host are trusted. Anything else is not treated as a
 * challenge and the original error surfaces through normal error handling.
 *
 * @param error - The error thrown by an Admin API request.
 * @returns The challenge URL, or undefined when the error is not a trust challenge.
 */
export declare function trustChallengeUrl(error: unknown): Promise<string | undefined>;
packages/cli-kit/dist/private/node/ui/wait-for-enter.d.ts
/**
 * Waits for Enter (or Space) on stdin and ignores every other key. Ctrl+C rejects
 * with AbortSilentError so the process exits the same way it would from any prompt.
 *
 * Raw mode is left the way it was found. Long-running commands such as `theme dev`
 * keep stdin raw for their own single-key shortcuts, and the accepted keys are ones
 * those commands do not bind, so pressing Enter here never triggers a shortcut.
 *
 * Chunks are compared as text rather than bytes: an active Ink renderer (for example
 * the `theme push` progress bar) calls `stdin.setEncoding('utf8')`, after which stdin
 * emits strings instead of Buffers.
 *
 * @param stdin - The stream to read from; defaults to process.stdin.
 * @returns A promise that resolves when Enter or Space is pressed.
 */
export declare function waitForEnter(stdin?: typeof process.stdin): Promise<void>;

Existing type declarations

packages/cli-kit/dist/private/node/api/graphql.d.ts
@@ -1,4 +1,4 @@
-import { Variables } from 'graphql-request';
+import { ClientError, Variables } from 'graphql-request';
 export declare function debugLogRequestInfo(api: string, query: string, url: string, variables?: Variables, headers?: Record<string, string>): void;
 export declare function sanitizeVariables(variables: Variables): string;
 /**
@@ -11,4 +11,5 @@ export declare function sanitizeVariables(variables: Variables): string;
  * the full JSON representation if no messages can be extracted.
  */
 export declare function extractGraphQLErrorMessages(errors: any[] | undefined): string | undefined;
-export declare function errorHandler(api: string): (error: unknown, requestId?: string) => unknown;
\ No newline at end of file
+export declare function errorHandler(api: string): (error: unknown, requestId?: string) => unknown;
+export declare function extractGraphQLErrors(error: unknown): NonNullable<ClientError['response']['errors']>;
\ No newline at end of file
packages/cli-kit/dist/public/node/themes/api.d.ts
@@ -34,6 +34,15 @@ export declare function metafieldDefinitionsByOwnerType(type: MetafieldOwnerType
     };
 }[]>;
 export declare function passwordProtected(session: AdminSession): Promise<boolean>;
+/**
+ * Whether the error ends a Trust Battery challenge on a theme request. A caller that
+ * retries theme requests must rethrow it, or the user is asked to verify again for
+ * each attempt.
+ *
+ * @param error - The error to check.
+ * @returns True when the error ends a Trust Battery challenge.
+ */
+export declare function isTrustChallengeError(error: unknown): boolean;
 type OnlineStoreThemeFileBody = {
     __typename: 'OnlineStoreThemeFileBodyBase64';
     contentBase64: string;
packages/cli-kit/dist/public/node/themes/theme-manager.d.ts
@@ -11,12 +11,5 @@ export declare abstract class ThemeManager {
     findOrCreate(name?: string, role?: Role): Promise<Theme>;
     fetch(name?: string, role?: Role): Promise<Theme | undefined>;
     generateThemeName(context: string): string;
-    create(themeRole?: Role, themeName?: string): Promise<{
-        createdAtRuntime: boolean;
-        id: number;
-        name: string;
-        processing: boolean;
-        role: string;
-        src?: string;
-    }>;
+    create(themeRole?: Role, themeName?: string): Promise<Theme>;
 }
\ No newline at end of file

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

Area: @shopify/cli @shopify/cli package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants