Prototype JSON event streaming for app dev - #8315
Draft
gonzaloriestra wants to merge 1 commit into
Draft
Conversation
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/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.6.0";
\ No newline at end of file
+export declare const CLI_KIT_VERSION = "4.5.0";
\ No newline at end of file
packages/cli-kit/dist/public/node/abort.d.ts@@ -1,24 +1,16 @@
+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;
\ No newline at end of file
+export declare class AbortSignal extends NodeAbortControllerSignal {
+}
\ No newline at end of file
packages/cli-kit/dist/public/node/base-command.d.ts@@ -2,15 +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 nonTTYFlagRequirements(_flags: FlagOutput): NonTTYFlagRequirement[];
static descriptionWithoutMarkdown(): string | undefined;
static analyticsNameOverride(): string | undefined;
static analyticsStopCommand(): string | undefined;
@@ -29,8 +22,6 @@ declare abstract class BaseCommand extends Command {
}>;
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@@ -55,19 +55,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,
- * `BaseCommand` validates the flag automatically and the requirement is shown in `--help`.
- * Use `BaseCommand.nonTTYFlagRequirements` 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/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,
packages/cli-kit/dist/public/node/output.d.ts@@ -10,6 +10,16 @@ export declare class TokenizedString {
constructor(value: string);
}
export type OutputMessage = string | TokenizedString;
+interface DiagnosticEvent {
+ level: 'debug' | 'info' | 'warning';
+ message: string;
+}
+/**
+ * Adapts a debug-level diagnostic event to the CLI's existing debug output behavior.
+ *
+ * @param event - Diagnostic event to adapt.
+ */
+export declare function debugMessageSink(event: DiagnosticEvent): void;
export declare const outputToken: {
raw(value: string): RawContentToken;
genericShellCommand(value: OutputMessage): CommandContentToken;
@@ -173,4 +183,5 @@ export declare function shouldDisplayColors(_process?: NodeJS.Process): boolean;
* @param body - The body of the message. Will respect the original formatting.
* @returns The formatted message.
*/
-export declare function formatSection(title: string, body: string): string;
\ No newline at end of file
+export declare function formatSection(title: string, body: string): string;
+export {};
\ No newline at end of file
packages/cli-kit/dist/public/node/system.d.ts@@ -115,19 +115,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>;
+export declare function isWsl(): Promise<boolean>;
/**
* Check if stdin has piped data available.
* This distinguishes between actual piped input (e.g., `echo "query" | cmd`)
@@ -146,5 +139,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/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/context/local.d.ts@@ -141,9 +141,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;
|
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 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.

What
Add a prototype
--jsonmode toshopify app devthat emits newline-delimited JSON (NDJSON) events for:The existing text and interactive UI paths remain unchanged.
Why
app devnever produces a single final result, so it cannot return one conventional JSON document. This prototype uses one complete JSON object per line, allowing consumers to process events as they arrive.It is stacked on #8241 and uses its synchronous diagnostic channel to keep event production separate from the JSON output adapter.
Prototype limitations
How to test
pnpm shopify app dev --jsonOutput example: