Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
shopify store create [flags]
44 changes: 44 additions & 0 deletions docs-shopify.dev/commands/interfaces/store-create.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This is an autogenerated file. Don't edit this file manually.
export interface storecreate {
/**
* The country code for the store (e.g., US, CA, GB).
* @environment SHOPIFY_FLAG_STORE_COUNTRY
*/
'-c, --country <value>'?: string

/**
* Create a development store instead of a trial store.
* @environment SHOPIFY_FLAG_STORE_DEV
*/
'--dev'?: ''

/**
* Output the result as JSON. Automatically disables color output.
* @environment SHOPIFY_FLAG_JSON
*/
'-j, --json'?: ''

/**
* The name of the store.
* @environment SHOPIFY_FLAG_STORE_NAME
*/
'-n, --name <value>'?: string

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

/**
* The custom myshopify.com subdomain for the store.
* @environment SHOPIFY_FLAG_STORE_SUBDOMAIN
*/
'--subdomain <value>'?: string

/**
* Increase the verbosity of the output.
* @environment SHOPIFY_FLAG_VERBOSE
*/
'--verbose'?: ''
}
36 changes: 36 additions & 0 deletions docs-shopify.dev/commands/store-create.doc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// This is an autogenerated file. Don't edit this file manually.
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'

const data: ReferenceEntityTemplateSchema = {
name: 'store create',
description: `Creates a new Shopify store associated with your account.

By default, creates a trial store. Use \`--dev\` to create a development store instead.`,
overviewPreviewDescription: `Create a new Shopify store.`,
type: 'command',
isVisualComponent: false,
defaultExample: {
codeblock: {
tabs: [
{
title: 'store create',
code: './examples/store-create.example.sh',
language: 'bash',
},
],
title: 'store create',
},
},
definitions: [
{
title: 'Flags',
description: 'The following flags are available for the `store create` command:',
type: 'storecreate',
},
],
category: 'store',
related: [
],
}

export default data
101 changes: 101 additions & 0 deletions docs-shopify.dev/generated/generated_docs_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -5855,6 +5855,107 @@
"category": "store",
"related": []
},
{
"name": "store create",
"description": "Creates a new Shopify store associated with your account.\n\nBy default, creates a trial store. Use `--dev` to create a development store instead.",
"overviewPreviewDescription": "Create a new Shopify store.",
"type": "command",
"isVisualComponent": false,
"defaultExample": {
"codeblock": {
"tabs": [
{
"title": "store create",
"code": "shopify store create [flags]",
"language": "bash"
}
],
"title": "store create"
}
},
"definitions": [
{
"title": "Flags",
"description": "The following flags are available for the `store create` command:",
"type": "storecreate",
"typeDefinitions": {
"storecreate": {
"filePath": "docs-shopify.dev/commands/interfaces/store-create.interface.ts",
"name": "storecreate",
"description": "",
"members": [
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--dev",
"value": "\"\"",
"description": "Create a development store instead of a trial store.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_STORE_DEV"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create.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-create.interface.ts",
"syntaxKind": "PropertySignature",
"name": "--subdomain <value>",
"value": "string",
"description": "The custom myshopify.com subdomain for the store.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_STORE_SUBDOMAIN"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create.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-create.interface.ts",
"syntaxKind": "PropertySignature",
"name": "-c, --country <value>",
"value": "string",
"description": "The country code for the store (e.g., US, CA, GB).",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_STORE_COUNTRY"
},
{
"filePath": "docs-shopify.dev/commands/interfaces/store-create.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-create.interface.ts",
"syntaxKind": "PropertySignature",
"name": "-n, --name <value>",
"value": "string",
"description": "The name of the store.",
"isOptional": true,
"environmentValue": "SHOPIFY_FLAG_STORE_NAME"
}
],
"value": "export interface storecreate {\n /**\n * The country code for the store (e.g., US, CA, GB).\n * @environment SHOPIFY_FLAG_STORE_COUNTRY\n */\n '-c, --country <value>'?: string\n\n /**\n * Create a development store instead of a trial store.\n * @environment SHOPIFY_FLAG_STORE_DEV\n */\n '--dev'?: ''\n\n /**\n * Output the result as JSON. Automatically disables color output.\n * @environment SHOPIFY_FLAG_JSON\n */\n '-j, --json'?: ''\n\n /**\n * The name of the store.\n * @environment SHOPIFY_FLAG_STORE_NAME\n */\n '-n, --name <value>'?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * The custom myshopify.com subdomain for the store.\n * @environment SHOPIFY_FLAG_STORE_SUBDOMAIN\n */\n '--subdomain <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
}
}
}
],
"category": "store",
"related": []
},
{
"name": "store execute",
"description": "Executes an Admin API GraphQL query or mutation on the specified store using previously stored app authentication.\n\nRun `shopify store auth` first to create stored auth for the store.\n\nMutations are disabled by default. Re-run with `--allow-mutations` if you intend to modify store data.",
Expand Down
22 changes: 21 additions & 1 deletion packages/cli-kit/src/private/node/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ interface BusinessPlatformAPIOAuthOptions {
scopes: BusinessPlatformScope[]
}

/**
* A scope supported by the Signups API.
* The Signups API uses the Identity bearer token directly (no application token exchange).
*/
export type SignupsScope = 'shop-create'
interface SignupsAPIOAuthOptions {
/** List of scopes to request permissions for. */
scopes: SignupsScope[]
}

/**
* It represents the authentication requirements and
* is the input necessary to trigger the authentication
Expand All @@ -103,6 +113,7 @@ export interface OAuthApplications {
partnersApi?: PartnersAPIOAuthOptions
businessPlatformApi?: BusinessPlatformAPIOAuthOptions
appManagementApi?: AppManagementAPIOauthOptions
signupsApi?: SignupsAPIOAuthOptions
}

export interface OAuthSession {
Expand All @@ -111,6 +122,7 @@ export interface OAuthSession {
storefront?: string
businessPlatform?: string
appManagement?: string
identity?: string
userId: string
}

Expand Down Expand Up @@ -397,6 +409,10 @@ async function tokensFor(applications: OAuthApplications, session: Session): Pro
tokens.appManagement = session.applications[appId]?.accessToken
}

if (applications.signupsApi) {
tokens.identity = session.identity.accessToken
}

return tokens
}

Expand All @@ -413,7 +429,8 @@ function getFlattenScopes(apps: OAuthApplications): string[] {
const storefront = apps.storefrontRendererApi?.scopes ?? []
const businessPlatform = apps.businessPlatformApi?.scopes ?? []
const appManagement = apps.appManagementApi?.scopes ?? []
const requestedScopes = [...admin, ...partner, ...storefront, ...businessPlatform, ...appManagement]
const signups = apps.signupsApi?.scopes ?? []
const requestedScopes = [...admin, ...partner, ...storefront, ...businessPlatform, ...appManagement, ...signups]
return allDefaultScopes(requestedScopes)
}

Expand All @@ -424,6 +441,9 @@ function getFlattenScopes(apps: OAuthApplications): string[] {
* @returns An object containing the scopes for each application.
*/
function getExchangeScopes(apps: OAuthApplications): ExchangeScopes {
// Note: signupsApi is intentionally excluded here. The Signups API uses the Identity bearer
// token directly rather than an exchanged application token. Its scopes are included in
// getFlattenScopes so they appear on the Identity token, but no exchange is needed.
const adminScope = apps.adminApi?.scopes ?? []
const partnerScope = apps.partnersApi?.scopes ?? []
const storefrontScopes = apps.storefrontRendererApi?.scopes ?? []
Expand Down
6 changes: 6 additions & 0 deletions packages/cli-kit/src/private/node/session/scopes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ describe('allDefaultScopes', () => {
])
})

test('transforms shop-create scope to full URI', async () => {
const got = allDefaultScopes(['shop-create'])

expect(got).toContain('https://api.shopify.com/auth/shop.create')
})

test('includes App Management and Store Management', async () => {
// When
const got = allDefaultScopes([])
Expand Down
2 changes: 2 additions & 0 deletions packages/cli-kit/src/private/node/session/scopes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ function scopeTransform(scope: string): string {
return 'https://api.shopify.com/auth/organization.on-demand-user-access'
case 'app-management':
return 'https://api.shopify.com/auth/organization.apps.manage'
case 'shop-create':
return 'https://api.shopify.com/auth/shop.create'
default:
return scope
}
Expand Down
60 changes: 60 additions & 0 deletions packages/cli-kit/src/public/node/api/signups.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {signupsRequest} from './signups.js'
import {graphqlRequest} from './graphql.js'
import {handleDeprecations} from './partners.js'
import {signupsFqdn} from '../context/fqdn.js'
import {beforeEach, describe, expect, test, vi} from 'vitest'

vi.mock('./graphql.js')
vi.mock('../context/fqdn.js')

const signupsFqdnValue = 'shopify.com'
const url = `https://${signupsFqdnValue}/services/signups/graphql`
const mockedToken = 'identity-token'

beforeEach(() => {
vi.mocked(signupsFqdn).mockResolvedValue(signupsFqdnValue)
})

describe('signupsRequest', () => {
test('calls graphqlRequest with correct parameters', async () => {
vi.mocked(graphqlRequest).mockResolvedValue({storeCreate: {shopPermanentDomain: 'test.myshopify.com'}})
const query = 'mutation StoreCreate($signup: ShopInput!) { storeCreate(signup: $signup) { shopPermanentDomain } }'
const variables = {signup: {country: 'US'}}

await signupsRequest(query, mockedToken, variables)

expect(graphqlRequest).toHaveBeenCalledWith({
query,
api: 'Signups',
url,
token: mockedToken,
variables,
responseOptions: {onResponse: handleDeprecations},
})
})

test('calls graphqlRequest without variables when not provided', async () => {
vi.mocked(graphqlRequest).mockResolvedValue({})
const query = 'query { __schema { types { name } } }'

await signupsRequest(query, mockedToken)

expect(graphqlRequest).toHaveBeenCalledWith({
query,
api: 'Signups',
url,
token: mockedToken,
variables: undefined,
responseOptions: {onResponse: handleDeprecations},
})
})

test('returns the response from graphqlRequest', async () => {
const expectedResponse = {storeCreate: {shopPermanentDomain: 'new-store.myshopify.com', polling: false}}
vi.mocked(graphqlRequest).mockResolvedValue(expectedResponse)

const result = await signupsRequest('query', mockedToken)

expect(result).toEqual(expectedResponse)
})
})
32 changes: 32 additions & 0 deletions packages/cli-kit/src/public/node/api/signups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {GraphQLVariables, graphqlRequest} from './graphql.js'
import {handleDeprecations} from './partners.js'
import {signupsFqdn} from '../context/fqdn.js'

async function setupRequest(token: string) {
const api = 'Signups'
const fqdn = await signupsFqdn()
const url = `https://${fqdn}/services/signups/graphql`
return {
token,
api,
url,
responseOptions: {onResponse: handleDeprecations},
}
}

/**
* Executes a GraphQL query against the Signups API.
* Uses the Identity bearer token directly (no application token exchange).
*
* @param query - GraphQL query to execute.
* @param token - Identity access token.
* @param variables - GraphQL variables to pass to the query.
* @returns The response of the query of generic type <T>.
*/
export async function signupsRequest<T>(query: string, token: string, variables?: GraphQLVariables): Promise<T> {
return graphqlRequest<T>({
...(await setupRequest(token)),
query,
variables,
})
}
16 changes: 16 additions & 0 deletions packages/cli-kit/src/public/node/context/fqdn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,22 @@ export async function businessPlatformFqdn(): Promise<string> {
}
}

/**
* It returns the Signups API service we should interact with.
*
* @returns Fully-qualified domain of the Signups service we should interact with.
*/
export async function signupsFqdn(): Promise<string> {
const environment = serviceEnvironment()
const productionFqdn = 'shopify.com'
switch (environment) {
case 'local':
return new DevServerCore().host('shopify')
default:
return productionFqdn
}
}

/**
* It returns the Identity service we should interact with.
*
Expand Down
Loading
Loading