Add shopify store create preview command#7558
Conversation
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
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 declarationsWe found no new type declarations in this PR Existing type declarationspackages/cli-kit/dist/public/node/hooks/postrun.d.ts@@ -1,3 +1,7 @@
+/**
+ * Postrun hook — uses dynamic imports to avoid loading heavy modules (base-command, analytics)
+ * at module evaluation time. These are only needed after the command has already finished.
+ */
import { Hook } from '@oclif/core';
/**
* Check if post run hook has completed.
@@ -5,21 +9,6 @@ import { Hook } from '@oclif/core';
* @returns Whether post run hook has completed.
*/
export declare function postRunHookHasCompleted(): boolean;
-/**
- * Wait for the postrun hook to finish (so auto-upgrade has a chance to run) and then
- * tree-kill the current process tree before exiting.
- *
- * Long-running interactive commands like need this when the user terminates
- * the command via or Ctrl+C. The dev sub-processes such as servers and watchers keep
- * the event loop alive, so even after oclif's postrun hook completes the node process
- * won't exit on its own and we have to the process tree. We must not do that
- * before the postrun hook has actually finished running auto-upgrade, otherwise we would
- * kill the upgrade mid-way while is still running.
- *
- * The flag is flipped at the very end of the hook after
- * resolves, so polling it here is safe.
- */
-export declare function waitForPostRunHookAndExit(): void;
export declare const hook: Hook.Postrun;
/**
* Auto-upgrades the CLI after a command completes, if a newer version is available.
packages/cli-kit/dist/private/node/ui/components/AutocompletePrompt.d.ts@@ -20,13 +20,6 @@ export interface AutocompletePromptProps<T> {
abortSignal?: AbortSignal;
infoMessage?: InfoMessageProps['message'];
groupOrder?: string[];
- /**
- * Throttle window in milliseconds applied to the search callback. Defaults to 400ms,
- * which is appropriate for remote/paginated backends. In-memory consumers (where the
- * search callback resolves synchronously) can pass 0 for instant filtering on every
- * keystroke.
- */
- searchDebounceMs?: number;
}
-declare function AutocompletePrompt<T>({ message, choices, infoTable, onSubmit, search, hasMorePages: initialHasMorePages, abortSignal, infoMessage, groupOrder, searchDebounceMs, }: React.PropsWithChildren<AutocompletePromptProps<T>>): ReactElement | null;
+declare function AutocompletePrompt<T>({ message, choices, infoTable, onSubmit, search, hasMorePages: initialHasMorePages, abortSignal, infoMessage, groupOrder, }: React.PropsWithChildren<AutocompletePromptProps<T>>): ReactElement | null;
export { AutocompletePrompt };
\ No newline at end of file
|
3c06e62 to
4aaf14e
Compare
83703d3 to
33fcd7e
Compare
4aaf14e to
a2d4ec2
Compare
33fcd7e to
20d8ae5
Compare
a2d4ec2 to
1899796
Compare
|
This PR seems inactive. If it's still relevant, please add a comment saying so. Otherwise, take no action. |

WHY are these changes introduced?
M1 of the Preview Store for AI Agent Surfaces initiative ships a CLI-first flow where an AI agent can ask the CLI to create a Shopify store, get back a storefront URL, and then keep iterating on the store via existing CLI commands — all without an account, an admin UI, or a browser hop.
Growth's prototype on
shop/world#708792adds the Core orchestrator (POST /services/preview-stores) that creates a placeholder identity, mints a realshpat_…Admin API token against theshopify-cli-connector-app, and returns a one-time-use magic link to admin. River put up a parallel CLI prototype (branchShopify/cli@preview-store/prototype) that called this endpoint via a new top-levelpreviewcommand group and required users to either pipe--jsonoutput into a file or pass--domain/--tokenflags into a custompreview executeclone ofstore execute.This PR ports the prototype onto the production CLI's
storenamespace and wires it through the existing stored-session machinery so we can delete the parallel transport entirely:shopify preview create→shopify store create preview(matches the language used in the DevTools kickoff and slack threads).shopify preview execute→ simply useshopify store execute --store <permanent-domain> …because the new command writes akind: 'preview'session into the same LocalStorage namespace thatstore executereads from.shopify preview claimis M2 work and is intentionally not included.The schema work that lets
kind: 'preview'round-trip safely is in #7557; this PR introduces the only producer of those sessions.WHAT is this pull request doing?
New command —
shopify store create preview(packages/store/src/cli/commands/store/create/preview.ts)Flags:
-n, --shop-nameSHOPIFY_FLAG_PREVIEW_STORE_SHOP_NAME--emailSHOPIFY_FLAG_PREVIEW_STORE_EMAIL@previewstore.invalidaddress.--countrySHOPIFY_FLAG_PREVIEW_STORE_COUNTRY"US".--core-urlSHOPIFY_FLAG_PREVIEW_STORE_CORE_URLhttps://app.shop.dev).--cli-usernameSHOPIFY_FLAG_PREVIEW_STORE_CLI_USERNAME--cli-secretSHOPIFY_FLAG_PREVIEW_STORE_CLI_SECRET-j, --jsonSHOPIFY_FLAG_JSONCore HTTP client (
services/store/create/preview/client.ts)Uses
shopifyFetchtoPOST /services/preview-storeswith the snake_case body Core expects (shop_name,email,country). Translates the response into a camelCase type and rejects non-2xx responses, non-JSON bodies, and responses missing any of the five required identifiers. Defaults —https://app.shop.devand the basic-auth pairpreview-store-cli/preview-store-cli-dev— match the dev-only secret hardcoded inServices::PreviewStoresControllerand are explicitly flagged in code comments as "needs productionization before non-developer release".Orchestrator (
services/store/create/preview/index.ts)After the client returns, the orchestrator persists the response as a stored store-auth session via
setStoredStoreAppSessionwith:kind: 'preview',preview: { placeholderAccountUuid, coreUrl, magicLinkUrl, magicLinkExpiresAt }— surfacing the metadata Add preview-store discriminator to stored store auth sessions #7557 introduced.userId: \placeholder:`` — non-numeric and prefixed so analytics filters can isolate placeholder identities and there's no collision with PKCE-issued sessions (which use numeric Shopify user ids).scopes: []— Core does not surface the granted scope list; the array is a sentinel and is not consulted bystore execute(which only validates against the live Admin API). Preview sessions never go through the recovery path that suggests--scopes.magicLinkExpiresAtderived locally asacquiredAt + 30 minutesto matchPreviewStores::Create::MAGIC_LINK_TTL.recordStoreFqdnMetadatais called twice (unvalidated → validated) to mirror the analytics shape PKCE auth emits, andsetLastSeenUserIdis updated so the nextstore executeagainst the new store finds the right session.Result presenter (
services/store/create/preview/result.ts)Text output renders the shop id, permanent domain, placeholder UUID, and magic link, plus a next-step suggestion to run
shopify store execute --store <permanent-domain> --query '{ shop { name } }'. The admin token is intentionally not rendered in text mode to avoid accidental copy-paste leakage. JSON output (used by AI agents) does include it because the agent needs the full session shape.Wiring
packages/store/src/index.tsregisters'store:create:preview'.packages/cli/README.mdandpackages/cli/oclif.manifest.jsonregenerated viapnpm refresh-manifests.StoredStoreSessionKindandStoredPreviewStoreSessionfrom Add preview-store discriminator to stored store auth sessions #7557 stay module-internal;PreviewStoreCreateRequestandCreatePreviewStoreInputare also kept module-internal. (Knip's check is clean.)Out of scope
shopify store create preview --reuse <fqdn>and the corresponding "re-mint expired token" code path that Add preview-store discriminator to stored store auth sessions #7557's preview-recovery message hints at. The token's lifetime is whatever Core assigns — the M1 prototype does not surface an expiry to the CLI, so we treat preview sessions as non-expiring until Growth wires that through.shopify store listintegration (raised in Daniel's thread). The session is stored wherestore listwill read it from once that command lands; no extra work needed here.How to test your changes?
Unit tests:
End-to-end on a local Core rig (per River's demo and the M1 prototype setup in
shop/world#708792):Bring up Core with the preview-store rig (
dev rig preview-store-minagainstshop/worldPR 708792 checked out locally).From this branch:
pnpm shopify store create preview --shop-name my-preview --json | tee /tmp/preview.jsonThe new store can immediately be queried — no
shopify store authneeded:Open
magicLinkUrlfrom the JSON output in a fresh browser session to land in admin without an Identity login (one-time use, ~30 minutes).The pre-existing 2 callback test failures in
packages/store/src/cli/services/store/auth/callback.test.ts("doesn't"vs"does not"string mismatch) are unchanged onmainand unrelated to this PR.Post-release steps
None.
Checklist
shopifyFetchandLocalStoragefrom cli-kit, no platform-specific surface.packages/cli/README.mdregenerated; user-facing docs on shopify.dev forstore create previewto follow once the command moves out of M1 prototype defaults.recordStoreFqdnMetadataandsetLastSeenUserIdmirror the PKCE-auth shape; theplaceholder:<uuid>userId prefix is intentional to make placeholder-issued sessions filterable in analytics.patchfor bug fixes ·minorfor new features ·majorfor breaking changes) and added a changeset withpnpm changeset add—minorbump on@shopify/storeand@shopify/cli(new top-level command).