Skip to content
Merged
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/auth-logout-json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli': minor
---

Add typed JSON output to `auth logout`.
38 changes: 37 additions & 1 deletion docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4206,9 +4206,45 @@
"description": "Print the command's JSON schemas.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_JSON_SCHEMA"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/auth-logout.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/auth-logout.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--no-input",
"value": "''",
"description": "Disable interactive prompts and browser authentication.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_NO_INPUT"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/auth-logout.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/auth-logout.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 authlogout {\n /**\n * Print the command's JSON schemas.\n * @environment SHOPIFY_FLAG_JSON_SCHEMA\n */\n '--json-schema'?: ''\n}"
"value": "export interface authlogout {\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 * Disable interactive prompts and browser authentication.\n * @environment SHOPIFY_FLAG_NO_INPUT\n */\n '--no-input'?: ''\n\n /**\n * Increase the verbosity of the output. May include sensitive data.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"commands": {
Expand Down
40 changes: 39 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3725,15 +3725,53 @@ Logs you out of the Shopify account or Partner account and store.

```
USAGE
$ shopify auth logout [--json-schema]
$ shopify auth logout [-j] [--json-schema] [--no-color] [--no-input] [--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]

--no-input
Disable interactive prompts and browser authentication.
[env: SHOPIFY_FLAG_NO_INPUT]

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

DESCRIPTION
Logs you out of the Shopify account or Partner account and store.

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

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

```json
{
"type": "object",
"properties": {
"status": {
"type": "string",
"const": "success"
}
},
"required": [
"status"
],
"additionalProperties": false,
"title": "AuthLogoutResult",
"$schema": "http://json-schema.org/draft-07/schema#"
}
```
```

## `shopify commands`
Expand Down
35 changes: 34 additions & 1 deletion packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5571,15 +5571,48 @@
],
"args": {
},
"description": "Logs you out of the Shopify account or Partner account and store.",
"description": "Logs you out of the Shopify account or Partner account and store.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\nOutput from `--json` conforms to the `AuthLogoutResult` schema.\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"status\": {\n \"type\": \"string\",\n \"const\": \"success\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"additionalProperties\": false,\n \"title\": \"AuthLogoutResult\",\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```",
"descriptionWithMarkdown": "Logs you out of the Shopify account or Partner account and store.",
"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"
},
"no-input": {
"allowNo": false,
"description": "Disable interactive prompts and browser authentication.",
"env": "SHOPIFY_FLAG_NO_INPUT",
"name": "no-input",
"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
121 changes: 121 additions & 0 deletions packages/cli/src/cli/commands/auth/logout-json.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Logout from './logout.js'
import {logout} from '@shopify/cli-kit/node/session'
import {launchCLI} from '@shopify/cli-kit/node/cli-launcher'
import {ShopifyConfig} from '@shopify/cli-kit/node/custom-oclif-loader'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputInfo, unstyled} from '@shopify/cli-kit/node/output'
import {mockAndCaptureOutput, withCapturedStandardStreams} from '@shopify/cli-kit/node/testing/output'
import {beforeEach, afterEach, expect, test, vi} from 'vitest'

vi.mock('@shopify/cli-kit/node/session')

beforeEach(() => {
vi.stubEnv('CI', '1')
vi.stubEnv('SHOPIFY_CLI_NO_ANALYTICS', '1')
vi.spyOn(ShopifyConfig.prototype, 'runHook').mockResolvedValue({successes: [], failures: []})
})

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

test.each(['--json', '-j'])('writes one completed result through the launcher with %s', async (flag) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of these tests seem like they could be shared somewhere - like have an isValidJsonCommand(command, ...workingFlagSet) function somewhere which runs the basic tests in a generalizable way rather than rewriting all the tests for each command.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, i'll take a look at that and probably update it in a future PR

await withCapturedStandardStreams(async ({stdout, stderr}) => {
await launchCLI({
moduleURL: import.meta.url,
argv: ['auth', 'logout', flag],
lazyCommandLoader: async () => Logout,
})

expect(logout).toHaveBeenCalledExactlyOnceWith()
expect(stdout()).toBe(`${JSON.stringify({status: 'success'}, null, 2)}\n`)
expect(stderr()).toBe('')
})
})

test('supports the JSON environment flag', async () => {
vi.stubEnv('SHOPIFY_FLAG_JSON', '1')

await withCapturedStandardStreams(async ({stdout, stderr}) => {
await Logout.run([], import.meta.url)

expect(JSON.parse(stdout())).toEqual({status: 'success'})
expect(stderr()).toBe('')
})
})

test.each([false, true])('keeps no-input independent from JSON output: %s', async (json) => {
await withCapturedStandardStreams(async ({stdout, stderr}) => {
await Logout.run(['--no-input', ...(json ? ['--json'] : [])], import.meta.url)

if (json) {
expect(JSON.parse(stdout())).toEqual({status: 'success'})
expect(stderr()).toBe('')
} else {
expect(stdout()).toBe('')
expect(unstyled(stderr())).toBe('✅ Success! Logged out from all the accounts.\n')
}
})
})

test('exposes the schema in help', () => {
expect(Logout.description).toContain('Output from `--json` conforms to the `AuthLogoutResult` schema.')
})

test.each([{}, {status: 'failed'}, {status: true}, {status: null}, {status: 'success', accessToken: 'secret'}])(
'rejects an invalid result %j',
(value) => {
expect(() => Logout.jsonOutputSchema.validate(value)).toThrow()
},
)

test('discovers the schema without logging out', async () => {
await withCapturedStandardStreams(async ({stdout, stderr}) => {
await launchCLI({
moduleURL: import.meta.url,
argv: ['auth', 'logout', '--json-schema'],
lazyCommandLoader: async () => Logout,
})

expect(JSON.parse(stdout()).definitions.Result).toMatchObject({
type: 'object',
properties: {status: {type: 'string', const: 'success'}},
required: ['status'],
additionalProperties: false,
})
expect(logout).not.toHaveBeenCalled()
expect(stderr()).toBe('')
})
})

test('writes diagnostics to stderr separately from the result', async () => {
vi.mocked(logout).mockImplementation(async () => {
outputInfo('Session cleanup diagnostic')
})

await withCapturedStandardStreams(async ({stdout, stderr}) => {
await Logout.run(['--json'], import.meta.url)

expect(JSON.parse(stdout())).toEqual({status: 'success'})
expect(JSON.parse(stderr())).toMatchObject({
type: 'diagnostic',
level: 'info',
message: 'Session cleanup diagnostic',
})
})
})

test('writes only the fatal error and preserves the failure exit code', async () => {
vi.stubEnv('SHOPIFY_FLAG_JSON', '1')
const exit = vi.spyOn(process, 'exit').mockReturnValue(undefined as never)
vi.mocked(logout).mockRejectedValue(new AbortError('Cannot clear sessions.'))

await withCapturedStandardStreams(async ({stdout, stderr}) => {
await Logout.run(['--json'], import.meta.url)

expect(JSON.parse(stdout())).toEqual({error: {type: 'abort', message: 'Cannot clear sessions.'}})
expect(stderr()).toBe('')
expect(exit).toHaveBeenCalledExactlyOnceWith(1)
})
})
27 changes: 24 additions & 3 deletions packages/cli/src/cli/commands/auth/logout.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,33 @@
import Command from '@shopify/cli-kit/node/base-command'
import {outputSuccess} from '@shopify/cli-kit/node/output'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
import {outputResult, outputSuccess} from '@shopify/cli-kit/node/output'
import {logout} from '@shopify/cli-kit/node/session'
import {defineJsonOutputSchema} from '@shopify/cli-kit/node/json-output-schema'
import {zod} from '@shopify/cli-kit/node/schema'

const authLogoutJsonOutputSchema = defineJsonOutputSchema({
name: 'AuthLogoutResult',
schema: zod.object({status: zod.literal('success')}).strict(),
})

export default class Logout extends Command {
static description = 'Logs you out of the Shopify account or Partner account and store.'
static descriptionWithMarkdown = 'Logs you out of the Shopify account or Partner account and store.'

static description = this.descriptionForHelp()

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

static get jsonOutputSchema() {
return authLogoutJsonOutputSchema
}

async run(): Promise<void> {
const {flags} = await this.parse(Logout)
await logout()
outputSuccess('Logged out from all the accounts')
if (flags.json) {
outputResult(authLogoutJsonOutputSchema.encode({status: 'success'}))
} else {
outputSuccess('Logged out from all the accounts')
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const commandExceptions = [
'packages/app/src/cli/commands/app/subscription-migrations/unschedule.ts',
'packages/app/src/cli/commands/app/webhook/trigger.ts',
'packages/cli/src/cli/commands/auth/login.ts',
'packages/cli/src/cli/commands/auth/logout.ts',
'packages/cli/src/cli/commands/doc/fetch.ts',
'packages/cli/src/cli/commands/doc/search.ts',
'packages/cli/src/cli/commands/upgrade.ts',
Expand Down
Loading