Review fixes for the store signup JWT handoff - #8445
Open
craigmichaelmartin wants to merge 7 commits into
Open
Conversation
Answer 404 for every rejection so a local prober cannot distinguish a wrong nonce from a spent handoff, or either from a port with no auth in flight. Restrict the endpoint to GET, and decline a browser's speculative fetch so a prefetch or prerender cannot spend the single-use handoff before the navigation it is speculating about arrives. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Assisted-By: devx/39092f35-5041-4a88-ab8d-808c98322495
buildStoreAuthUrl no longer accepts or serializes the credential; the URL that carries it is derived only for the loopback redirect. Also drops the unused signup field from the authorization context, which returned the credential to every caller of createPkceBootstrap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Assisted-By: devx/39092f35-5041-4a88-ab8d-808c98322495
The presenter refused to print a URL only when a caller marked it sensitive, so a caller that forgot leaked it. Key the refusal on the URL itself as well, and fail closed on a URL that cannot be parsed. The call site no longer passes a hardcoded sensitive: false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Assisted-By: devx/39092f35-5041-4a88-ab8d-808c98322495
An interactive stdin never ends, so omitting --signup hung the command instead of reporting the missing credential. Report it immediately when stdin is not piped, cap what is read, and treat a blank --signup as not supplied rather than authorizing without a credential. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Assisted-By: devx/39092f35-5041-4a88-ab8d-808c98322495
The signup flag became optional and its description and examples changed, but the manifest still described it as required. Regenerated output, limited to the store:stripe-auth entry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Assisted-By: devx/39092f35-5041-4a88-ab8d-808c98322495
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/public/common/object.d.ts@@ -38,14 +38,6 @@ export declare function mapValues<T extends object, TResult>(source: T | null |
* @returns True if the objects are equal, false otherwise.
*/
export declare function deepCompare(one: object, two: object): boolean;
-/**
- * Deeply compares two values and treats arrays as order-insensitive.
- *
- * @param one - The first value to be compared.
- * @param two - The second value to be compared.
- * @returns True if the normalized values are equal, false otherwise.
- */
-export declare function deepCompareWithOrderInsensitiveArrays(one: unknown, two: unknown): boolean;
/**
* Return the difference between two nested objects.
*
packages/cli-kit/dist/public/common/string.d.ts@@ -1,4 +1,4 @@
-import type { Token, TokenItem } from '../../private/node/ui/components/token-item.js';
+import { Token, TokenItem } from '../../private/node/ui/components/TokenizedText.js';
export type RandomNameFamily = 'business' | 'creative';
/**
* Generates a random name by combining an adjective and noun.
packages/cli-kit/dist/public/common/version.d.ts@@ -1 +1 @@
-export declare const CLI_KIT_VERSION = "4.7.0";
\ No newline at end of file
+export declare const CLI_KIT_VERSION = "4.4.0";
\ No newline at end of file
packages/cli-kit/dist/private/node/constants.d.ts@@ -6,6 +6,7 @@ export declare const environmentVariables: {
doctor: string;
enableCliRedirect: string;
env: string;
+ firstPartyDev: string;
noAnalytics: string;
optOutInstrumentation: string;
appAutomationToken: string;
@@ -30,6 +31,7 @@ export declare const environmentVariables: {
otelURL: string;
themeKitAccessDomain: string;
json: string;
+ neverUsePartnersApi: string;
skipNetworkLevelRetry: string;
maxRequestTimeForNetworkCalls: string;
disableImportScanning: string;
packages/cli-kit/dist/private/node/otel-metrics.d.ts@@ -2,7 +2,7 @@ import { OtelService } from '../../public/node/vendor/otel-js/service/types.js';
import { DefaultOtelServiceOptions } from '../../public/node/vendor/otel-js/service/DefaultOtelService/DefaultOtelService.js';
type MetricRecorder = 'console' | {
type: 'otel';
- otel: Pick<OtelService, 'getMeterProvider' | 'record'>;
+ otel: Pick<OtelService, 'record'>;
};
interface Timing {
active: number;
packages/cli-kit/dist/private/node/session.d.ts@@ -90,7 +90,6 @@ export declare function setLastSeenUserIdAfterAuth(id: string): void;
*/
export declare function getLastSeenAuthMethod(): Promise<AuthMethod>;
export declare function setLastSeenAuthMethod(method: AuthMethod): void;
-export declare function setCommandSessionId(sessionId: string | undefined): void;
export interface EnsureAuthenticatedAdditionalOptions {
noPrompt?: boolean;
forceRefresh?: boolean;
packages/cli-kit/dist/public/node/abort.d.ts+import { AbortController as NodeAbortController, AbortSignal as NodeAbortControllerSignal } from 'node-abort-controller';
/**
* The AbortController interface represents a controller object that allows you to abort one or more Web requests as and when desired.
*
* - MDN Documentation: https://developer.mozilla.org/en-US/docs/Web/API/AbortController
*
- * This class exists to keep the historical `@shopify/cli-kit/node/abort` import path working
- * now that Node provides AbortController natively.
+ * This class is necessary because AbortController support was added to Node 15 and the minimum
+ * version that we support is Node 14.
*/
-export declare class AbortController extends globalThis.AbortController {
+export declare class AbortController extends NodeAbortController {
}
/**
* The AbortSignal interface represents a signal object that allows you to communicate with a DOM request (such as a fetch request) and abort it if required via an AbortController object.
- *
- * Note that AbortSignal cannot be constructed directly. Get one from an AbortController's
- * `signal` property or from the static helpers such as `AbortSignal.timeout()`.
*/
-export declare const AbortSignal: {
- new (): globalThis.AbortSignal;
- prototype: globalThis.AbortSignal;
- abort(reason?: any): globalThis.AbortSignal;
- any(signals: globalThis.AbortSignal[]): globalThis.AbortSignal;
- timeout(milliseconds: number): globalThis.AbortSignal;
-};
-export type AbortSignal = globalThis.AbortSignal;
+export declare class AbortSignal extends NodeAbortControllerSignal {
+}
packages/cli-kit/dist/public/node/analytics.d.ts import { RuntimeData } from '../../private/node/analytics/storage.js';
import { Interfaces } from '@oclif/core';
export type CommandExitMode = 'ok' | 'unexpected_error' | 'expected_error';
interface ReportAnalyticsEventOptions {
config: Interfaces.Config;
errorMessage?: string;
exitMode: CommandExitMode;
}
-export declare function sendAnalyticsEventFromStdin(): Promise<void>;
/**
* Report an analytics event, sending it off to Monorail -- Shopify's internal analytics service.
*
* The payload for an event includes both generic data, and data gathered from installed plug-ins.
*
*/
export declare function reportAnalyticsEvent(options: ReportAnalyticsEventOptions): Promise<void>;
/**
* Records timing data for performance monitoring. Call twice with the same
* event name to start and stop timing. First call starts the timer, second
* call stops it and records the duration.
*
* @example
* ```ts
* recordTiming('theme-upload') // Start timing
* // ... do work ...
* recordTiming('theme-upload') // Stop timing and record duration
* ```
*
* @param eventName - Unique identifier for the timing event
*/
export declare function recordTiming(eventName: string): void;
/**
* Records error information for debugging and monitoring. Use this to track
* any exceptions or error conditions that occur during theme operations.
* Errors are automatically categorized for easier analysis.
*
* @example
* ```ts
* try {
* // ... risky operation ...
* } catch (error) {
* recordError(error)
* }
* ```
*
* @param error - Error object or message to record
*/
export declare function recordError<T>(error: T): T;
/**
* Records retry attempts for network operations. Use this to track when
* operations are retried due to transient failures. Helps identify
* problematic endpoints or operations that frequently fail.
*
* @example
* ```ts
* recordRetry('https://api.shopify.com/themes', 'upload')
* ```
*
* @param url - The URL or endpoint being retried
* @param operation - Description of the operation being retried
*/
export declare function recordRetry(url: string, operation: string): void;
/**
* Records custom events for tracking specific user actions or system events.
* Use this for important milestones, user interactions, or significant
* state changes in the application.
*
* @example
* ```ts
* recordEvent('theme-dev-started')
* recordEvent('file-watcher-connected')
* ```
*
* @param eventName - Descriptive name for the event
*/
export declare function recordEvent(eventName: string): void;
/**
* Compiles and returns all runtime analytics data collected during the session.
* This includes timing measurements, error records, retry attempts, and custom
* events. Use this to retrieve a complete snapshot of analytics data for
* reporting or debugging purposes.
*
* @example
* ```ts
* const analyticsData = compileData()
* console.log(`Recorded ${analyticsData.timings.length} timing events`)
* console.log(`Recorded ${analyticsData.errors.length} errors`)
* ```
*
* @returns Object containing all collected analytics data including timings, errors, retries, and events
*/
export declare function compileData(): RuntimeData;
export {};
packages/cli-kit/dist/public/node/base-command.d.ts@@ -2,16 +2,8 @@ import { Command } from '@oclif/core';
import { OutputFlags, Input, ParserOutput, FlagInput, OutputArgs } from '@oclif/core/parser';
export type ArgOutput = OutputArgs<any>;
export type FlagOutput = OutputFlags<any>;
-export interface NonTTYFlagRequirement {
- /** At least one of these flags must be present when the requirement applies. */
- flags: string[];
- /** Determines whether the requirement applies to the parsed flags. */
- when?: (flags: FlagOutput) => boolean;
-}
declare abstract class BaseCommand extends Command {
static baseFlags: FlagInput<{}>;
- static get requiresSyncAnalytics(): boolean;
- static nonTTYFlagRequirements(_flags: FlagOutput): NonTTYFlagRequirement[];
static descriptionWithoutMarkdown(): string | undefined;
static analyticsNameOverride(): string | undefined;
static analyticsStopCommand(): string | undefined;
@@ -24,14 +16,11 @@ declare abstract class BaseCommand extends Command {
protected parse<TFlags extends FlagOutput & {
path?: string;
verbose?: boolean;
- 'auth-alias'?: string;
}, TGlobalFlags extends FlagOutput, TArgs extends ArgOutput>(options?: Input<TFlags, TGlobalFlags, TArgs>, argv?: string[]): Promise<ParserOutput<TFlags, TGlobalFlags, TArgs> & {
argv: string[];
}>;
protected environmentsFilename(): string | undefined;
protected failMissingNonTTYFlags(flags: FlagOutput, requiredFlags: string[]): void;
- private failMissingNonTTYFlagRequirements;
- private applicableNonTTYFlagRequirements;
private resultWithEnvironment;
/**
* Tries to load an environment to forward to the command. If no environment
packages/cli-kit/dist/public/node/cli.d.ts@@ -39,9 +39,6 @@ export declare const globalFlags: {
export declare const jsonFlag: {
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
};
-export declare const authAliasFlag: {
- 'auth-alias': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
-};
/**
* Builds a flag that only accepts a valid port number. The flag parses its
* value as an integer and rejects anything that isn't a whole number between 1 and
@@ -55,19 +52,6 @@ export declare const portFlag: (options?: {
env?: string;
hidden?: boolean;
}) => import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
-/**
- * Marks a flag as required when the CLI cannot prompt for a value.
- *
- * The flag remains optional in interactive terminals. In non-interactive environments,
- * validates the flag automatically and the requirement is shown in .
- * Use for conditional or alternative requirements.
- *
- * @param flag - An oclif flag definition.
- * @returns A new flag definition annotated for non-interactive validation and help output.
- */
-export declare function requiredIfNonInteractive<TFlag extends {
- description?: string;
-}>(flag: TFlag): TFlag;
/**
* Clear the CLI cache, used to store some API responses and handle notifications status
*/
packages/cli-kit/dist/public/node/environment.d.ts@@ -45,6 +45,12 @@ export declare function getIdentityTokenInformation(): {
* @returns True if the JSON output is enabled, false otherwise.
*/
export declare function jsonOutputEnabled(environment?: NodeJS.ProcessEnv): boolean;
+/**
+ * If true, the CLI should not use the Partners API.
+ *
+ * @returns True when the CLI should not use the Partners API.
+ */
+export declare function blockPartnersAccess(): boolean;
/**
* If true, the CLI should not use the network level retry.
*
packages/cli-kit/dist/public/node/error.d.ts@@ -1,6 +1,7 @@
import { OutputMessage } from './output.js';
-import { type InlineToken, type TokenItem } from '../../private/node/ui/components/token-item.js';
+import { InlineToken, TokenItem } from '../../private/node/ui/components/TokenizedText.js';
import type { AlertCustomSection } from './ui.js';
+export { ExtendableError } from 'ts-error';
export declare enum FatalErrorType {
Abort = 0,
AbortSilent = 1,
@@ -37,6 +38,8 @@ export declare abstract class FatalError extends Error {
* Those usually represent unexpected scenarios that we can't handle and that usually require some action from the developer.
*/
export declare class AbortError extends FatalError {
+ nextSteps?: TokenItem<InlineToken>[];
+ customSections?: AlertCustomSection[];
constructor(message: TokenItem | OutputMessage, tryMessage?: TokenItem | OutputMessage | null, nextSteps?: TokenItem<InlineToken>[], customSections?: AlertCustomSection[]);
}
/**
packages/cli-kit/dist/public/node/metadata.d.ts@@ -42,7 +42,6 @@ declare const coreData: RuntimeMetadataManager<CmdFieldsFromMonorail, {
startCommand: string;
startTopic?: string;
startArgs: string[];
- requiresSyncAnalytics?: boolean;
};
} & {
environmentFlags: string;
@@ -64,7 +63,6 @@ export declare const getAllPublicMetadata: () => Partial<CmdFieldsFromMonorail>,
startCommand: string;
startTopic?: string;
startArgs: string[];
- requiresSyncAnalytics?: boolean;
};
} & {
environmentFlags: string;
@@ -85,7 +83,6 @@ export declare const getAllPublicMetadata: () => Partial<CmdFieldsFromMonorail>,
startCommand: string;
startTopic?: string;
startArgs: string[];
- requiresSyncAnalytics?: boolean;
};
} & {
environmentFlags: string;
packages/cli-kit/dist/public/node/session.d.ts@@ -22,19 +22,6 @@ export type AccountInfo = UserAccountInfo | ServiceAccountInfo | UnknownAccountI
* @param userId - User identifier to report on the command analytics event.
*/
export declare function setLastSeenUserId(userId: string): void;
-/**
- * Finds a stored Shopify account session by alias without changing the current session.
- *
- * @param alias - The account alias to find.
- * @returns The matching session ID, or undefined if no session matches.
- */
-export declare function findSessionIdByAlias(alias: string): Promise<string | undefined>;
-/**
- * Selects a stored Shopify account session by alias for the current command process.
- *
- * @param alias - The account alias to select. Passing undefined clears the command selection.
- */
-export declare function setCurrentSessionAlias(alias?: string): Promise<void>;
interface UserAccountInfo {
type: 'UserAccount';
email: string;
packages/cli-kit/dist/public/node/system.d.ts@@ -105,25 +105,12 @@ export declare function terminalSupportsPrompting(): boolean;
* @returns True if the current environment is a CI environment.
*/
export declare function isCI(): boolean;
-interface WslDetectionOverrides {
- platform?: NodeJS.Platform;
- kernelRelease?: string;
- procVersion?: string;
- insideContainer?: boolean;
-}
/**
* Check if the current environment is a WSL environment.
*
- * @param overrides - Detection inputs, read from the system when not provided. Intended for tests.
* @returns True if the current environment is a WSL environment.
*/
-export declare function isWsl(overrides?: WslDetectionOverrides): Promise<boolean>;
-/**
- * Check if the current process is running inside a container.
- *
- * @returns True if the current process is running inside a container.
- */
-export declare function isInsideContainer(): boolean;
+export declare function isWsl(): Promise<boolean>;
/**
* Check if stdin has piped data available.
* This distinguishes between actual piped input (e.g., )
@@ -142,5 +129,4 @@ export declare function isStdinPiped(): boolean;
*
* @returns A promise that resolves with the stdin content, or undefined if stdin is a TTY.
*/
-export declare function readStdinString(): Promise<string | undefined>;
-export {};
\ No newline at end of file
+export declare function readStdinString(): Promise<string | undefined>;
\ No newline at end of file
packages/cli-kit/dist/public/node/ui.d.ts@@ -6,7 +6,7 @@ import { AlertOptions } from '../../private/node/ui/alert.js';
import { CustomSection } from '../../private/node/ui/components/Alert.js';
import ScalarDict from '../../private/node/ui/components/Table/ScalarDict.js';
import { TableColumn, TableProps } from '../../private/node/ui/components/Table/Table.js';
-import { type InlineToken, type LinkToken, type ListToken, type Token, type TokenItem } from '../../private/node/ui/components/token-item.js';
+import { Token, InlineToken, LinkToken, ListToken, TokenItem } from '../../private/node/ui/components/TokenizedText.js';
import { DangerousConfirmationPromptProps } from '../../private/node/ui/components/DangerousConfirmationPrompt.js';
import { SelectPromptProps } from '../../private/node/ui/components/SelectPrompt.js';
import { Task } from '../../private/node/ui/components/Tasks.js';
packages/cli-kit/dist/private/node/analytics/graphql-error-codes.d.ts@@ -21,8 +21,8 @@ export declare function graphQLErrorCodes(errors: unknown): string[];
/**
* Whether a single code is a rate-limit signal ( or ).
*
- * Shared with the retry path ( in ), where these codes signal
- * rate limiting even at HTTP 200.
+ * Mirrors the established shape detected by in ,
+ * where signals rate limiting even at HTTP 200.
*/
export declare function isRateLimitCode(code: string | undefined): boolean;
/**
packages/cli-kit/dist/private/node/session/exchange.d.ts@@ -1,9 +1,10 @@
import { ApplicationToken, IdentityToken } from './schema.js';
import { API } from '../api.js';
import { Result } from '../../../public/node/result.js';
-export declare class InvalidGrantError extends Error {
+import { ExtendableError } from '../../../public/node/error.js';
+export declare class InvalidGrantError extends ExtendableError {
}
-export declare class InvalidRequestError extends Error {
+export declare class InvalidRequestError extends ExtendableError {
}
export interface ExchangeScopes {
admin: string[];
@@ -51,8 +52,7 @@ export declare function exchangeAppAutomationTokenForBusinessPlatformAccessToken
accessToken: string;
userId: string;
}>;
-declare const identityDeviceErrors: readonly ["authorization_pending", "access_denied", "expired_token", "slow_down", "unknown_failure"];
-type IdentityDeviceError = (typeof identityDeviceErrors)[number];
+type IdentityDeviceError = 'authorization_pending' | 'access_denied' | 'expired_token' | 'slow_down' | 'unknown_failure';
/**
* Given a deviceCode obtained after starting a device identity flow, request an identity token.
* @param deviceCode - The device code obtained after starting a device identity flow
packages/cli-kit/dist/private/node/ui/utilities.d.ts@@ -1,16 +1,16 @@
-import { type TokenItem } from './components/token-item.js';
-export declare function messageWithPunctuation(message: TokenItem): string | import("./components/token-item.js").LinkToken | import("./components/token-item.js").UserInputToken | import("./components/token-item.js").ListToken | {
+import { TokenItem } from './components/TokenizedText.js';
+export declare function messageWithPunctuation(message: TokenItem): string | {
command: string;
-} | {
+} | import("./components/TokenizedText.js").LinkToken | {
char: string;
-} | {
+} | import("./components/TokenizedText.js").UserInputToken | {
subdued: string;
} | {
filePath: string;
-} | import("./components/token-item.js").BoldToken | {
+} | import("./components/TokenizedText.js").ListToken | import("./components/TokenizedText.js").BoldToken | {
info: string;
} | {
warn: string;
} | {
error: string;
-} | import("./components/token-item.js").Token[];
\ No newline at end of file
+} | import("./components/TokenizedText.js").Token[];
\ No newline at end of file
packages/cli-kit/dist/public/node/api/partners.d.ts@@ -1,5 +1,7 @@
import { GraphQLVariables, GraphQLResponse, CacheOptions, UnauthorizedHandler } from './graphql.js';
import { RequestModeInput } from '../http.js';
+import { Variables } from 'graphql-request';
+import { TypedDocumentNode } from '@graphql-typed-document-node/core';
/**
* Executes a GraphQL query against the Partners API.
*
@@ -12,6 +14,21 @@ import { RequestModeInput } from '../http.js';
* @returns The response of the query of generic type <T>.
*/
export declare function partnersRequest<T>(query: string, token: string, variables?: GraphQLVariables, cacheOptions?: CacheOptions, preferredBehaviour?: RequestModeInput, unauthorizedHandler?: UnauthorizedHandler): Promise<T>;
+export declare const generateFetchAppLogUrl: (cursor?: string, filters?: {
+ status?: string;
+ source?: string;
+}) => Promise<string>;
+/**
+ * Executes a GraphQL query against the Partners API. Uses typed documents.
+ *
+ * @param query - GraphQL query to execute.
+ * @param token - Partners token.
+ * @param variables - GraphQL variables to pass to the query.
+ * @param preferredBehaviour - Preferred behaviour for the request.
+ * @param unauthorizedHandler - Optional handler for unauthorized requests.
+ * @returns The response of the query of generic type <TResult>.
+ */
+export declare function partnersRequestDoc<TResult, TVariables extends Variables>(query: TypedDocumentNode<TResult, TVariables>, token: string, variables?: TVariables, preferredBehaviour?: RequestModeInput, unauthorizedHandler?: UnauthorizedHandler): Promise<TResult>;
/**
* Sets the next deprecation date from [GraphQL response extensions](https://www.apollographql.com/docs/resources/graphql-glossary/#extensions)
* if objects contain a (ISO 8601-formatted string).
packages/cli-kit/dist/public/node/context/local.d.ts@@ -63,6 +63,13 @@ export declare function alwaysLogAnalytics(env?: NodeJS.ProcessEnv): boolean;
* @returns True if SHOPIFY_CLI_ALWAYS_LOG_METRICS is truthy.
*/
export declare function alwaysLogMetrics(env?: NodeJS.ProcessEnv): boolean;
+/**
+ * Returns true if the CLI User is 1P.
+ *
+ * @param env - The environment variables from the environment of the current process.
+ * @returns True if SHOPIFY_CLI_1P is truthy.
+ */
+export declare function firstPartyDev(env?: NodeJS.ProcessEnv): boolean;
/**
* Returns true if the CLI can run the "doctor-release" command.
*
@@ -141,9 +148,7 @@ export declare function ciPlatform(env?: NodeJS.ProcessEnv): {
metadata?: undefined;
};
/**
- * Returns the first mac address found, preferring external interfaces. Returns a random
- * value when no interface has a MAC, so callers hashing it as a device id don't group
- * unrelated devices together.
+ * Returns the first mac address found.
*
* @returns Mac address.
*/
packages/cli-kit/dist/public/node/plugins/tunnel.d.ts@@ -1,3 +1,4 @@
+import { ExtendableError } from '../error.js';
import { OutputMessage } from '../output.js';
import { FanoutHookFunction, PluginReturnsForHook } from '../plugins.js';
import { Result } from '../result.js';
@@ -21,7 +22,7 @@ export type TunnelStatusType = {
message: TokenItem | OutputMessage;
tryMessage?: TokenItem | OutputMessage | null;
};
-export declare class TunnelError extends Error {
+export declare class TunnelError extends ExtendableError {
type: TunnelErrorType;
constructor(type: TunnelErrorType, message?: string);
}
packages/cli-kit/dist/private/node/ui/components/Alert.d.ts@@ -1,7 +1,7 @@
import { BannerType } from './Banner.js';
+import { BoldToken, InlineToken, LinkToken, TokenItem } from './TokenizedText.js';
import { TabularDataProps } from './TabularData.js';
import { FunctionComponent } from 'react';
-import type { BoldToken, InlineToken, LinkToken, TokenItem } from './token-item.js';
export interface CustomSection {
title?: string;
body: TabularDataProps | TokenItem;
packages/cli-kit/dist/private/node/ui/components/DangerousConfirmationPrompt.d.ts@@ -1,7 +1,7 @@
+import { InlineToken, TokenItem } from './TokenizedText.js';
import { InfoTableProps } from './Prompts/InfoTable.js';
import { AbortSignal } from '../../../../public/node/abort.js';
import { FunctionComponent } from 'react';
-import type { InlineToken, TokenItem } from './token-item.js';
export interface DangerousConfirmationPromptProps {
message: string;
confirmation: string;
packages/cli-kit/dist/private/node/ui/components/List.d.ts@@ -1,6 +1,6 @@
+import { InlineToken, TokenItem } from './TokenizedText.js';
import { TextProps } from 'ink';
import { FunctionComponent } from 'react';
-import type { InlineToken, TokenItem } from './token-item.js';
export interface CustomListItem {
type?: string;
item: TokenItem<InlineToken>;
packages/cli-kit/dist/private/node/ui/components/TabularData.d.ts@@ -1,4 +1,4 @@
-import { type InlineToken } from './token-item.js';
+import { InlineToken } from './TokenizedText.js';
import { FunctionComponent } from 'react';
export interface TabularDataProps {
tabularData: InlineToken[][];
packages/cli-kit/dist/private/node/ui/components/TextPrompt.d.ts@@ -1,6 +1,6 @@
+import { InlineToken, TokenItem } from './TokenizedText.js';
import { AbortSignal } from '../../../../public/node/abort.js';
import { FunctionComponent } from 'react';
-import type { InlineToken, TokenItem } from './token-item.js';
export interface TextPromptProps {
message: TokenItem;
onSubmit: (value: string) => void;
packages/cli-kit/dist/private/node/ui/components/TokenizedText.d.ts@@ -1,5 +1,42 @@
import { FunctionComponent } from 'react';
-import type { TokenItem } from './token-item.js';
+export interface LinkToken {
+ link: {
+ label?: string;
+ url: string;
+ };
+}
+export interface UserInputToken {
+ userInput: string;
+}
+export interface ListToken {
+ list: {
+ title?: TokenItem<InlineToken>;
+ items: TokenItem<InlineToken>[];
+ ordered?: boolean;
+ };
+}
+export interface BoldToken {
+ bold: string;
+}
+export type Token = string | {
+ command: string;
+} | LinkToken | {
+ char: string;
+} | UserInputToken | {
+ subdued: string;
+} | {
+ filePath: string;
+} | ListToken | BoldToken | {
+ info: string;
+} | {
+ warn: string;
+} | {
+ error: string;
+};
+export type InlineToken = Exclude<Token, ListToken>;
+export type TokenItem<T extends Token = Token> = T | T[];
+export declare function tokenItemToString(token: TokenItem): string;
+export declare function appendToTokenItem(token: TokenItem, suffix: string): TokenItem;
interface TokenizedTextProps {
item: TokenItem;
}
packages/cli-kit/dist/private/node/ui/components/Prompts/InfoMessage.d.ts@@ -1,6 +1,6 @@
+import { InlineToken, LinkToken, TokenItem, UserInputToken } from '../TokenizedText.js';
import { TextProps } from 'ink';
import { FunctionComponent } from 'react';
-import type { InlineToken, LinkToken, TokenItem, UserInputToken } from '../token-item.js';
export interface InfoMessageProps {
message: {
title: {
packages/cli-kit/dist/private/node/ui/components/Prompts/InfoTable.d.ts@@ -1,7 +1,7 @@
import { CustomListItem } from '../List.js';
+import { InlineToken, TokenItem } from '../TokenizedText.js';
import { TextProps } from 'ink';
import { FunctionComponent } from 'react';
-import type { InlineToken, TokenItem } from '../token-item.js';
type Items = (TokenItem<InlineToken> | CustomListItem)[];
export interface InfoTableSection {
color?: TextProps['color'];
packages/cli-kit/dist/private/node/ui/components/Prompts/PromptLayout.d.ts@@ -1,9 +1,9 @@
import { InfoTableProps } from './InfoTable.js';
import { InfoMessageProps } from './InfoMessage.js';
+import { InlineToken, LinkToken, TokenItem } from '../TokenizedText.js';
import { AbortSignal } from '../../../../../public/node/abort.js';
import { PromptState } from '../../hooks/use-prompt.js';
import { ReactElement } from 'react';
-import type { InlineToken, LinkToken, TokenItem } from '../token-item.js';
export type Message = TokenItem<Exclude<InlineToken, LinkToken>>;
interface PromptLayoutProps {
message: Message;
packages/cli-kit/dist/public/node/vendor/otel-js/service/types.d.ts@@ -1,5 +1,5 @@
-import type { Counter, Histogram, MetricAttributes, MetricOptions, UpDownCounter } from '@opentelemetry/api';
-import type { MeterProvider, ViewOptions } from '@opentelemetry/sdk-metrics';
+import type { Counter, Histogram, MeterProvider, MetricAttributes, MetricOptions, UpDownCounter } from '@opentelemetry/api';
+import type { ViewOptions } from '@opentelemetry/sdk-metrics';
export type CustomMetricLabels<TLabels extends Record<TKeys, MetricAttributes>, TKeys extends string = keyof TLabels & string> = {
[P in TKeys]: TLabels[P] extends MetricAttributes ? TLabels[P] : never;
};
|
craigmichaelmartin
force-pushed
the
signup-stdin-loopback
branch
from
September 1, 2026 13:36
2b3a55b to
0d0855c
Compare
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?
Review fixes for #8428 (and one carried defect from #8427), stacked on top of
signup-stdin-loopbackso neither of @dengjeffrey's branches has to be force-pushed and #8427 keeps its approval. Take these as commits to cherry-pick or squash into the stack, whichever you prefer — I have deliberately not touched either branch.Context: I picked up Vault 69736 as this week's Gardener and went over both PRs. The design in both is right; these are defects in the details. Each commit is self-contained and independently tested.
WHAT is this pull request doing?
Five commits, each with tests that fail without its source change (verified by reverting each source file in isolation — 2 failing tests per fix).
1.
Harden the store auth handoff endpoint—callback.ts404. Previously a wrong nonce got403and a spent handoff got410, which tells a local prober that a store auth flow is live on this port and that it has already been used.GET.http://127.0.0.1:13387/auth/handoff?nonce=…for a human to paste. If the browser prefetches or prerenders that URL from the omnibox, the nonce is spent and the real navigation gets a dead handoff — an intermittent, confusing auth failure in exactly the headless environment the handoff exists to keep working. Requests carryingSec-Purpose,Purpose, orX-mozwithprefetch/prerenderare declined without consuming it. Covered by a test that prefetches, then navigates, and asserts the navigation still gets its 302.2.
Keep the signup credential out of buildStoreAuthUrl—pkce.tsThis is the finding's literal first step, and the stack currently skips it:
buildStoreAuthUrlstill takessignup?: stringand still runsparams.set('signup', …). Now it neither accepts nor serializes the credential — the URL that carries it is derived only for the loopback redirect.Also removes
signup?: stringfromStoreAuthorizationContext. It had no readers, but it meantcreatePkceBootstrap(...).authorizationhanded the raw credential back to every caller.3.
Withhold manual auth URLs that carry a signup credential—result.ts,index.ts#8427 keys the refusal on a caller-supplied boolean. Once the handoff lands, no caller passes
trueany more — #8428 hardcodes{sensitive: false}— so the guard #8427 added is dormant and a future caller that forgets the flag leaks the URL again. The presenter now also refuses any URL that actually carries asignupparameter, and fails closed on a URL it cannot parse.options.sensitiveis kept as the extension point for credentials the presenter can't recognise.This is the one real design change here, so it's the one to push back on if you disagree — I'd rather you decide than have me quietly reshape an approved PR.
4.
Don't wait on an interactive stdin for the signup JWT—stripe-auth.tsreadSignupJwtFromStdinreadsprocess.stdinunconditionally. Since--signupbecame optional,shopify store stripe-auth --store … --scopes …on an interactive terminal hangs forever instead of reporting the missing credential. Now guarded with cli-kit's existingisStdinPiped()(system.ts:413).flags.signup ?? …meant--signup ''ran a signup-less authorization. A blank flag is now treated as not supplied. (Implemented by normalising the flag rather than||, because@typescript-eslint/prefer-nullish-coalescingis an error here.)Two things NOT fixed here, because a layer on #8428 structurally can't
returns, but control is still insidewaitForStoreAuthCode'sonListening, andcallback.ts:222discards the fulfilled value — so nothing settles and the CLI blocks the fulltimeoutMs = 5 * 60 * 1000before failing. That is deterministic in Codespaces/Gitpod/Cloud Shell, whereopenURLreturnsfalseatsystem.ts:57without trying. It needs anAbortError(the pattern atprivate/node/session/device-authorization.ts:74-79) on Avoid printing sensitive store auth URLs #8427 itself — it only bites if Avoid printing sensitive store auth URLs #8427 lands alone, and once the handoff lands the printed URL is usable, so an abort here would be wrong.— fixed inpackages/cli/oclif.manifest.jsonis stale48d196e3. I originally left this, reasoning that regenerating on a base 547 commits behindmainwould bake in a stale command set. That was wrong: the first CI run here printed the generator's actual output, and it is only thestore:stripe-authentry — description, the newprintfexample, and"required": true→false. The ~900-line divergence is between this base andmain, which the rebase resolves on its own and has nothing to do with regenerating. Applied the generator's output verbatim. The command ishidden, appears nowhere inpackages/cli/README.md, and has nodocs-shopify.devinterface, so the readme and docs steps of that job are unaffected.How to test your changes?
Full
packages/storesuite: 386 passed / 55 files.eslintandprettier --checkclean on all ten changed files.nx run store:type-checkreports 0 errors — for what it's worth, it's also 0 on this base unpatched, so the type-check failure noted in #8428's description looks environmental rather than real.CI: 23 pass, 4 fail — all four inherited from the base. Verified rather than predicted:
main)The remaining four fail on #8427 too, with none of these commits, and all of them pass on a branch cut from current
main— they clear on rebase.graphql-codegendiffs regenerated types (I touched no GraphQL); the E2E jobs reportProject(s) not foundagainst the stale base.One correction to what I first wrote here, since it matters for reading the two PRs' CI: on #8427 (base
main) the OCLIF job dies early withCannot find module bin/check-commands-snapshot.js, because the workflow comes frommainwhile the checked-out code predates that script. On this PR the base issignup-stdin-loopback, so the older workflow runs and gets far enough to actually check the manifest — which is how the real stale-manifest problem surfaced and got fixed in48d196e3.Also note
pnpm buildfails on this base inui-extensions-dev-console(Rollup failed to resolve import "react/jsx-runtime"—reactis not installed for this base's lockfile). Reproduced with my commits stashed, so pre-existing. That is why the manifest was applied from the generator's CI output rather than regenerated locally; the follow-up CI run confirmed it green.Post-release steps
None.
Checklist
isStdinPiped()is existing cli-kit code used elsewhere; the handoff endpoint is loopback HTTP with no platform-specific behaviouroclif.manifest.jsonregenerated for Read store signup JWT from stdin #8428's flag change; the command is hidden so README and shopify.dev docs are unaffected