diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index af5f3e73590..834a7228282 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -1114,6 +1114,15 @@ "isOptional": true, "environmentValue": "SHOPIFY_FLAG_APP_CONFIG" }, + { + "filePath": "docs-shopify.dev/commands/interfaces/app-dev.interface.ts", + "syntaxKind": "PropertySignature", + "name": "-j, --json", + "value": "''", + "description": "Output the result as JSON. Automatically disables color output.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_JSON" + }, { "filePath": "docs-shopify.dev/commands/interfaces/app-dev.interface.ts", "syntaxKind": "PropertySignature", @@ -1133,7 +1142,7 @@ "environmentValue": "SHOPIFY_FLAG_THEME" } ], - "value": "export interface appdev {\n /**\n * Alias of the Shopify account to use for authentication.\n * @environment SHOPIFY_FLAG_AUTH_ALIAS\n */\n '--auth-alias '?: string\n\n /**\n * Resource URL for checkout UI extension. Format: \"/cart/{productVariantID}:{productQuantity}\"\n * @environment SHOPIFY_FLAG_CHECKOUT_CART_URL\n */\n '--checkout-cart-url '?: string\n\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id '?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config '?: string\n\n /**\n * Install and use mkcert to generate localhost certificates when --use-localhost is enabled without prompting.\n * @environment SHOPIFY_FLAG_INSTALL_MKCERT\n */\n '--install-mkcert'?: ''\n\n /**\n * Port to use for localhost. Must be between 1 and 65535.\n * @environment SHOPIFY_FLAG_LOCALHOST_PORT\n */\n '--localhost-port '?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Uses the app URL from the toml file instead an autogenerated URL for dev.\n * @environment SHOPIFY_FLAG_NO_UPDATE\n */\n '--no-update'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify '?: string\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path '?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * Skips the installation of dependencies. Deprecated, use workspaces instead.\n * @environment SHOPIFY_FLAG_SKIP_DEPENDENCIES_INSTALLATION\n */\n '--skip-dependencies-installation'?: ''\n\n /**\n * Store URL. Must be an existing development or Shopify Plus sandbox store.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store '?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password '?: string\n\n /**\n * Resource URL for subscription UI extension. Format: \"/products/{productId}\"\n * @environment SHOPIFY_FLAG_SUBSCRIPTION_PRODUCT_URL\n */\n '--subscription-product-url '?: string\n\n /**\n * Theme ID or name of the theme app extension host theme.\n * @environment SHOPIFY_FLAG_THEME\n */\n '-t, --theme '?: string\n\n /**\n * Local port of the theme app extension development server. Must be between 1 and 65535.\n * @environment SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT\n */\n '--theme-app-extension-port '?: string\n\n /**\n * Use a custom tunnel, it must be running before executing dev. Format: \"https://my-tunnel-url:port\".\n * @environment SHOPIFY_FLAG_TUNNEL_URL\n */\n '--tunnel-url '?: string\n\n /**\n * Service entry point will listen to localhost. A tunnel won't be used. Will work for testing many app features, but not those that directly invoke your app (E.g: Webhooks)\n * @environment SHOPIFY_FLAG_USE_LOCALHOST\n */\n '--use-localhost'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" + "value": "export interface appdev {\n /**\n * Alias of the Shopify account to use for authentication.\n * @environment SHOPIFY_FLAG_AUTH_ALIAS\n */\n '--auth-alias '?: string\n\n /**\n * Resource URL for checkout UI extension. Format: \"/cart/{productVariantID}:{productQuantity}\"\n * @environment SHOPIFY_FLAG_CHECKOUT_CART_URL\n */\n '--checkout-cart-url '?: string\n\n /**\n * The Client ID of your app.\n * @environment SHOPIFY_FLAG_CLIENT_ID\n */\n '--client-id '?: string\n\n /**\n * The name of the app configuration.\n * @environment SHOPIFY_FLAG_APP_CONFIG\n */\n '-c, --config '?: string\n\n /**\n * Install and use mkcert to generate localhost certificates when --use-localhost is enabled without prompting.\n * @environment SHOPIFY_FLAG_INSTALL_MKCERT\n */\n '--install-mkcert'?: ''\n\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Port to use for localhost. Must be between 1 and 65535.\n * @environment SHOPIFY_FLAG_LOCALHOST_PORT\n */\n '--localhost-port '?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Uses the app URL from the toml file instead an autogenerated URL for dev.\n * @environment SHOPIFY_FLAG_NO_UPDATE\n */\n '--no-update'?: ''\n\n /**\n * The file path or URL. The file path is to a file that you want updated on idle. The URL path is where you want a webhook posted to report on file changes.\n * @environment SHOPIFY_FLAG_NOTIFY\n */\n '--notify '?: string\n\n /**\n * The path to your app directory.\n * @environment SHOPIFY_FLAG_PATH\n */\n '--path '?: string\n\n /**\n * Reset all your settings.\n * @environment SHOPIFY_FLAG_RESET\n */\n '--reset'?: ''\n\n /**\n * Skips the installation of dependencies. Deprecated, use workspaces instead.\n * @environment SHOPIFY_FLAG_SKIP_DEPENDENCIES_INSTALLATION\n */\n '--skip-dependencies-installation'?: ''\n\n /**\n * Store URL. Must be an existing development or Shopify Plus sandbox store.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store '?: string\n\n /**\n * The password for storefronts with password protection.\n * @environment SHOPIFY_FLAG_STORE_PASSWORD\n */\n '--store-password '?: string\n\n /**\n * Resource URL for subscription UI extension. Format: \"/products/{productId}\"\n * @environment SHOPIFY_FLAG_SUBSCRIPTION_PRODUCT_URL\n */\n '--subscription-product-url '?: string\n\n /**\n * Theme ID or name of the theme app extension host theme.\n * @environment SHOPIFY_FLAG_THEME\n */\n '-t, --theme '?: string\n\n /**\n * Local port of the theme app extension development server. Must be between 1 and 65535.\n * @environment SHOPIFY_FLAG_THEME_APP_EXTENSION_PORT\n */\n '--theme-app-extension-port '?: string\n\n /**\n * Use a custom tunnel, it must be running before executing dev. Format: \"https://my-tunnel-url:port\".\n * @environment SHOPIFY_FLAG_TUNNEL_URL\n */\n '--tunnel-url '?: string\n\n /**\n * Service entry point will listen to localhost. A tunnel won't be used. Will work for testing many app features, but not those that directly invoke your app (E.g: Webhooks)\n * @environment SHOPIFY_FLAG_USE_LOCALHOST\n */\n '--use-localhost'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" } }, "appenvpull": { diff --git a/packages/app/package.json b/packages/app/package.json index 2436bc203a8..9216866fcdd 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -62,6 +62,7 @@ "@luckycatfactory/esbuild-graphql-loader": "3.8.1", "@oclif/core": "4.8.3", "@shopify/cli-kit": "4.5.0", + "@shopify/diagnostics": "4.5.0", "@shopify/organizations": "4.5.0", "@shopify/plugin-cloudflare": "4.5.0", "@shopify/theme": "4.5.0", diff --git a/packages/app/src/cli/commands/app/dev.test.ts b/packages/app/src/cli/commands/app/dev.test.ts index 26ac10c63ee..de985bae9a0 100644 --- a/packages/app/src/cli/commands/app/dev.test.ts +++ b/packages/app/src/cli/commands/app/dev.test.ts @@ -59,6 +59,7 @@ describe('app dev command', () => { localhostPort: undefined, }) expect(dev).toHaveBeenCalledWith(expect.objectContaining({installMkcert: false, tunnel: {mode: 'auto'}})) + expect(dev).toHaveBeenCalledWith(expect.objectContaining({format: 'text'})) }) }) @@ -69,4 +70,28 @@ describe('app dev command', () => { expect(dev).not.toHaveBeenCalled() }) }) + + test('uses JSON output when --json is passed', async () => { + await inTemporaryDirectory(async (tmp) => { + const app = testAppLinked({directory: tmp}) + const appContextResult = { + app, + remoteApp: testOrganizationApp(), + organization: testOrganization(), + project: testProject(), + activeConfig: {} as never, + specifications: [], + developerPlatformClient: testDeveloperPlatformClient(), + } as Awaited> + const store = testOrganizationStore({shopDomain: 'dev-store.myshopify.com'}) + + vi.mocked(linkedAppContext).mockResolvedValue(appContextResult) + vi.mocked(storeContext).mockResolvedValue(store) + vi.mocked(getTunnelMode).mockResolvedValue({mode: 'auto'}) + + await Dev.run(['--path', tmp, '--store', store.shopDomain, '--json'], import.meta.url) + + expect(dev).toHaveBeenCalledWith(expect.objectContaining({format: 'json'})) + }) + }) }) diff --git a/packages/app/src/cli/commands/app/dev.ts b/packages/app/src/cli/commands/app/dev.ts index ee6f217ee97..64d3df3c798 100644 --- a/packages/app/src/cli/commands/app/dev.ts +++ b/packages/app/src/cli/commands/app/dev.ts @@ -5,9 +5,11 @@ import AppLinkedCommand, {AppLinkedCommandOutput} from '../../utilities/app-link import {linkedAppContext} from '../../services/app-context.js' import {storeContext} from '../../services/store-context.js' import {getTunnelMode} from '../../services/dev/tunnel-mode.js' +import {DevEvent, devJsonEventSink} from '../../services/dev/json.js' import {Flags} from '@oclif/core' +import {createSyncDiagnosticChannel} from '@shopify/diagnostics' import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn' -import {globalFlags, portFlag} from '@shopify/cli-kit/node/cli' +import {globalFlags, jsonFlag, portFlag} from '@shopify/cli-kit/node/cli' import {addPublicMetadata} from '@shopify/cli-kit/node/metadata' export default class Dev extends AppLinkedCommand { @@ -20,6 +22,7 @@ export default class Dev extends AppLinkedCommand { static flags = { ...globalFlags, ...appFlags, + ...jsonFlag, store: Flags.string({ char: 's', description: 'Store URL. Must be an existing development or Shopify Plus sandbox store.', @@ -149,6 +152,8 @@ export default class Dev extends AppLinkedCommand { graphiqlKey: flags['graphiql-key'], installMkcert: flags['install-mkcert'] ?? false, tunnel: tunnelMode, + format: flags.json ? 'json' : 'text', + events: createSyncDiagnosticChannel(...(flags.json ? [devJsonEventSink] : [])), } await dev(devOptions) diff --git a/packages/app/src/cli/services/dev.test.ts b/packages/app/src/cli/services/dev.test.ts index 119576f884d..86ca019f7a2 100644 --- a/packages/app/src/cli/services/dev.test.ts +++ b/packages/app/src/cli/services/dev.test.ts @@ -15,6 +15,7 @@ import {describe, expect, test, vi} from 'vitest' import {hashString} from '@shopify/cli-kit/node/crypto' import {reportAnalyticsEvent} from '@shopify/cli-kit/node/analytics' import {checkPortAvailability, getAvailableTCPPort} from '@shopify/cli-kit/node/tcp' +import {createSyncDiagnosticChannel} from '@shopify/diagnostics' vi.mock('./dev/fetch.js') vi.mock('./dev/processes/setup-dev-processes.js') @@ -62,6 +63,8 @@ describe('dev', () => { commandConfig: {} as any, skipDependenciesInstallation: true, tunnel: {mode: 'custom', url: 'https://localhost:3456'}, + format: 'text', + events: createSyncDiagnosticChannel(), }) expect(publicMetadata).toEqual( diff --git a/packages/app/src/cli/services/dev.ts b/packages/app/src/cli/services/dev.ts index 24e0b135bb0..6939767d4a5 100644 --- a/packages/app/src/cli/services/dev.ts +++ b/packages/app/src/cli/services/dev.ts @@ -16,6 +16,7 @@ import {DevProcessFunction} from './dev/processes/types.js' import {getCachedAppInfo, setCachedAppInfo} from './local-storage.js' import {fetchAppRemoteConfiguration} from './app/select-app.js' import {DevSessionStatusManager} from './dev/processes/dev-session/dev-session-status-manager.js' +import {DevEvent, DevFormat} from './dev/json.js' import {TunnelMode} from './dev/tunnel-mode.js' import {PortDetail, renderPortWarnings} from './dev/port-warnings.js' import {DeveloperPlatformClient} from '../utilities/developer-platform-client.js' @@ -39,6 +40,7 @@ import {reportAnalyticsEvent} from '@shopify/cli-kit/node/analytics' import {OutputProcess} from '@shopify/cli-kit/node/output' import {hashString} from '@shopify/cli-kit/node/crypto' import {AbortError} from '@shopify/cli-kit/node/error' +import {SyncDiagnosticChannel} from '@shopify/diagnostics' export interface DevOptions { app: AppLinkedInterface @@ -62,6 +64,8 @@ export interface DevOptions { graphiqlPort?: number graphiqlKey?: string installMkcert?: boolean + format: DevFormat + events: SyncDiagnosticChannel } export async function dev(commandOptions: DevOptions) { @@ -357,6 +361,8 @@ async function launchDevProcesses({ organizationName: config.commandOptions.organization.businessName, configPath: config.localApp.configPath, localURL: config.network.proxyUrl, + format: config.commandOptions.format, + events: config.commandOptions.events, }) } diff --git a/packages/app/src/cli/services/dev/json.test.ts b/packages/app/src/cli/services/dev/json.test.ts new file mode 100644 index 00000000000..7d476505f35 --- /dev/null +++ b/packages/app/src/cli/services/dev/json.test.ts @@ -0,0 +1,74 @@ +import {DevEvent, devJsonEventSink, renderDevJson} from './json.js' +import {DevSessionStatusManager} from './processes/dev-session/dev-session-status-manager.js' +import {afterEach, describe, expect, test} from 'vitest' +import {createSyncDiagnosticChannel} from '@shopify/diagnostics' +import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output' +import {AbortController} from '@shopify/cli-kit/node/abort' + +afterEach(() => { + mockAndCaptureOutput().clear() +}) + +describe('renderDevJson', () => { + test('renders startup, process output, and status updates as NDJSON events', async () => { + const output = mockAndCaptureOutput() + const events = createSyncDiagnosticChannel(devJsonEventSink) + const devSessionStatusManager = new DevSessionStatusManager() + + await renderDevJson({ + processes: [ + { + prefix: 'web', + action: async (stdout, stderr) => { + stdout.write('\u001B[32mServer ready\u001B[39m\n') + stderr.write('Dependency warning\n') + devSessionStatusManager.updateStatus({ + isReady: true, + statusMessage: {message: 'Ready, watching for changes', type: 'success'}, + }) + }, + }, + ], + previewUrl: 'https://example.myshopify.com', + graphiqlUrl: 'http://localhost:3457/graphiql', + abortController: new AbortController(), + devSessionStatusManager, + events, + }) + + const renderedEvents = output + .output() + .split('\n') + .map((line) => JSON.parse(line)) + expect(renderedEvents).toEqual([ + { + type: 'started', + level: 'info', + message: 'Dev session started', + preview_url: 'https://example.myshopify.com', + graphiql_url: 'http://localhost:3457/graphiql', + }, + { + type: 'log', + level: 'info', + message: 'Server ready\n', + source: 'web', + stream: 'stdout', + }, + { + type: 'log', + level: 'info', + message: 'Dependency warning\n', + source: 'web', + stream: 'stderr', + }, + { + type: 'status', + level: 'info', + message: 'Ready, watching for changes', + status: 'success', + is_ready: true, + }, + ]) + }) +}) diff --git a/packages/app/src/cli/services/dev/json.ts b/packages/app/src/cli/services/dev/json.ts new file mode 100644 index 00000000000..413369c0398 --- /dev/null +++ b/packages/app/src/cli/services/dev/json.ts @@ -0,0 +1,109 @@ +import {DevSessionStatus, DevSessionStatusManager} from './processes/dev-session/dev-session-status-manager.js' +import {OutputProcess, outputResult, unstyled} from '@shopify/cli-kit/node/output' +import {AbortController} from '@shopify/cli-kit/node/abort' +import {DiagnosticEvent, SyncDiagnosticChannel} from '@shopify/diagnostics' +import {Writable} from 'stream' + +export type DevFormat = 'json' | 'text' + +export type DevEvent = + | (DiagnosticEvent & { + type: 'started' + level: 'info' + preview_url: string + graphiql_url?: string + }) + | (DiagnosticEvent & { + type: 'status' + level: 'info' | 'warning' + status: DevSessionStatusMessage + is_ready: boolean + preview_url?: string + graphiql_url?: string + }) + | (DiagnosticEvent & { + type: 'log' + level: 'info' + source: string + stream: 'stdout' | 'stderr' + }) + +type DevSessionStatusMessage = NonNullable['type'] | 'unknown' + +interface RenderDevJsonOptions { + processes: OutputProcess[] + previewUrl: string + graphiqlUrl?: string + abortController: AbortController + devSessionStatusManager: DevSessionStatusManager + events: SyncDiagnosticChannel +} + +export function devJsonEventSink(event: DevEvent): void { + outputResult(JSON.stringify(event)) +} + +export async function renderDevJson({ + processes, + previewUrl, + graphiqlUrl, + abortController, + devSessionStatusManager, + events, +}: RenderDevJsonOptions): Promise { + events.emit({ + type: 'started', + level: 'info', + message: 'Dev session started', + preview_url: previewUrl, + ...(graphiqlUrl ? {graphiql_url: graphiqlUrl} : {}), + }) + + const emitStatus = (status: DevSessionStatus) => { + const statusMessage = status.statusMessage + events.emit({ + type: 'status', + level: statusMessage?.type === 'error' ? 'warning' : 'info', + message: statusMessage?.message ?? 'Dev session updated', + status: statusMessage?.type ?? 'unknown', + is_ready: status.isReady, + ...(status.previewURL ? {preview_url: status.previewURL} : {}), + ...(status.graphiqlURL ? {graphiql_url: status.graphiqlURL} : {}), + }) + } + + devSessionStatusManager.on('dev-session-update', emitStatus) + + try { + await Promise.all( + processes.map(async (devProcess) => { + await devProcess.action( + createLogStream(devProcess.prefix, 'stdout', events), + createLogStream(devProcess.prefix, 'stderr', events), + abortController.signal, + ) + }), + ) + } finally { + devSessionStatusManager.off('dev-session-update', emitStatus) + } +} + +function createLogStream( + source: string, + stream: 'stdout' | 'stderr', + events: SyncDiagnosticChannel, +): Writable { + return new Writable({ + write(chunk, _encoding, callback) { + events.emit({ + type: 'log', + level: 'info', + message: unstyled(chunk.toString('utf8')), + source, + stream, + }) + callback() + }, + }) +} diff --git a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts index de0626c6c3a..93102497b24 100644 --- a/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts +++ b/packages/app/src/cli/services/dev/processes/setup-dev-processes.test.ts @@ -32,6 +32,7 @@ import {getEnvironmentVariables} from '@shopify/cli-kit/node/environment' import {isStorefrontPasswordProtected} from '@shopify/theme' import {fetchTheme} from '@shopify/cli-kit/node/themes/api' import {adminFqdn} from '@shopify/cli-kit/node/context/fqdn' +import {createSyncDiagnosticChannel} from '@shopify/diagnostics' vi.mock('@shopify/cli-kit/node/session.js') vi.mock('../fetch.js') @@ -73,6 +74,8 @@ const appContextResult = { organization: testOrganization(), store: testOrganizationStore({}), specifications: [], + format: 'text' as const, + events: createSyncDiagnosticChannel(), } describe('setup-dev-processes', () => { diff --git a/packages/app/src/cli/services/dev/ui.test.tsx b/packages/app/src/cli/services/dev/ui.test.tsx index ad6e27c29e8..3b931dac3c6 100644 --- a/packages/app/src/cli/services/dev/ui.test.tsx +++ b/packages/app/src/cli/services/dev/ui.test.tsx @@ -6,12 +6,14 @@ import {afterEach, describe, expect, test, vi} from 'vitest' import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output' import {AbortController} from '@shopify/cli-kit/node/abort' import {terminalSupportsPrompting} from '@shopify/cli-kit/node/system' +import {createSyncDiagnosticChannel} from '@shopify/diagnostics' vi.mock('@shopify/cli-kit/node/system') vi.mock('./ui/components/DevSessionUI.js') const developerPlatformClient = testDeveloperPlatformClient() const devSessionStatusManager = new DevSessionStatusManager() +const events = createSyncDiagnosticChannel() afterEach(() => { mockAndCaptureOutput().clear() @@ -35,6 +37,8 @@ describe('ui', () => { abortController, shopFqdn: 'mystore.shopify.io', devSessionStatusManager, + format: 'text', + events, }) expect(concurrentProcess.action).toHaveBeenNthCalledWith( @@ -61,6 +65,8 @@ describe('ui', () => { abortController: new AbortController(), shopFqdn: 'mystore.shopify.io', devSessionStatusManager, + format: 'text', + events, }) const output = write.mock.calls.map(([message]) => message).join('') @@ -93,6 +99,8 @@ describe('ui', () => { abortController, shopFqdn: 'mystore.shopify.io', devSessionStatusManager, + format: 'text', + events, }) await new Promise((resolve) => setTimeout(resolve, 10)) @@ -135,6 +143,8 @@ describe('ui', () => { abortController: new AbortController(), shopFqdn, devSessionStatusManager, + format: 'text', + events, }) await new Promise((resolve) => setTimeout(resolve, 10)) diff --git a/packages/app/src/cli/services/dev/ui.tsx b/packages/app/src/cli/services/dev/ui.tsx index 9eb23042433..c84e4643e40 100644 --- a/packages/app/src/cli/services/dev/ui.tsx +++ b/packages/app/src/cli/services/dev/ui.tsx @@ -1,11 +1,13 @@ import {DevSessionUI} from './ui/components/DevSessionUI.js' import {DevSessionStatusManager} from './processes/dev-session/dev-session-status-manager.js' +import {DevEvent, DevFormat, renderDevJson} from './json.js' import {DeveloperPlatformClient} from '../../utilities/developer-platform-client.js' import {OutputProcess} from '@shopify/cli-kit/node/output' import {AbortController} from '@shopify/cli-kit/node/abort' import React from 'react' import {render} from '@shopify/cli-kit/node/ui' import {terminalSupportsPrompting} from '@shopify/cli-kit/node/system' +import {SyncDiagnosticChannel} from '@shopify/diagnostics' interface DevProps { processes: OutputProcess[] @@ -13,6 +15,8 @@ interface DevProps { graphiqlUrl?: string abortController: AbortController shopFqdn: string + format: DevFormat + events: SyncDiagnosticChannel app: { id: string developerPlatformClient: DeveloperPlatformClient @@ -32,6 +36,8 @@ export async function renderDev({ organizationName, configPath, localURL, + format, + events, }: DevProps & { devSessionStatusManager: DevSessionStatusManager appURL?: string @@ -40,6 +46,17 @@ export async function renderDev({ configPath?: string localURL?: string }) { + if (format === 'json') { + return renderDevJson({ + processes, + previewUrl, + graphiqlUrl, + abortController, + devSessionStatusManager, + events, + }) + } + if (terminalSupportsPrompting()) { return render( ] [--checkout-cart-url ] [--client-id | -c ] - [--install-mkcert --use-localhost] [--localhost-port ] [--no-color] [--no-update] [--notify ] [--path - ] [--reset | ] [--skip-dependencies-installation] [-s ] [--store-password ] + [--install-mkcert --use-localhost] [-j] [--localhost-port ] [--no-color] [--no-update] [--notify ] + [--path ] [--reset | ] [--skip-dependencies-installation] [-s ] [--store-password ] [--subscription-product-url ] [-t ] [--theme-app-extension-port ] [--tunnel-url | ] [--verbose] @@ -663,6 +663,10 @@ FLAGS The name of the app configuration. [env: SHOPIFY_FLAG_APP_CONFIG] + -j, --json + Output the result as JSON. Automatically disables color output. + [env: SHOPIFY_FLAG_JSON] + -s, --store= Store URL. Must be an existing development or Shopify Plus sandbox store. [env: SHOPIFY_FLAG_STORE] diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index d4735ee1f7a..0c76706db00 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -1067,6 +1067,15 @@ "name": "install-mkcert", "type": "boolean" }, + "json": { + "allowNo": false, + "char": "j", + "description": "Output the result as JSON. Automatically disables color output.", + "env": "SHOPIFY_FLAG_JSON", + "hidden": false, + "name": "json", + "type": "boolean" + }, "localhost-port": { "description": "Port to use for localhost. Must be between 1 and 65535.", "env": "SHOPIFY_FLAG_LOCALHOST_PORT", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32ba5fd0f0e..509607f4686 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -176,6 +176,9 @@ importers: '@shopify/cli-kit': specifier: 4.5.0 version: link:../cli-kit + '@shopify/diagnostics': + specifier: 4.5.0 + version: link:../diagnostics '@shopify/organizations': specifier: 4.5.0 version: link:../organizations