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
170 changes: 170 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4385,6 +4385,80 @@ DESCRIPTION

Use `--json` for machine-readable output.

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

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

```json
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"displayName": {
"type": "string"
},
"subdomain": {
"type": "string"
},
"organizationId": {
"type": "string"
},
"organizationName": {
"type": "string"
},
"storeOwner": {
"$ref": "#/definitions/StoreInfoStoreOwner"
},
"type": {
"type": "string"
},
"plan": {
"type": "string"
},
"featurePreview": {
"type": "string"
},
"adminUrl": {
"type": "string"
},
"accessUrl": {
"type": "string"
},
"saveUrl": {
"type": "string"
},
"authScopes": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"subdomain"
],
"additionalProperties": false,
"title": "StoreInfoResult",
"definitions": {
"StoreInfoStoreOwner": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
}
},
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
```

EXAMPLES
$ shopify store info --store shop.myshopify.com

Expand Down Expand Up @@ -4437,6 +4511,102 @@ DESCRIPTION

Run `shopify organization list` to find organization IDs.

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

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

```json
{
"type": "object",
"properties": {
"stores": {
"type": "array",
"items": {
"$ref": "#/definitions/StoreListEntry"
}
},
"organization": {
"$ref": "#/definitions/StoreListOrganization"
},
"storeType": {
"type": "string",
"enum": [
"dev",
"production",
"client-transfer",
"collaborator"
]
},
"notice": {
"type": "string"
},
"truncated": {
"type": "boolean"
}
},
"required": [
"stores"
],
"additionalProperties": false,
"title": "StoreListResult",
"definitions": {
"StoreListEntry": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"store": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"organizationId": {
"type": "string"
},
"organizationName": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
},
"plan": {
"type": "string"
}
},
"required": [
"store",
"createdAt",
"organizationId",
"organizationName"
],
"additionalProperties": false
},
"StoreListOrganization": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
],
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#"
}
```

EXAMPLES
$ shopify store list

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/oclif.manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8765,7 +8765,7 @@
"args": {
},
"customPluginName": "@shopify/store",
"description": "Returns available metadata about a store you have access to, such as its id, display name, subdomain, organization, store owner, type, plan, feature preview, admin URL, and access and save URLs for preview stores.\n\nSome details may be omitted when they are not available for the store.\n\nUse `--json` for machine-readable output.",
"description": "Returns available metadata about a store you have access to, such as its id, display name, subdomain, organization, store owner, type, plan, feature preview, admin URL, and access and save URLs for preview stores.\n\nSome details may be omitted when they are not available for the store.\n\nUse `--json` for machine-readable output.\n\nOutput from `--json` conforms to the `StoreInfoResult` schema.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"displayName\": {\n \"type\": \"string\"\n },\n \"subdomain\": {\n \"type\": \"string\"\n },\n \"organizationId\": {\n \"type\": \"string\"\n },\n \"organizationName\": {\n \"type\": \"string\"\n },\n \"storeOwner\": {\n \"$ref\": \"#/definitions/StoreInfoStoreOwner\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"plan\": {\n \"type\": \"string\"\n },\n \"featurePreview\": {\n \"type\": \"string\"\n },\n \"adminUrl\": {\n \"type\": \"string\"\n },\n \"accessUrl\": {\n \"type\": \"string\"\n },\n \"saveUrl\": {\n \"type\": \"string\"\n },\n \"authScopes\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n },\n \"required\": [\n \"subdomain\"\n ],\n \"additionalProperties\": false,\n \"title\": \"StoreInfoResult\",\n \"definitions\": {\n \"StoreInfoStoreOwner\": {\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\"\n },\n \"email\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```",
"descriptionWithMarkdown": "Returns available metadata about a store you have access to, such as its id, display name, subdomain, organization, store owner, type, plan, feature preview, admin URL, and access and save URLs for preview stores.\n\nSome details may be omitted when they are not available for the store.\n\nUse `--json` for machine-readable output.",
"examples": [
"<%= config.bin %> <%= command.id %> --store shop.myshopify.com",
Expand Down Expand Up @@ -8831,7 +8831,7 @@
"args": {
},
"customPluginName": "@shopify/store",
"description": "Lists stores in a Shopify organization available to the current CLI account.\n\nWhen more than one organization is available, the command prompts you to pick one unless you provide `--organization-id`. In that case, `--organization-id` is required in non-interactive environments.\n\nRun `<%= config.bin %> organization list` to find organization IDs.",
"description": "Lists stores in a Shopify organization available to the current CLI account.\n\nWhen more than one organization is available, the command prompts you to pick one unless you provide `--organization-id`. In that case, `--organization-id` is required in non-interactive environments.\n\nRun `<%= config.bin %> organization list` to find organization IDs.\n\nOutput from `--json` conforms to the `StoreListResult` schema.\n\nUse `--json-schema` to print the result, error, and event schemas.\n\n```json\n{\n \"type\": \"object\",\n \"properties\": {\n \"stores\": {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/StoreListEntry\"\n }\n },\n \"organization\": {\n \"$ref\": \"#/definitions/StoreListOrganization\"\n },\n \"storeType\": {\n \"type\": \"string\",\n \"enum\": [\n \"dev\",\n \"production\",\n \"client-transfer\",\n \"collaborator\"\n ]\n },\n \"notice\": {\n \"type\": \"string\"\n },\n \"truncated\": {\n \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"stores\"\n ],\n \"additionalProperties\": false,\n \"title\": \"StoreListResult\",\n \"definitions\": {\n \"StoreListEntry\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"store\": {\n \"type\": \"string\"\n },\n \"createdAt\": {\n \"type\": \"string\"\n },\n \"organizationId\": {\n \"type\": \"string\"\n },\n \"organizationName\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n },\n \"type\": {\n \"type\": \"string\"\n },\n \"plan\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"store\",\n \"createdAt\",\n \"organizationId\",\n \"organizationName\"\n ],\n \"additionalProperties\": false\n },\n \"StoreListOrganization\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\n \"type\": \"string\"\n },\n \"name\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"id\",\n \"name\"\n ],\n \"additionalProperties\": false\n }\n },\n \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n```",
"descriptionWithMarkdown": "Lists stores in a Shopify organization available to the current CLI account.\n\nWhen more than one organization is available, the command prompts you to pick one unless you provide `--organization-id`. In that case, `--organization-id` is required in non-interactive environments.\n\nRun `<%= config.bin %> organization list` to find organization IDs.",
"examples": [
"<%= config.bin %> <%= command.id %>",
Expand Down
9 changes: 6 additions & 3 deletions packages/store/src/cli/commands/store/info.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import StoreInfo from './info.js'
import {getStoreInfo} from '../../services/store/info/index.js'
import {renderStoreInfoResult} from '../../services/store/info/result.js'
import {storeInfoJsonOutputSchema} from '../../services/store/info/types.js'
import {beforeEach, describe, expect, test, vi} from 'vitest'

vi.mock('../../services/store/info/index.js')
Expand All @@ -18,9 +19,7 @@ describe('store info command', () => {
test('passes the store flag through to the service', async () => {
await StoreInfo.run(['--store', 'shop.myshopify.com'])

expect(getStoreInfo).toHaveBeenCalledWith({
store: 'shop.myshopify.com',
})
expect(getStoreInfo).toHaveBeenCalledWith({store: 'shop.myshopify.com'})
expect(renderStoreInfoResult).toHaveBeenCalledWith(
expect.objectContaining({subdomain: 'shop.myshopify.com'}),
'text',
Expand All @@ -37,4 +36,8 @@ describe('store info command', () => {
expect(StoreInfo.flags.store).toBeDefined()
expect(StoreInfo.flags.json).toBeDefined()
})

test('exposes the JSON output schema', () => {
expect(StoreInfo.jsonOutputSchema).toBe(storeInfoJsonOutputSchema)
})
})
5 changes: 5 additions & 0 deletions packages/store/src/cli/commands/store/info.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {getStoreInfo} from '../../services/store/info/index.js'
import {renderStoreInfoResult} from '../../services/store/info/result.js'
import {storeInfoJsonOutputSchema} from '../../services/store/info/types.js'
import StoreCommand from '../../utilities/store-command.js'
import {storeFlags} from '../../flags.js'
import {globalFlags, jsonFlag} from '@shopify/cli-kit/node/cli'
Expand All @@ -26,6 +27,10 @@ Use \`--json\` for machine-readable output.`
store: storeFlags.store,
}

static get jsonOutputSchema() {
return storeInfoJsonOutputSchema
}

public async run(): Promise<void> {
const {flags} = await this.parse(StoreInfo)

Expand Down
19 changes: 12 additions & 7 deletions packages/store/src/cli/commands/store/list.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import StoreList from './list.js'
import {listStores} from '../../services/store/list.js'
import {writeStoreListResult} from '../../services/store/list/result.js'
import {presentStoreListResult} from '../../services/store/list/result.js'
import {storeListJsonOutputSchema} from '../../services/store/list/types.js'
import {describe, expect, test, vi} from 'vitest'

vi.mock('../../services/store/list.js')
Expand All @@ -9,37 +10,37 @@ vi.mock('../../services/store/attribution.js')

describe('store list command', () => {
test('runs the list service and writes text output by default', async () => {
vi.mocked(listStores).mockResolvedValue({stores: [], source: 'organization'})
vi.mocked(listStores).mockResolvedValue({stores: []})

await StoreList.run([])

expect(listStores).toHaveBeenCalledWith({organizationId: undefined, storeType: undefined})
expect(writeStoreListResult).toHaveBeenCalledWith({stores: [], source: 'organization'}, 'text')
expect(presentStoreListResult).toHaveBeenCalledWith({stores: []}, 'text')
})

test('passes the organization id through to the list service', async () => {
vi.mocked(listStores).mockResolvedValue({stores: [], source: 'organization'})
vi.mocked(listStores).mockResolvedValue({stores: []})

await StoreList.run(['--organization-id', '1234567'])

expect(listStores).toHaveBeenCalledWith({organizationId: 1234567, storeType: undefined})
})

test('passes the store type filter through to the list service', async () => {
vi.mocked(listStores).mockResolvedValue({stores: [], source: 'organization'})
vi.mocked(listStores).mockResolvedValue({stores: []})

await StoreList.run(['--type', 'client-transfer'])

expect(listStores).toHaveBeenCalledWith({organizationId: undefined, storeType: 'client-transfer'})
})

test('writes json output when requested', async () => {
vi.mocked(listStores).mockResolvedValue({stores: [], source: 'organization'})
vi.mocked(listStores).mockResolvedValue({stores: []})

await StoreList.run(['--json'])

expect(listStores).toHaveBeenCalledWith({organizationId: undefined, storeType: undefined})
expect(writeStoreListResult).toHaveBeenCalledWith({stores: [], source: 'organization'}, 'json')
expect(presentStoreListResult).toHaveBeenCalledWith({stores: []}, 'json')
})

test('defines the expected flags', () => {
Expand All @@ -48,4 +49,8 @@ describe('store list command', () => {
expect(StoreList.flags.type?.options).toEqual(['dev', 'production', 'client-transfer', 'collaborator'])
expect(StoreList.flags).not.toHaveProperty('from')
})

test('exposes the JSON output schema', () => {
expect(StoreList.jsonOutputSchema).toBe(storeListJsonOutputSchema)
})
})
9 changes: 7 additions & 2 deletions packages/store/src/cli/commands/store/list.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {listStores} from '../../services/store/list.js'
import {writeStoreListResult} from '../../services/store/list/result.js'
import {presentStoreListResult} from '../../services/store/list/result.js'
import {storeListJsonOutputSchema} from '../../services/store/list/types.js'
import {storeTypeFilters, type StoreTypeFilter} from '../../services/store/store-type.js'
import {storeFlags} from '../../flags.js'
import StoreCommand from '../../utilities/store-command.js'
Expand Down Expand Up @@ -38,6 +39,10 @@ Run \`<%= config.bin %> organization list\` to find organization IDs.`
}),
}

static get jsonOutputSchema() {
return storeListJsonOutputSchema
}

public async run(): Promise<void> {
const {flags} = await this.parse(StoreList)
const result = await listStores({
Expand All @@ -46,6 +51,6 @@ Run \`<%= config.bin %> organization list\` to find organization IDs.`
storeType: flags.type as StoreTypeFilter | undefined,
})

writeStoreListResult(result, flags.json ? 'json' : 'text')
presentStoreListResult(result, flags.json ? 'json' : 'text')
}
}
2 changes: 2 additions & 0 deletions packages/store/src/cli/services/store/info/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {getStoreInfo} from './index.js'
import {storeInfoJsonOutputSchema} from './types.js'
import {StoreLookupStoreNotFoundError, fetchDestinationsContext} from '../../../utilities/store-lookup/destinations.js'
import {fetchOrganizationShop} from '../../../utilities/store-lookup/organization-shop.js'
import {STORE_AUTH_APP_CLIENT_ID} from '../auth/config.js'
Expand Down Expand Up @@ -149,6 +150,7 @@ describe('getStoreInfo', () => {
featurePreview: 'extended_variants',
adminUrl: 'https://admin.shopify.com/store/shop',
})
expect(storeInfoJsonOutputSchema.validate(result)).toEqual(result)
})

test('returns fresh access and save URLs for locally stored preview stores', async () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/store/src/cli/services/store/info/result.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {storeInfoJsonOutputSchema, type StoreInfoResult, type StoreInfoStoreOwner} from './types.js'
import {outputResult} from '@shopify/cli-kit/node/output'
import {renderInfo, type InlineToken, type LinkToken} from '@shopify/cli-kit/node/ui'
import {capitalizeWords} from '@shopify/cli-kit/common/string'
import type {StoreInfoResult, StoreInfoStoreOwner} from './types.js'

type StoreInfoOutputFormat = 'text' | 'json'

export function renderStoreInfoResult(result: StoreInfoResult, format: StoreInfoOutputFormat): void {
if (format === 'json') {
outputResult(JSON.stringify(result, null, 2))
outputResult(storeInfoJsonOutputSchema.encode(result))
return
}
const actions = storeActions(result)
Expand Down
Loading
Loading