Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6343,6 +6343,49 @@
"pluginType": "core",
"strict": true
},
"kitchen-sink:json-output": {
"aliases": [
],
"args": {
},
"description": "Exercise command JSON output infrastructure.\n\nOutput from `--json` conforms to the `KitchenSinkJsonOutputResult` schema.\n\nUse `--json-schema` to print the schema directly:\n\n```ts\ninterface KitchenSinkJsonOutputResult {\n items: SampleItem[]\n}\n\ninterface SampleItem {\n id: number\n name: string\n}\n```",
"descriptionWithMarkdown": "Exercise command JSON output infrastructure.",
"enableJsonFlag": false,
"flags": {
"fail": {
"allowNo": false,
"description": "Fail with a sample error.",
"env": "SHOPIFY_FLAG_FAIL",
"name": "fail",
"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"
},
"json-schema": {
"allowNo": false,
"description": "Print the command's JSON schemas.",
"env": "SHOPIFY_FLAG_JSON_SCHEMA",
"name": "json-schema",
"type": "boolean"
}
},
"hasDynamicHelp": false,
"hidden": true,
"hiddenAliases": [
],
"id": "kitchen-sink:json-output",
"pluginAlias": "@shopify/cli",
"pluginName": "@shopify/cli",
"pluginType": "core",
"strict": true
},
"kitchen-sink:prompts": {
"aliases": [
],
Expand Down
45 changes: 45 additions & 0 deletions packages/cli/src/cli/commands/kitchen-sink/json-output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import KitchenSinkJsonOutput from './json-output.js'
import {kitchenSinkJsonOutputSchema} from '../../services/kitchen-sink/json-output.js'
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
import {Config} from '@oclif/core'
import {afterEach, describe, expect, test} from 'vitest'
import {fileURLToPath} from 'node:url'

afterEach(() => {
mockAndCaptureOutput().clear()
})

describe('kitchen-sink json-output command', () => {
test('prints the validated JSON result', async () => {
const output = mockAndCaptureOutput()
await KitchenSinkJsonOutput.run(['--json'], import.meta.url)

const sideEvents = output
.info()
.split('\n')
.slice(0, 2)
.map((line) => JSON.parse(line) as unknown)
expect(sideEvents).toMatchObject([
{type: 'diagnostic', level: 'info', message: 'Preparing the sample result.'},
{type: 'progress', message: 'Prepared 1 item.', current: 1, total: 1},
])
expect(output.info()).toContain(kitchenSinkJsonOutputSchema.encode({items: [{id: 1, name: 'Example'}]}))
})

test('prints a human-readable result', async () => {
const output = mockAndCaptureOutput()
await KitchenSinkJsonOutput.run([], import.meta.url)

expect(output.info()).toContain('Prepared 1 item.')
})

test('exposes its JSON schema', () => {
expect(KitchenSinkJsonOutput.jsonOutputSchema).toBe(kitchenSinkJsonOutputSchema)
})

test('can exercise JSON error handling', async () => {
const config = await Config.load(fileURLToPath(import.meta.url))

await expect(new KitchenSinkJsonOutput(['--fail'], config).run()).rejects.toThrow('Sample command failure.')
})
})
42 changes: 42 additions & 0 deletions packages/cli/src/cli/commands/kitchen-sink/json-output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {createKitchenSinkJsonOutput, kitchenSinkJsonOutputSchema} from '../../services/kitchen-sink/json-output.js'
import {createCommandEventChannel} from '@shopify/cli-kit/common/command-events'
import Command from '@shopify/cli-kit/node/base-command'
import {jsonFlag} from '@shopify/cli-kit/node/cli'
import {AbortError} from '@shopify/cli-kit/node/error'
import {renderCommandEvent, renderCommandEventAsJson} from '@shopify/cli-kit/node/command-events'
import {outputResult} from '@shopify/cli-kit/node/output'
import {Flags} from '@oclif/core'

export default class KitchenSinkJsonOutput extends Command {
static descriptionWithMarkdown = 'Exercise command JSON output infrastructure.'
static description = this.descriptionWithoutMarkdown()
static hidden = true

static flags = {
...jsonFlag,
fail: Flags.boolean({
description: 'Fail with a sample error.',
env: 'SHOPIFY_FLAG_FAIL',
default: false,
}),
}

static get jsonOutputSchema() {
return kitchenSinkJsonOutputSchema
}

async run(): Promise<void> {
const {flags} = await this.parse(KitchenSinkJsonOutput)
const events = createCommandEventChannel({
sink: flags.json ? renderCommandEventAsJson : renderCommandEvent,
})
const result = createKitchenSinkJsonOutput(events)

if (flags.fail) {
events.emit({type: 'diagnostic', level: 'warning', message: 'Failing as requested.'})
throw new AbortError('Sample command failure.')
}

outputResult(flags.json ? kitchenSinkJsonOutputSchema.encode(result) : `Prepared ${result.items.length} item.`)
}
}
24 changes: 24 additions & 0 deletions packages/cli/src/cli/services/kitchen-sink/json-output.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {createKitchenSinkJsonOutput, kitchenSinkJsonOutputSchema} from './json-output.js'
import {describe, expect, test, vi} from 'vitest'
import type {CommandEventChannel} from '@shopify/cli-kit/common/command-events'

describe('kitchen sink JSON output service', () => {
test('returns a valid result and reports side events', () => {
const emit = vi.fn<CommandEventChannel['emit']>()

const result = createKitchenSinkJsonOutput({emit})

expect(kitchenSinkJsonOutputSchema.validate(result)).toEqual({items: [{id: 1, name: 'Example'}]})
expect(emit).toHaveBeenNthCalledWith(1, {
type: 'diagnostic',
level: 'info',
message: 'Preparing the sample result.',
})
expect(emit).toHaveBeenNthCalledWith(2, {
type: 'progress',
message: 'Prepared 1 item.',
current: 1,
total: 1,
})
})
})
24 changes: 24 additions & 0 deletions packages/cli/src/cli/services/kitchen-sink/json-output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {defineJsonOutputSchema, type InferJsonOutputSchema} from '@shopify/cli-kit/node/json-output-schema'
import {zod} from '@shopify/cli-kit/node/schema'
import type {CommandEventChannel} from '@shopify/cli-kit/common/command-events'

const SampleItemSchema = zod.object({
id: zod.number(),
name: zod.string(),
})

export const kitchenSinkJsonOutputSchema = defineJsonOutputSchema({
name: 'KitchenSinkJsonOutputResult',
schema: zod.object({items: zod.array(SampleItemSchema)}),
definitions: {SampleItem: SampleItemSchema},
})

type KitchenSinkJsonOutputResult = InferJsonOutputSchema<typeof kitchenSinkJsonOutputSchema>

export function createKitchenSinkJsonOutput(events: CommandEventChannel): KitchenSinkJsonOutputResult {
events.emit({type: 'diagnostic', level: 'info', message: 'Preparing the sample result.'})

const result = {items: [{id: 1, name: 'Example'}]}
events.emit({type: 'progress', message: 'Prepared 1 item.', current: 1, total: 1})
return result
}
2 changes: 2 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Logout from './cli/commands/auth/logout.js'
import Login from './cli/commands/auth/login.js'
import CommandFlags from './cli/commands/debug/command-flags.js'
import KitchenSinkAsync from './cli/commands/kitchen-sink/async.js'
import KitchenSinkJsonOutput from './cli/commands/kitchen-sink/json-output.js'
import KitchenSinkPrompts from './cli/commands/kitchen-sink/prompts.js'
import KitchenSinkStatic from './cli/commands/kitchen-sink/static.js'
import KitchenSink from './cli/commands/kitchen-sink/index.js'
Expand Down Expand Up @@ -151,6 +152,7 @@ export const COMMANDS: any = {
'debug:command-flags': CommandFlags,
'kitchen-sink': KitchenSink,
'kitchen-sink:async': KitchenSinkAsync,
'kitchen-sink:json-output': KitchenSinkJsonOutput,
'kitchen-sink:prompts': KitchenSinkPrompts,
'kitchen-sink:static': KitchenSinkStatic,
'doctor-release': Doctor,
Expand Down
Loading