Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
11c195e
Add `shopify store info <store>` command
amcaplan May 28, 2026
62d6258
Use compact() to drop nullish optional fields in store info result
amcaplan May 28, 2026
43ef6e9
Convert store info to --store flag instead of positional arg
amcaplan May 28, 2026
c0d5668
Extract shared storeFlags.store across store: commands
amcaplan May 28, 2026
e93ae37
Fix store info shop matching against BP URL fields
amcaplan May 28, 2026
ef34443
Humanize labels and enum values in store info text output
amcaplan May 28, 2026
ce17561
Drop internal IDs and is_main_shop from store info output
amcaplan May 28, 2026
a1628ff
Render store info dates in UTC with explicit timezone label
amcaplan May 28, 2026
1c4bce7
Trim store info changeset
amcaplan May 28, 2026
20a39e6
Rewrite store info user-facing text in user-friendly terms
amcaplan May 28, 2026
ffc3326
Rename store info --verbose to --full
amcaplan May 28, 2026
ef049ef
Trim store info --full to high-signal fields only
amcaplan May 28, 2026
241a0d8
Drop --full flag from store info; include extras automatically when a…
amcaplan May 28, 2026
90ff077
Group store info text output by user-facing category
amcaplan May 28, 2026
a742088
Drop stale --verbose references from store info command test
amcaplan May 28, 2026
aefa7e9
Drop unused exports from store info internals
amcaplan May 28, 2026
1d09887
Share decodeOrganizationGid between organizations and store packages
amcaplan May 28, 2026
c1e7a69
Move GID codec to cli-kit and apply across the codebase
amcaplan May 31, 2026
32d49ea
Lift extractHost and extractMyshopifyHandle to cli-kit/common/url
amcaplan May 31, 2026
0a80c76
Move store info GraphQL queries into .graphql files with codegen
amcaplan May 31, 2026
cc62e1a
Gitignore store package schemas; fetch via bin/get-graphql-schemas
amcaplan May 31, 2026
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/store-info-command.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/cli': minor
---

Add `shopify store info --store <domain>` command. Supports `--json`. Additional fields are included automatically when you have run `store auth` for the store.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ packages/ui-extensions-dev-console/css-transform.js
packages/ui-extensions-dev-console/dist
packages/cli-kit/src/cli/api/graphql/*/*_schema.graphql
packages/organizations/src/cli/api/graphql/*/*_schema.graphql
packages/store/src/cli/api/graphql/*/*_schema.graphql

.claude/settings.local.json

Expand Down
7 changes: 6 additions & 1 deletion bin/get-graphql-schemas.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,17 @@ const schemas = [
localPaths: [
'./packages/app/src/cli/api/graphql/business-platform-destinations/destinations_schema.graphql',
'./packages/organizations/src/cli/api/graphql/business-platform-destinations/destinations_schema.graphql',
'./packages/store/src/cli/api/graphql/business-platform-destinations/destinations_schema.graphql',
],
},
{
owner: 'shop',
repo: 'world',
pathToFile: 'areas/platforms/organizations/db/graphql/organizations_schema.graphql',
localPaths: ['./packages/app/src/cli/api/graphql/business-platform-organizations/organizations_schema.graphql'],
localPaths: [
'./packages/app/src/cli/api/graphql/business-platform-organizations/organizations_schema.graphql',
'./packages/store/src/cli/api/graphql/business-platform-organizations/organizations_schema.graphql',
],
},
{
owner: 'shop',
Expand All @@ -79,6 +83,7 @@ const schemas = [
'./packages/cli-kit/src/cli/api/graphql/admin/admin_schema.graphql',
'./packages/app/src/cli/api/graphql/bulk-operations/admin_schema.graphql',
'./packages/app/src/cli/api/graphql/admin/admin_schema.graphql',
'./packages/store/src/cli/api/graphql/admin/admin_schema.graphql',
],
usesLfs: true,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface storeauth {
'--scopes <value>': string

/**
* The myshopify.com domain of the store to authenticate against.
* The myshopify.com domain of the store.
* @environment SHOPIFY_FLAG_STORE
*/
'-s, --store <value>': string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface storeexecute {
'--query-file <value>'?: string

/**
* The myshopify.com domain of the store to execute against.
* The myshopify.com domain of the store.
* @environment SHOPIFY_FLAG_STORE
*/
'-s, --store <value>': string
Expand Down
30 changes: 30 additions & 0 deletions docs-shopify.dev/commands/interfaces/store-info.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This is an autogenerated file. Don't edit this file manually.
/**
* The following flags are available for the `store info` command:
* @publicDocs
*/
export interface storeinfo {
/**
* Output the result as JSON. Automatically disables color output.
* @environment SHOPIFY_FLAG_JSON
*/
'-j, --json'?: ''

/**
* Disable color output.
* @environment SHOPIFY_FLAG_NO_COLOR
*/
'--no-color'?: ''

/**
* The myshopify.com domain of the store.
* @environment SHOPIFY_FLAG_STORE
*/
'-s, --store <value>': string

/**
* Increase the verbosity of the output.
* @environment SHOPIFY_FLAG_VERBOSE
*/
'--verbose'?: ''
}
54 changes: 50 additions & 4 deletions docs-shopify.dev/generated/generated_docs_data_v2.json
Original file line number Diff line number Diff line change
Expand Up @@ -4177,11 +4177,11 @@
"syntaxKind": "PropertySignature",
"name": "-s, --store <value>",
"value": "string",
"description": "The myshopify.com domain of the store to authenticate against.",
"description": "The myshopify.com domain of the store.",
"environmentValue": "SHOPIFY_FLAG_STORE"
}
],
"value": "export interface storeauth {\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Comma-separated Admin API scopes to request for the app.\n * @environment SHOPIFY_FLAG_SCOPES\n */\n '--scopes <value>': string\n\n /**\n * The myshopify.com domain of the store to authenticate against.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>': string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
"value": "export interface storeauth {\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Comma-separated Admin API scopes to request for the app.\n * @environment SHOPIFY_FLAG_SCOPES\n */\n '--scopes <value>': string\n\n /**\n * The myshopify.com domain of the store.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>': string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"storeexecute": {
Expand Down Expand Up @@ -4277,7 +4277,7 @@
"syntaxKind": "PropertySignature",
"name": "-s, --store <value>",
"value": "string",
"description": "The myshopify.com domain of the store to execute against.",
"description": "The myshopify.com domain of the store.",
"environmentValue": "SHOPIFY_FLAG_STORE"
},
{
Expand All @@ -4290,7 +4290,53 @@
"environmentValue": "SHOPIFY_FLAG_VARIABLES"
}
],
"value": "export interface storeexecute {\n /**\n * Allow GraphQL mutations to run against the target store.\n * @environment SHOPIFY_FLAG_ALLOW_MUTATIONS\n */\n '--allow-mutations'?: ''\n\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * The file name where results should be written, instead of STDOUT.\n * @environment SHOPIFY_FLAG_OUTPUT_FILE\n */\n '--output-file <value>'?: string\n\n /**\n * The GraphQL query or mutation, as a string.\n * @environment SHOPIFY_FLAG_QUERY\n */\n '-q, --query <value>'?: string\n\n /**\n * Path to a file containing the GraphQL query or mutation. Can't be used with --query.\n * @environment SHOPIFY_FLAG_QUERY_FILE\n */\n '--query-file <value>'?: string\n\n /**\n * The myshopify.com domain of the store to execute against.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>': string\n\n /**\n * Path to a file containing GraphQL variables in JSON format. Can't be used with --variables.\n * @environment SHOPIFY_FLAG_VARIABLE_FILE\n */\n '--variable-file <value>'?: string\n\n /**\n * The values for any GraphQL variables in your query or mutation, in JSON format.\n * @environment SHOPIFY_FLAG_VARIABLES\n */\n '-v, --variables <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n\n /**\n * The API version to use for the query or mutation. Defaults to the latest stable version.\n * @environment SHOPIFY_FLAG_VERSION\n */\n '--version <value>'?: string\n}"
"value": "export interface storeexecute {\n /**\n * Allow GraphQL mutations to run against the target store.\n * @environment SHOPIFY_FLAG_ALLOW_MUTATIONS\n */\n '--allow-mutations'?: ''\n\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * The file name where results should be written, instead of STDOUT.\n * @environment SHOPIFY_FLAG_OUTPUT_FILE\n */\n '--output-file <value>'?: string\n\n /**\n * The GraphQL query or mutation, as a string.\n * @environment SHOPIFY_FLAG_QUERY\n */\n '-q, --query <value>'?: string\n\n /**\n * Path to a file containing the GraphQL query or mutation. Can't be used with --query.\n * @environment SHOPIFY_FLAG_QUERY_FILE\n */\n '--query-file <value>'?: string\n\n /**\n * The myshopify.com domain of the store.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>': string\n\n /**\n * Path to a file containing GraphQL variables in JSON format. Can't be used with --variables.\n * @environment SHOPIFY_FLAG_VARIABLE_FILE\n */\n '--variable-file <value>'?: string\n\n /**\n * The values for any GraphQL variables in your query or mutation, in JSON format.\n * @environment SHOPIFY_FLAG_VARIABLES\n */\n '-v, --variables <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n\n /**\n * The API version to use for the query or mutation. Defaults to the latest stable version.\n * @environment SHOPIFY_FLAG_VERSION\n */\n '--version <value>'?: string\n}"
}
},
"storeinfo": {
"docs-shopify.dev/commands/interfaces/store-info.interface.ts": {
"filePath": "docs-shopify.dev/commands/interfaces/store-info.interface.ts",
"name": "storeinfo",
"description": "The following flags are available for the `store info` command:",
"isPublicDocs": true,
"members": [
{
"filePath": "docs-shopify.dev/commands/interfaces/store-info.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/store-info.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--verbose",
"value": "''",
"description": "Increase the verbosity of the output.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_VERBOSE"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-info.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/store-info.interface.ts",
"syntaxKind": "PropertySignature",
"name": "-s, --store <value>",
"value": "string",
"description": "The myshopify.com domain of the store.",
"environmentValue": "SHOPIFY_FLAG_STORE"
}
],
"value": "export interface storeinfo {\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * The myshopify.com domain of the store.\n * @environment SHOPIFY_FLAG_STORE\n */\n '-s, --store <value>': string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
},
"themecheck": {
Expand Down
3 changes: 3 additions & 0 deletions graphql.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,8 @@ export default {
functions: projectFactory('functions', 'functions_cli_schema.graphql', 'app'),
adminAsApp: projectFactory('admin', 'admin_schema.graphql'),
organizationsDestinations: projectFactory('business-platform-destinations', 'destinations_schema.graphql', 'organizations'),
storeBusinessPlatformDestinations: projectFactory('business-platform-destinations', 'destinations_schema.graphql', 'store'),
storeBusinessPlatformOrganizations: projectFactory('business-platform-organizations', 'organizations_schema.graphql', 'store'),
storeAdmin: projectFactory('admin', 'admin_schema.graphql', 'store'),
},
}
18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,24 @@
]
}
},
"packages/store": {
"entry": [
"**/{commands,hooks}/**/*.ts!",
"**/index.ts!"
],
"project": "**/*.ts!",
"ignore": [
"**/graphql/**/generated/*.ts"
],
"ignoreDependencies": [
"@graphql-typed-document-node/core"
],
"vite": {
"config": [
"vite.config.ts"
]
}
},
"packages/cli": {
"entry": [
"**/{commands,hooks}/**/*.ts!",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ import {
BusinessPlatformRequestOptions,
} from '@shopify/cli-kit/node/api/business-platform'
import {CLI_KIT_VERSION} from '@shopify/cli-kit/common/version'
import {encodeGid, numericIdFromEncodedGid, numericIdFromGid} from '@shopify/cli-kit/common/gid'
import {versionSatisfies} from '@shopify/cli-kit/node/node-package-manager'
import {outputDebug} from '@shopify/cli-kit/node/output'
import {developerDashboardFqdn, normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
Expand Down Expand Up @@ -1266,7 +1267,7 @@ export function organizationGidForBP(id: string): string {

// 1234 => gid://organization/Organization/1234 => base64
export function encodedGidFromOrganizationIdForBP(id: string): string {
return Buffer.from(organizationGidForBP(id)).toString('base64')
return encodeGid(organizationGidForBP(id))
}

// App Managament uses a different GID format than Business Platform for organizationId.
Expand All @@ -1277,20 +1278,26 @@ function gidFromOrganizationIdForShopify(id: string): string {

// 1234 => gid://organization/ShopifyShop/1234 => base64
export function encodedGidFromShopId(id: string): string {
const gid = `gid://organization/ShopifyShop/${id}`
return Buffer.from(gid).toString('base64')
return encodeGid(`gid://organization/ShopifyShop/${id}`)
}

// base64 => gid://organization/Organization/1234 => 1234
function idFromEncodedGid(gid: string): string {
const decodedGid = Buffer.from(gid, 'base64').toString('ascii')
return numberFromGid(decodedGid).toString()
const numeric = numericIdFromEncodedGid(gid)
if (numeric === undefined) {
throw new Error(`Invalid encoded GID: ${gid}`)
}
return numeric
}

// gid://organization/Organization/1234 => 1234
function numberFromGid(gid: string): number {
if (gid.startsWith('gid://')) {
return Number(gid.match(/^gid.*\/(\d+)$/)![1])
const numeric = numericIdFromGid(gid)
if (numeric === undefined) {
throw new Error(`Invalid GID: ${gid}`)
}
return Number(numeric)
}
return Number(gid)
}
Expand Down
39 changes: 39 additions & 0 deletions packages/cli-kit/src/public/common/gid.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {encodeGid, numericIdFromEncodedGid, numericIdFromGid} from './gid.js'
import {describe, expect, test} from 'vitest'

describe('numericIdFromGid', () => {
test('extracts the trailing numeric id from a plain gid', () => {
expect(numericIdFromGid('gid://shopify/Product/1234')).toBe('1234')
expect(numericIdFromGid('gid://organization/Organization/9876')).toBe('9876')
})

test('returns undefined when there is no trailing /<digits>', () => {
expect(numericIdFromGid('gid://shopify/Product/ABC')).toBeUndefined()
expect(numericIdFromGid('not-a-gid')).toBeUndefined()
expect(numericIdFromGid('1234')).toBeUndefined()
})
})

describe('numericIdFromEncodedGid', () => {
test('extracts the trailing numeric id from a base64-encoded gid', () => {
const gid = Buffer.from('gid://organization/Organization/1234').toString('base64')
expect(numericIdFromEncodedGid(gid)).toBe('1234')
})

test('returns undefined when the decoded string does not end with /<digits>', () => {
expect(numericIdFromEncodedGid(Buffer.from('not-a-gid').toString('base64'))).toBeUndefined()
expect(numericIdFromEncodedGid('!!!')).toBeUndefined()
})
})

describe('encodeGid', () => {
test('base64-encodes a plain gid', () => {
expect(encodeGid('gid://organization/Organization/1234')).toBe(
Buffer.from('gid://organization/Organization/1234').toString('base64'),
)
})

test('round-trips with numericIdFromEncodedGid', () => {
expect(numericIdFromEncodedGid(encodeGid('gid://shopify/Product/42'))).toBe('42')
})
})
33 changes: 33 additions & 0 deletions packages/cli-kit/src/public/common/gid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Extracts the trailing numeric id from a plain GraphQL global id like
* `gid://shopify/Product/123`.
*
* @param gid - A plain GraphQL global id string.
* @returns The trailing numeric id, or undefined when the string does not end with `/<digits>`.
*/
export function numericIdFromGid(gid: string): string | undefined {
const match = gid.match(/\/(\d+)$/)
return match ? match[1] : undefined
}

/**
* Decodes a base64-encoded GraphQL global id (for example, the form
* Business Platform APIs return) and returns the trailing numeric id.
*
* @param gid - A base64-encoded GraphQL global id.
* @returns The trailing numeric id, or undefined when the decoded string does not end with `/<digits>`.
*/
export function numericIdFromEncodedGid(gid: string): string | undefined {
return numericIdFromGid(Buffer.from(gid, 'base64').toString('ascii'))
}

/**
* Encodes a plain GraphQL global id (`gid://...`) as base64, which is the
* form some Business Platform endpoints require.
*
* @param gid - A plain GraphQL global id string to encode.
* @returns The base64-encoded gid.
*/
export function encodeGid(gid: string): string {
return Buffer.from(gid).toString('base64')
}
33 changes: 32 additions & 1 deletion packages/cli-kit/src/public/common/url.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {isValidURL, safeParseURL} from './url.js'
import {extractHost, extractMyshopifyHandle, isValidURL, safeParseURL} from './url.js'
import {describe, expect, test} from 'vitest'

describe('isValidURL', () => {
Expand Down Expand Up @@ -57,3 +57,34 @@ describe('safeParseURL', () => {
expect(result).toBeUndefined()
})
})

describe('extractHost', () => {
test('returns the hostname for a full URL', () => {
expect(extractHost('https://Shop.MyShopify.com/admin')).toBe('shop.myshopify.com')
})

test('strips the scheme and path for a bare host string', () => {
expect(extractHost('shop.myshopify.com/admin')).toBe('shop.myshopify.com')
})

test('returns undefined for null/undefined/empty input', () => {
expect(extractHost(null)).toBeUndefined()
expect(extractHost(undefined)).toBeUndefined()
expect(extractHost('')).toBeUndefined()
})
})

describe('extractMyshopifyHandle', () => {
test('extracts the subdomain from a myshopify.com URL', () => {
expect(extractMyshopifyHandle('https://my-shop.myshopify.com')).toBe('my-shop')
})

test('returns undefined when the host is not a myshopify.com domain', () => {
expect(extractMyshopifyHandle('https://example.com')).toBeUndefined()
})

test('returns undefined for null/undefined input', () => {
expect(extractMyshopifyHandle(null)).toBeUndefined()
expect(extractMyshopifyHandle(undefined)).toBeUndefined()
})
})
29 changes: 29 additions & 0 deletions packages/cli-kit/src/public/common/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,32 @@ export function safeParseURL(url: string): URL | undefined {
return undefined
}
}

/**
* Extracts the lowercased hostname from a URL-shaped string. Tolerates
* bare hosts (without a scheme) and inputs that come back from APIs as
* either `https://shop.myshopify.com` or `shop.myshopify.com`.
*
* @param value - A URL or bare host string, possibly null/undefined.
* @returns The lowercased hostname, or undefined when the input is empty.
*/
export function extractHost(value: string | null | undefined): string | undefined {
if (!value) return undefined
const lowered = value.toLowerCase()
const parsed = safeParseURL(lowered)
if (parsed) return parsed.hostname
return lowered.replace(/^https?:\/\//, '').split('/')[0]
}

/**
* Extracts the subdomain handle from a `*.myshopify.com` URL or host.
*
* @param value - A URL or host string, possibly null/undefined.
* @returns The myshopify subdomain handle, or undefined when the input isn't a `*.myshopify.com` URL.
*/
export function extractMyshopifyHandle(value: string | null | undefined): string | undefined {
const host = extractHost(value)
if (!host) return undefined
const match = host.match(/^([^.]+)\.myshopify\.com$/)
return match ? match[1] : undefined
}
Loading
Loading