Preview Stores prototype: default store list to Business Platform with local fallback#7612
Draft
alfonso-noriega wants to merge 1 commit into
Draft
Preview Stores prototype: default store list to Business Platform with local fallback#7612alfonso-noriega wants to merge 1 commit into
store list to Business Platform with local fallback#7612alfonso-noriega wants to merge 1 commit into
Conversation
This was referenced May 22, 2026
Contributor
Author
|
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. |
store list to Business Platform with local fallback
4 tasks
c6f8202 to
983fb9f
Compare
3b34956 to
767639c
Compare
983fb9f to
a1a9286
Compare
767639c to
4b286e3
Compare
Reworks `store list` to query BP across every org the logged-in user has CLI access to, listing all accessible shops (not just app-dev sandboxes). Adds a `--source` flag with `local` opt-out for offline use and for placeholder sessions where BP returns empty by design (per Donald's shop/world#730858 stance). Surfaces a placeholder-aware notice when BP can't resolve the current user, suggesting --source local instead of silently falling back.
4b286e3 to
f10fe32
Compare
a1a9286 to
482efd6
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 declarationsWe found no new type declarations in this PR Existing type declarationspackages/cli-kit/dist/private/node/constants.d.ts@@ -28,6 +28,8 @@ export declare const environmentVariables: {
spinAppHost: string;
organization: string;
identityToken: string;
+ identityTokenUserId: string;
+ identityTokenExpiresAt: string;
refreshToken: string;
otelURL: string;
themeKitAccessDomain: string;
packages/cli-kit/dist/public/node/environment.d.ts@@ -33,10 +33,11 @@ export declare function getBackendPort(): number | undefined;
*
* @returns The identity token information in case it exists.
*/
-export declare function getIdentityTokenInformation(): {
+export declare function getIdentityTokenInformation(env?: NodeJS.ProcessEnv): {
accessToken: string;
refreshToken: string;
userId: string;
+ expiresAt?: Date;
} | undefined;
/**
* Checks if the JSON output is enabled via flag (--json or -j) or environment variable (SHOPIFY_FLAG_JSON).
packages/cli-kit/dist/public/node/local-storage.d.ts@@ -34,6 +34,19 @@ export declare class LocalStorage<T extends Record<string, any>> {
* @throws BugError if an unexpected error occurs.
*/
delete<TKey extends keyof T>(key: TKey): void;
+ /**
+ * Get every pair currently held in the local storage.
+ *
+ * Useful for callers that need to enumerate all stored values without knowing the
+ * full set of keys in advance (for example, a command iterating over every
+ * stored session). The package stores its entire state as a single JSON
+ * object, so this is just a typed wrapper around that object.
+ *
+ * @returns An array of tuples.
+ * @throws AbortError if a permission error occurs.
+ * @throws BugError if an unexpected error occurs.
+ */
+ entries(): [keyof T, T[keyof T]][];
/**
* Clear the local storage (delete all values).
*
packages/cli-kit/dist/public/node/session.d.ts import { AdminAPIScope, AppManagementAPIScope, BusinessPlatformScope, EnsureAuthenticatedAdditionalOptions, PartnersAPIScope, StorefrontRendererScope } from '../../private/node/session.js';
+import type { Session as IdentityStoredSession, Sessions } from '../../private/node/session/schema.js';
/**
* Session Object to access the Admin API, includes the token and the store FQDN.
*/
export interface AdminSession {
token: string;
storeFqdn: string;
}
/**
* Session Object for Partners API and App Management API access.
*/
export interface Session {
token: string;
businessPlatformToken: string;
accountInfo: AccountInfo;
userId: string;
}
export type AccountInfo = UserAccountInfo | ServiceAccountInfo | UnknownAccountInfo;
/**
* Records the user ID that should be attached to command analytics for this process.
*
* @param userId - User identifier to report on the command analytics event.
*/
export declare function setLastSeenUserId(userId: string): void;
interface UserAccountInfo {
type: 'UserAccount';
email: string;
}
interface ServiceAccountInfo {
type: 'ServiceAccount';
orgName: string;
}
interface UnknownAccountInfo {
type: 'UnknownAccount';
}
/**
* Type guard to check if an account is a UserAccount.
*
* @param account - The account to check.
* @returns True if the account is a UserAccount.
*/
export declare function isUserAccount(account: AccountInfo): account is UserAccountInfo;
/**
* Type guard to check if an account is a ServiceAccount.
*
* @param account - The account to check.
* @returns True if the account is a ServiceAccount.
*/
export declare function isServiceAccount(account: AccountInfo): account is ServiceAccountInfo;
/**
+ * Diagnostic snapshot of the currently-active CLI session. Returned by
+ * `getCurrentSessionInfo`. Intended for `shopify auth whoami` and similar
+ * inspection commands; not part of the normal request-execution path.
+ *
+ * All token values are masked to their first 8 characters + length to avoid
+ * leaking credentials into logs/screenshots, while still letting an operator
+ * eyeball that a token is present and roughly which one.
+ */
+export interface CurrentSessionInfo {
+ /** True when a Sessions row resolves to the current session id. */
+ loggedIn: boolean;
+ /** Identity FQDN this run resolved to (e.g. `accounts.shopify.com`, `identity.shop.dev`). */
+ identityFqdn: string;
+ /** Active session id (= bucket key in `Sessions[fqdn]`). */
+ userId?: string;
+ /** Heuristic: `userId` looks like a UUID and matches the imported placeholder convention. */
+ looksLikePlaceholder?: boolean;
+ /** Display alias from the identity record (typically the user email, undefined for placeholders). */
+ alias?: string;
+ /** Number of scopes claimed by the identity token. */
+ scopeCount?: number;
+ /** Scopes claimed by the identity token. */
+ scopes?: string[];
+ /** Masked preview of the identity access token + raw length. */
+ identityToken?: {
+ preview: string;
+ length: number;
+ expiresAt: string;
+ };
+ /** Whether the identity refresh token is present + its length. */
+ refreshToken?: {
+ present: true;
+ length: number;
+ } | {
+ present: false;
+ };
+ /** Per-audience application tokens cached for this session. */
+ applications?: {
+ appId: string;
+ preview: string;
+ length: number;
+ expiresAt: string;
+ storeFqdn?: string;
+ }[];
+ /**
+ * The raw, unredacted session row read from disk. Populated only when the
+ * caller explicitly opts in via `getCurrentSessionInfo({raw: true})`. Use
+ * sparingly — contains live access tokens and refresh tokens.
+ */
+ raw?: IdentityStoredSession;
+ /**
+ * The full `Sessions` blob (all fqdns, all users) read from disk. Populated
+ * only when `{raw: true}`. Useful for diagnosing why a particular session
+ * is/isn't being resolved as the current one.
+ */
+ rawAllSessions?: Sessions;
+}
+/**
+ * Options for `getCurrentSessionInfo`.
+ */
+export interface GetCurrentSessionInfoOptions {
+ /**
+ * When true, include the raw (unredacted) session row and the full Sessions
+ * blob on disk. Contains live tokens — do not log this in shared channels.
+ * Intended for `shopify auth whoami --raw` and equivalent diagnostic uses.
+ */
+ raw?: boolean;
+}
+/**
+ * Read the currently-active CLI session from disk and return a non-secret
+ * snapshot of its shape. Does NOT validate scopes, refresh expired tokens, or
+ * make any network calls — it's a pure inspection of `sessionStore.fetch()`.
+ *
+ * Returns `{ loggedIn: false }` when there is no current session row on disk.
+ *
+ * @param options - Optional flags. Pass `{raw: true}` to include unredacted
+ * tokens and the full Sessions blob in the returned object.
+ */
+export declare function getCurrentSessionInfo(options?: GetCurrentSessionInfoOptions): Promise<CurrentSessionInfo>;
+/**
+ * A bootstrap payload describing a backend-issued Identity OAuth session that
+ * the CLI should adopt as if `auth login` had just succeeded.
+ *
+ * Used by `importIdentitySession` to make `preview create` (and similar
+ * server-driven account-provisioning flows) leave the CLI authenticated with
+ * a real `IdentityToken` + `refreshToken` + per-application tokens, persisted
+ * under the standard `Sessions[identityFqdn][userId]` storage.
+ */
+export interface IdentitySessionBootstrap {
+ /** Identity access token (`shpat_...`-style or Identity-issued). Required. */
+ accessToken: string;
+ /** Identity refresh token. Required. */
+ refreshToken: string;
+ /** When the `accessToken` is expected to expire. Required. */
+ expiresAt: Date;
+ /**
+ * The Identity-side user id this session represents. Optional; when omitted,
+ * a deterministic UUID derived from `accessToken` is used. For placeholder
+ * accounts the backend should pass the placeholder UUID directly so the
+ * resulting bucket lines up with `ResourceOwner` rows on the server.
+ */
+ userId?: string;
+ /**
+ * Per-shop Admin API tokens to cache alongside the Identity session.
+ *
+ * The Admin API only accepts shop-app tokens (`shpat_*`), not Identity-issued
+ * OAuth access tokens. The bootstrap's `accessToken` above is the Identity
+ * OAuth token — valid for Identity-fronted APIs (partners / BP / storefront-
+ * renderer / app-management) but rejected by the Admin API with
+ * `[API] Service is not valid for authentication`. To make
+ * `ensureAuthenticatedAdmin(storeFqdn)` resolve to a working token, callers
+ * must pass the per-shop `shpat_*` token here (e.g. the value of
+ * `store_auth_bootstrap.access_token` from a preview-store create response).
+ *
+ * Each entry's key should be the same domain the user will type as `--store`
+ * (e.g. `preview-X.dev-api.shop.dev` on the rig, where `.my.shop.dev` isn't
+ * routable). The token is seeded into `applications[`${storeFqdn}-${adminAppId}`]`
+ * with a 1-year expiry; that's the exact key `tokensFor` looks up when an
+ * Admin API call requests `storeFqdn`. Omit when the bootstrap isn't per-shop.
+ */
+ adminStoreTokens?: Record<string, string>;
+}
+/**
+ * Adopt a backend-issued Identity OAuth session as the active CLI account.
+ *
+ * Writes the bootstrap tokens directly into the `Sessions[identityFqdn][userId]`
+ * storage and marks the row as the current session. To satisfy `validateSession`
+ * without triggering the device-auth re-prompt or the multi-audience token
+ * exchange, the import pre-seeds:
+ *
+ * - `identity.scopes`: the union of all default CLI scopes (plus `employee`
+ * when running as a first-party dev). The placeholder's effective scopes
+ * are opaque to the CLI; we claim coverage so `validateScopes` returns true
+ * and the cached session is used verbatim.
+ * - `applications`: the same bootstrap accessToken aliased under every
+ * standard appId (`admin`, `partners`, `business-platform`,
+ * `storefront-renderer`, `app-management`). The placeholder bootstrap is
+ * usable directly against the audiences the backend authorized for it
+ * (typically Admin + Business Platform); aliasing it lets
+ * `ensureAuthenticatedBusinessPlatform` / `ensureAuthenticatedAdmin` read
+ * it straight out of the cache without re-running the exchange. APIs that
+ * the bootstrap is not authorized for will reject the request with a
+ * clean 401/403 at the call site — better than a mid-import re-auth loop.
+ *
+ * The caller is expected to have already obtained a valid Identity refresh
+ * token from a trusted backend path (e.g. `POST /services/preview-stores`).
+ * No browser, no device-code prompt, no consent UI.
+ *
+ * @param bootstrap - Backend-issued Identity tokens to import.
+ * @returns The userId under which the session was persisted.
+ */
+export declare function importIdentitySession(bootstrap: IdentitySessionBootstrap): Promise<{
+ userId: string;
+}>;
+/**
* Ensure that we have a valid session with no particular scopes.
*
* @param env - Optional environment variables to use.
* @param options - Optional extra options to use.
* @returns The user ID.
*/
export declare function ensureAuthenticatedUser(env?: NodeJS.ProcessEnv, options?: EnsureAuthenticatedAdditionalOptions): Promise<{
userId: string;
}>;
/**
* Ensure that we have a valid session to access the Partners API.
* If SHOPIFY_CLI_PARTNERS_TOKEN exists, that token will be used to obtain a valid Partners Token
* If SHOPIFY_CLI_PARTNERS_TOKEN exists, scopes will be ignored.
*
* @param scopes - Optional array of extra scopes to authenticate with.
* @param env - Optional environment variables to use.
* @param options - Optional extra options to use.
* @returns The access token for the Partners API.
*/
export declare function ensureAuthenticatedPartners(scopes?: PartnersAPIScope[], env?: NodeJS.ProcessEnv, options?: EnsureAuthenticatedAdditionalOptions): Promise<{
token: string;
userId: string;
}>;
/**
* Ensure that we have a valid session to access the App Management API.
*
* @param options - Optional extra options to use.
* @param appManagementScopes - Optional array of extra scopes to authenticate with.
* @param businessPlatformScopes - Optional array of extra scopes to authenticate with.
* @param env - Optional environment variables to use.
* @returns The access token for the App Management API.
*/
export declare function ensureAuthenticatedAppManagementAndBusinessPlatform(options?: EnsureAuthenticatedAdditionalOptions, appManagementScopes?: AppManagementAPIScope[], businessPlatformScopes?: BusinessPlatformScope[], env?: NodeJS.ProcessEnv): Promise<{
appManagementToken: string;
userId: string;
businessPlatformToken: string;
}>;
/**
* Ensure that we have a valid session to access the Storefront API.
*
* @param scopes - Optional array of extra scopes to authenticate with.
* @param password - Optional password to use.
* @param options - Optional extra options to use.
* @returns The access token for the Storefront API.
*/
export declare function ensureAuthenticatedStorefront(scopes?: StorefrontRendererScope[], password?: string | undefined, options?: EnsureAuthenticatedAdditionalOptions): Promise<string>;
/**
* Ensure that we have a valid Admin session for the given store.
*
* @param store - Store fqdn to request auth for.
* @param scopes - Optional array of extra scopes to authenticate with.
* @param options - Optional extra options to use.
* @returns The access token for the Admin API.
*/
export declare function ensureAuthenticatedAdmin(store: string, scopes?: AdminAPIScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<AdminSession>;
/**
* Ensure that we have a valid session to access the Theme API.
* If a password is provided, that token will be used against Theme Access API.
* Otherwise, it will ensure that the user is authenticated with the Admin API.
*
* @param store - Store fqdn to request auth for.
* @param password - Password generated from Theme Access app.
* @param scopes - Optional array of extra scopes to authenticate with.
* @param options - Optional extra options to use.
* @returns The access token and store.
*/
export declare function ensureAuthenticatedThemes(store: string, password: string | undefined, scopes?: AdminAPIScope[], options?: EnsureAuthenticatedAdditionalOptions): Promise<AdminSession>;
/**
* Ensure that we have a valid session to access the Business Platform API.
*
* @param scopes - Optional array of extra scopes to authenticate with.
* @returns The access token for the Business Platform API.
*/
export declare function ensureAuthenticatedBusinessPlatform(scopes?: BusinessPlatformScope[]): Promise<string>;
/**
* Logout from Shopify.
*
* @returns A promise that resolves when the logout is complete.
*/
export declare function logout(): Promise<void>;
/**
* Ensure that we have a valid Admin session for the given store, with access on behalf of the app.
*
* See `ensureAuthenticatedAdmin` for access on behalf of a user.
*
* @param storeFqdn - Store fqdn to request auth for.
* @param clientId - Client ID of the app.
* @param clientSecret - Client secret of the app.
* @returns The access token for the Admin API.
*/
export declare function ensureAuthenticatedAdminAsApp(storeFqdn: string, clientId: string, clientSecret: string): Promise<AdminSession>;
export {};
packages/cli-kit/dist/private/node/session/exchange.d.ts@@ -14,10 +14,26 @@ export interface ExchangeScopes {
appManagement: string[];
}
/**
- * Given an identity token, request an application token.
+ * Given an identity token, request an application token for each Shopify API
+ * (partners / storefront-renderer / business-platform / admin / app-management).
+ *
+ * Per-API failures are tolerated: a token exchange that returns
+ * or other non-fatal errors for one audience is logged at debug level and skipped,
+ * while the successful exchanges are merged into the returned record. Callers
+ * already validate that the specific API token they need is present (see the
+ * BugError throws in
+ * ), so partial success surfaces a clear, scoped error at
+ * the call site rather than a confusing mid-Promise.all.
+ *
+ * The motivating case is server-issued Identity bootstraps (e.g. preview-store
+ * ) whose Identity token is bound to a single OAuth
+ * application and therefore can only be exchanged for a subset of audiences. For
+ * normal device-auth logins all five exchanges still succeed exactly as before.
+ *
* @param identityToken - access token obtained in a previous step
+ * @param scopes - per-API scope sets to request
* @param store - the store to use, only needed for admin API
- * @returns An array with the application access tokens.
+ * @returns A merged record of every application token that was successfully minted.
*/
export declare function exchangeAccessForApplicationTokens(identityToken: IdentityToken, scopes: ExchangeScopes, store?: string): Promise<Record<string, ApplicationToken>>;
/**
packages/cli-kit/dist/private/node/session/schema.d.ts@@ -12,8 +12,8 @@ declare const IdentityTokenSchema: zod.ZodObject<{
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -34,8 +34,8 @@ declare const ApplicationTokenSchema: zod.ZodObject<{
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -54,8 +54,8 @@ declare const SessionSchema: zod.ZodObject<{
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -73,8 +73,8 @@ declare const SessionSchema: zod.ZodObject<{
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -88,8 +88,8 @@ declare const SessionSchema: zod.ZodObject<{
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -103,8 +103,8 @@ declare const SessionSchema: zod.ZodObject<{
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -116,16 +116,16 @@ declare const SessionSchema: zod.ZodObject<{
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -166,8 +166,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -185,8 +185,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -200,8 +200,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -215,8 +215,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -228,16 +228,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -269,8 +269,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -288,8 +288,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -303,8 +303,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -318,8 +318,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -331,16 +331,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -372,8 +372,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -391,8 +391,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -406,8 +406,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -421,8 +421,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -434,16 +434,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -475,8 +475,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -494,8 +494,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -509,8 +509,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -524,8 +524,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -537,16 +537,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -578,8 +578,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -597,8 +597,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -612,8 +612,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -627,8 +627,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -640,16 +640,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -681,8 +681,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -700,8 +700,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -715,8 +715,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -730,8 +730,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -743,16 +743,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -784,8 +784,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -803,8 +803,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -818,8 +818,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -833,8 +833,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -846,16 +846,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -887,8 +887,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -906,8 +906,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -921,8 +921,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -936,8 +936,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -949,16 +949,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
@@ -990,8 +990,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
}, "strip", zod.ZodTypeAny, {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
}, {
@@ -1009,8 +1009,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -1024,8 +1024,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -1039,8 +1039,8 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
storeFqdn: zod.ZodOptional<zod.ZodString>;
}, "strip", zod.ZodTypeAny, {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
}, {
accessToken: string;
@@ -1052,16 +1052,16 @@ export declare const SessionsSchema: zod.ZodObject<{}, "strip", zod.ZodObject<{}
identity: {
accessToken: string;
refreshToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
userId: string;
alias?: string | undefined;
};
applications: {} & {
[k: string]: {
accessToken: string;
- scopes: string[];
expiresAt: Date;
+ scopes: string[];
storeFqdn?: string | undefined;
};
};
|
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?
The original
store listcommand (introduced in thestore-list-commandbranch) reads the localshopify-cli-store-nodejsLocalStorage cache and lists every shop the CLI has previously authed against on this machine. That's the right answer when you want "what's on this disk" — useful for debugging, for freshly-created preview stores, and offline use — but it's the wrong default for the common case of "what stores does my Shopify account actually have access to."After the parent PR in this stack,
store create previewleaves the CLI authenticated as a real (placeholder-backed) Identity session. With that piece in place, the logical default forstore listbecomes Business Platform: list what the currently-logged-in account can see in the platform itself.But the placeholder session is a wrinkle:
PlaceholderAccountaUserAccountin BP. The "Path B keep the change surface tiny" framing means BP'scurrentUserAccount.organizationsWithAccessToDestinationreturnsnullfor placeholders, so a BP listing for a placeholder session is always empty.organization list --jsonfor the placeholder session, but his description explicitly says "succeeds" — i.e. doesn't crash — without asserting that it returns useful data. His prototype is silent on the broader UX gap.This PR takes the smallest deviation from Donald's pattern that still gives users a sensible default: keep BP as the default for real users, fall back to the local cache for placeholder sessions, and let the user override with
--sourceeither way.WHAT is this pull request doing?
Two sources, dispatched by the user's choice (or sensible default)
services/store/list/bp-source.ts(new):listBusinessPlatformStores()does a two-phase fetch — destinations BP for the orgs the current user has CLI access to (organizationsWithAccessToDestination(destination: APPS_CLI)), then per-org organizations BP foraccessibleShopspaginated to completion (50/page, capped at 200 pages as a runaway guard). NoSTORE_TYPEfilter —store listshould show production / development / preview / etc., not just app-dev sandboxes, so the type appears in its own column instead. Per-org fetches run in parallel because CLI users typically have one or two orgs and the politeness cost is dwarfed by the wall-clock gain.services/store/list/index.ts: refactored from a synchronous list-and-project into an async dispatch that picks the source fromoptions.source ∈ {'bp', 'local'}. The function now returns aListStoredStoresResultwrapper with{entries, source, notice?, currentUserEmail?}instead of bareStoreListEntry[], so the renderer can distinguish "I queried BP and it returned 0 because you're a placeholder" from "I queried local and there's nothing on disk."commands/store/list.ts: adds--source(defaultbp, withlocalas the explicit opt-out),--search(forwarded to BP only — local-source ignores it; callers can post-filter), keeps the existing--kindflag but documents that it only applies to--source localbecause BP entries don't carry the local-cachestandard/previewdiscriminator.services/store/list/result.ts: two different table layouts based on source — BP showsStore / Name / Type / Organization, local keeps the originalStore / Kind / User. The empty-state message for BP suggests--source localas the natural fallback for users who just created a preview store and want to see it.Notice carries the placeholder-aware messaging
When BP returns
currentUserAccount: null, the result includes a notice:This is the deliberate UX choice: rather than auto-falling-back to local (which would mask the fact that the user is on a placeholder session), we surface the explanation and let the user opt in.
How to test your changes?
Automated
Expected: 20 tests pass (5 local-source + 4 BP-source dispatch + 6 result-renderer × 2 modes). The only type-check error is the pre-existing
client.test.tsResponse-vs-node-fetch.Responsemismatch unrelated to this PR.Manual smoke tests
For the "real user post auth login" path, the BP query should return their accessible stores across every org with CLI access. That path will be exercisable once
auth loginis wired through the rig.Post-release steps
None. This is part of the Preview Stores prototype stack.
Checklist