Preview Stores prototype: import cli_identity_bootstrap on store create preview#7610
Draft
alfonso-noriega wants to merge 1 commit into
Draft
Preview Stores prototype: import cli_identity_bootstrap on store create preview#7610alfonso-noriega wants to merge 1 commit into
store create preview#7610alfonso-noriega wants to merge 1 commit into
Conversation
4 tasks
store create preview
4 tasks
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. |
This was referenced May 22, 2026
7a36a25 to
9b52c0a
Compare
Consumes the new `cli_identity_bootstrap` and `store_auth_bootstrap` fields returned by Donald's PreviewStoresController extension, so the canonical `store create preview` command lands the user in a real (placeholder-backed) Identity session in addition to the per-shop Admin token. Adds `importIdentitySession` to cli-kit, makes `throwOnNoPrompt` non-destructive, and makes app-token exchange tolerant of per-audience failures.
9b52c0a to
9d7ec9b
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?
This stacks on top of
store-list-commandand continues the Preview Stores prototype work led by Donald (shop/world#730858, #7574). The originalstore create previewPoC handed users an explicit admin API token and a magic link, leaving every downstream CLI interaction (Identity-backed reads,whoami, future cross-org commands) on a separate auth path.Donald's stacked World PR extends the orchestrator response with two new bootstrap blocks:
…and Donald's CLI prototype (cli#7574) consumes them through
importIdentitySession+importStoreAuthBootstrapshims that live alongside the existing PoC commands.This PR brings the equivalent into the
store create previewcommand itself, so the existing PoC stack (#7557 → #7558 →store-list-command) becomes the canonical place where preview-store creation also leaves the CLI fully authenticated. No new preview-only commands, no parallel auth paths —store create previewis now the only entry point and it lands the user in a real (placeholder-backed) Identity session.WHAT is this pull request doing?
@shopify/cli-kit— newimportIdentitySessionprimitivepublic/node/session.ts: addsIdentitySessionBootstrap+importIdentitySession({accessToken, refreshToken, expiresAt, userId, adminApiStoreFqdns?}). The function writes the bootstrap tokens directly intoSessions[identityFqdn][userId]storage and marks the row as current. To satisfyvalidateSessionwithout triggering device-auth re-prompts or the multi-audience token exchange, the import pre-seeds:identity.scopeswithallDefaultScopes()(plusemployeewhenfirstPartyDev()is true, becausevalidateScopesrequires the firstPartyDev flag and theemployeescope to track each other);applicationswith the bootstrap accessToken aliased under every non-store-scoped appId (partners / business-platform / storefront-renderer / app-management) AND under the store-prefixed Admin key (${storeFqdn}-${adminAppId}) for each entry inadminApiStoreFqdns.tokensForreads the bare appId for non-store-scoped APIs and the store-prefixed key for Admin; aliasing the bootstrap under both letsensureAuthenticatedBusinessPlatform/ensureAuthenticatedAdmin(<shop>)read it straight out of the cache without re-running the exchange. Without the store-prefixed seed,theme push --store <preview>,store execute --store <preview>, and any otherensureAuthenticatedAdmin-gated command would miss the cache and attempt an Identity token exchange that fails for placeholders (they aren't standard UserAccount resource owners that Identity can mint shop-scoped Admin tokens for).public/node/environment.ts:getIdentityTokenInformation()now accepts anenvarg and surfacesexpiresAt+ an explicituserIdwhenSHOPIFY_CLI_IDENTITY_TOKEN_EXPIRES_AT/SHOPIFY_CLI_IDENTITY_USER_IDare present.private/node/constants.ts: new env-var names (SHOPIFY_CLI_IDENTITY_USER_ID,SHOPIFY_CLI_IDENTITY_TOKEN_EXPIRES_AT).private/node/session.ts:executeCompleteFlow/ensureAuthenticatedaccept an explicitenvarg so the bootstrap path can opt intonoPrompt: truewithout prompting the user mid-import.throwOnNoPromptno longer callslogout()on failure — wiping the entire Sessions store on everynoPromptrejection is destructive when the caller is in a 401-retry cascade against a backend-issued session that can't be reconstructed without re-creating a shop. Users who explicitly want to clear sessions can runshopify auth logout.private/node/session/exchange.ts:exchangeAccessForApplicationTokensis now tolerant of per-audience failures viaPromise.allSettled. When at least one exchange succeeds the result is the union of successes; only when every exchange fails do we surface the first identity-level error (matching the previous "all-or-nothing" behavior). Motivation: backend-issued sessions (preview-store placeholders, app-automation tokens) may have a narrower audience grant than a real PKCE user; the old all-or-nothing semantics would surface that as a single confusinginvalid_requestmid-Promise.allrather than as a scoped "no<api>token found" at the actual call site.@shopify/store—store create previewconsumes the new bootstrap shapeservices/store/create/preview/client.ts: parsescli_identity_bootstrapandstore_auth_bootstrapfrom the orchestrator JSON. Defensive narrowing drops partial/malformed payloads silently so the create flow can't be stranded by a half-formed bootstrap (analytics still report the store as created on Core; the user just doesn't get the import side effects).services/store/create/preview/index.ts: persists the store-auth bucket first (so a failed Identity import never orphans the just-created shop on the CLI side), then best-effort imports the Identity session viaimportIdentitySessionwithadminApiStoreFqdns: [resolvedShopDomain]so the store-prefixed Admin entry lands alongside the bare-audience entries. Whenstore_auth_bootstrapis present, the bucket is keyed underbootstrap.shop_domainbecause that's the host that actually answers Admin API requests for this shop in the local rig (top-levelshop_permanent_domainis canonical/display-only and doesn't route to a Spin instance on local). That sameshop_domainis also what the result struct surfaces asshopPermanentDomain, so users can pipe--jsonoutput straight intostore execute --store ...and the Admin URL the CLI builds resolves.services/store/create/preview/result.ts: JSON output now includesidentityImported: booleanso agents can tell whether the bootstrap was actually consumed; text output gains a "CLI identity" section explaining the outcome.How to test your changes?
Automated
Expected: 126 cli-kit session tests pass, 22 preview tests pass (4 new for bootstrap behavior + the previously-existing baseline), and the only type-check error is the pre-existing
client.test.tsResponse-vs-node-fetch.Responsemismatch that's unrelated to this PR.Manual against the local rig (with Donald's paired World branch checked out)
Post-release steps
None. This is part of the Preview Stores prototype stack and is paired to Donald's local-only
shop/world#730858; not intended to ship as-is.Checklist