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
5 changes: 5 additions & 0 deletions .changeset/version-json-output.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli': minor
---

Add JSON output to the `version` command.
29 changes: 28 additions & 1 deletion docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -8566,9 +8566,36 @@
"description": "Print the command's JSON schemas.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_JSON_SCHEMA"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/version.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--no-color",
"value": "''",
"description": "Disable color output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_NO_COLOR"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/version.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--verbose",
"value": "''",
"description": "Increase the verbosity of the output. May include sensitive data.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_VERBOSE"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/version.interface.ts",
"syntaxKind": "PropertySignature",
"name": "-j, --json",
"value": "''",
"description": "Output the result as JSON. Automatically disables color output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_JSON"
}
],
"value": "export interface version {\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n}"
"value": "export interface version {\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Increase the verbosity of the output. May include sensitive data.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
}
}
35 changes: 34 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5831,14 +5831,47 @@ Shopify CLI version currently installed.

```
USAGE
$ shopify version [--json-schema]
$ shopify version [-j] [--json-schema] [--no-color] [--verbose]

FLAGS
-j, --json
Output the result as JSON. Automatically disables color output.
[env: SHOPIFY_FLAG_JSON]

--json-schema
Print the command's JSON schemas.
[env: SHOPIFY_FLAG_JSON_SCHEMA]

--no-color
Disable color output.
[env: SHOPIFY_FLAG_NO_COLOR]

--verbose
Increase the verbosity of the output. May include sensitive data.
[env: SHOPIFY_FLAG_VERBOSE]

DESCRIPTION
Shopify CLI version currently installed.

Output from `--json` conforms to the `VersionResult` schema.

Use `--json-schema` to print the result, error, and event schemas.

```json
{
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": [
"version"
],
"additionalProperties": false,
"title": "VersionResult",
"$schema": "http://json-schema.org/draft-07/schema#"
}
```
```
<!-- commandsstop -->
28 changes: 27 additions & 1 deletion packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11492,15 +11492,41 @@
],
"args": {
},
"description": "Shopify CLI version currently installed.",
"description": "Shopify CLI version currently installed.\n\nOutput from `--json` conforms to the `VersionResult` schema.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"version\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"version\"\n ],\n \"additionalProperties\": false,\n \"title\": \"VersionResult\",\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```",
"descriptionWithMarkdown": "Shopify CLI version currently installed.",
"enableJsonFlag": false,
"flags": {
"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"
},
"no-color": {
"allowNo": false,
"description": "Disable color output.",
"env": "SHOPIFY_FLAG_NO_COLOR",
"hidden": false,
"name": "no-color",
"type": "boolean"
},
"verbose": {
"allowNo": false,
"description": "Increase the verbosity of the output. May include sensitive data.",
"env": "SHOPIFY_FLAG_VERBOSE",
"hidden": false,
"name": "verbose",
"type": "boolean"
}
},
"hasDynamicHelp": false,
Expand Down
111 changes: 105 additions & 6 deletions packages/cli/src/cli/commands/version.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,114 @@
import Version from './version.js'
import {versionService} from '../services/commands/version.js'
import {describe, test, vi, expect} from 'vitest'
import {versionService} from '../services/commands/version/index.js'
import {versionJsonOutputSchema} from '../services/commands/version/types.js'
import {CLI_KIT_VERSION} from '@shopify/cli-kit/common/version'
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
import {execa} from 'execa'
import {afterEach, describe, expect, test, vi} from 'vitest'

vi.mock('../services/commands/version.js')
vi.mock('../services/commands/version/index.js', async (importOriginal) => ({
...(await importOriginal<typeof import('../services/commands/version/index.js')>()),
versionService: vi.fn(),
}))

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

const commandUrl = new URL('./version.ts', import.meta.url).href
const sourceLoaderUrl = new URL('../../../../cli-kit/test/fixtures/cli-kit-source-loader.js', import.meta.url).href

const runVersion = async (arguments_: string[]) => {
const script = `
const {default: Version} = await import(${JSON.stringify(commandUrl)})
const argv = ${JSON.stringify(arguments_)}
if (argv.includes('--json-schema')) {
// Schema inspection runs in the launcher before the command lifecycle.
const {launchCLI} = await import('@shopify/cli-kit/node/cli-launcher')
await launchCLI({
moduleURL: ${JSON.stringify(commandUrl)},
argv: ['version', ...argv],
lazyCommandLoader: async () => Version,
})
} else {
await Version.run(argv, ${JSON.stringify(commandUrl)})
}
`

return execa(
process.execPath,
['--loader', 'ts-node/esm', '--loader', sourceLoaderUrl, '--input-type=module', '--eval', script],
{
env: {
...process.env,
FORCE_COLOR: '0',
NODE_NO_WARNINGS: '1',
SHOPIFY_CLI_ENV: 'development',
SHOPIFY_CLI_NO_ANALYTICS: '1',
SHOPIFY_UNIT_TEST: 'false',
},
reject: false,
stripFinalNewline: false,
},
)
}

describe('version command', () => {
test('launches service', async () => {
vi.mocked(versionService).mockResolvedValue()
test('writes the raw version text by default', async () => {
const outputMock = mockAndCaptureOutput()
vi.mocked(versionService).mockResolvedValue({version: '2.2.2'})

await Version.run([], import.meta.url)

expect(versionService).toHaveBeenCalled()
expect(versionService).toHaveBeenCalledOnce()
expect(outputMock.output()).toBe('2.2.2')
expect(outputMock.warn()).toBe('')
})

test('writes one JSON document when requested', async () => {
const outputMock = mockAndCaptureOutput()
vi.mocked(versionService).mockResolvedValue({version: '2.2.2'})

await Version.run(['--json'], import.meta.url)

expect(outputMock.output()).toBe(JSON.stringify({version: '2.2.2'}, null, 2))
expect(JSON.parse(outputMock.output())).toEqual({version: '2.2.2'})
expect(outputMock.warn()).toBe('')
})

test('exposes the object schema and JSON flags in help', () => {
expect(Version.jsonOutputSchema).toBe(versionJsonOutputSchema)
expect(Version.flags.json).toBeDefined()
expect(Version.description).toContain('Output from `--json` conforms to the `VersionResult` schema.')
expect(Version.description).toContain('"type": "object"')
})

test('writes the installed version without stderr output', {timeout: 20000}, async () => {
const result = await runVersion([])

expect(result.exitCode).toBe(0)
expect(result.stderr).toBe('')
expect(result.stdout).toBe(`${CLI_KIT_VERSION}\n`)
})

test('writes the installed version as JSON without stderr output', {timeout: 20000}, async () => {
const result = await runVersion(['--json'])

expect(result.exitCode).toBe(0)
expect(result.stderr).toBe('')
expect(result.stdout).toBe(`${JSON.stringify({version: CLI_KIT_VERSION}, null, 2)}\n`)
expect(JSON.parse(result.stdout)).toEqual({version: CLI_KIT_VERSION})
})

test('writes a schema with an object result without stderr output', {timeout: 20000}, async () => {
const result = await runVersion(['--json-schema'])

expect(result.exitCode).toBe(0)
expect(result.stderr).toBe('')
const definition = JSON.parse(result.stdout).definitions.Result
expect(definition.type).toBe('object')
expect(definition.properties.version).toEqual({type: 'string'})
expect(definition.required).toEqual(['version'])
expect(definition.additionalProperties).toBe(false)
})
})
23 changes: 20 additions & 3 deletions packages/cli/src/cli/commands/version.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import {versionService} from '../services/commands/version.js'
import {versionService} from '../services/commands/version/index.js'
import {presentVersionResult} from '../services/commands/version/result.js'
import {versionJsonOutputSchema} from '../services/commands/version/types.js'
import Command from '@shopify/cli-kit/node/base-command'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'

export default class Version extends Command {
static description = 'Shopify CLI version currently installed.'
static descriptionWithMarkdown = 'Shopify CLI version currently installed.'

static description = this.descriptionForHelp()

static flags = {
...globalFlags,
...jsonFlag,
}

static get jsonOutputSchema() {
return versionJsonOutputSchema
}

async run(): Promise<void> {
await versionService()
const {flags} = await this.parse(Version)
const result = await versionService()

presentVersionResult(result, flags.json ? 'json' : 'text')
}
}
25 changes: 0 additions & 25 deletions packages/cli/src/cli/services/commands/version.test.ts

This file was deleted.

6 changes: 0 additions & 6 deletions packages/cli/src/cli/services/commands/version.ts

This file was deleted.

19 changes: 19 additions & 0 deletions packages/cli/src/cli/services/commands/version/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {versionService} from './index.js'
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
import {afterEach, describe, expect, test, vi} from 'vitest'

vi.mock('@shopify/cli-kit/common/version', () => ({CLI_KIT_VERSION: '2.2.2'}))

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

describe('version service', () => {
test('returns the installed version as an object without writing output', async () => {
const outputMock = mockAndCaptureOutput()

await expect(versionService()).resolves.toEqual({version: '2.2.2'})

expect(outputMock.output()).toBe('')
})
})
6 changes: 6 additions & 0 deletions packages/cli/src/cli/services/commands/version/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {CLI_KIT_VERSION} from '@shopify/cli-kit/common/version'
import type {VersionResult} from './types.js'

export async function versionService(): Promise<VersionResult> {
return {version: CLI_KIT_VERSION}
}
47 changes: 47 additions & 0 deletions packages/cli/src/cli/services/commands/version/result.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {presentVersionResult} from './result.js'
import {versionJsonOutputSchema} from './types.js'
import {mockAndCaptureOutput} from '@shopify/cli-kit/node/testing/output'
import {afterEach, describe, expect, test} from 'vitest'

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

describe('presentVersionResult', () => {
test('writes the raw version text by default', () => {
const outputMock = mockAndCaptureOutput()

presentVersionResult({version: '2.2.2'}, 'text')

expect(outputMock.output()).toBe('2.2.2')
expect(outputMock.warn()).toBe('')
})

test('writes one JSON document when requested', () => {
const outputMock = mockAndCaptureOutput()

presentVersionResult({version: '2.2.2'}, 'json')

expect(outputMock.output()).toBe(JSON.stringify({version: '2.2.2'}, null, 2))
expect(JSON.parse(outputMock.output())).toEqual({version: '2.2.2'})
})
})

describe('version JSON result contract', () => {
test('encodes an object with a version string', () => {
expect(versionJsonOutputSchema.encode({version: '2.2.2'})).toBe(JSON.stringify({version: '2.2.2'}, null, 2))
})

test.each([['2.2.2'], [{}], [{version: 1}]])('rejects %j', (value) => {
expect(() => versionJsonOutputSchema.validate(value)).toThrow()
})

test('declares a closed object schema with a required version string', () => {
expect(versionJsonOutputSchema.jsonSchema).toMatchObject({
type: 'object',
properties: {version: {type: 'string'}},
required: ['version'],
additionalProperties: false,
})
})
})
Loading
Loading