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
4 changes: 2 additions & 2 deletions packages/cli/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@
"lint": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm eslint \"src/**/*.ts\" 'bin/*.js' ",
"command": "pnpm eslint src bin",
"cwd": "packages/cli"
}
},
"lint:fix": {
"executor": "nx:run-commands",
"options": {
"command": "pnpm eslint 'src/**/*.ts' 'bin/*.js' --fix",
"command": "pnpm eslint src bin --fix",
"cwd": "packages/cli"
}
},
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/cli/commands/docs/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export async function writeCommandDocumentation(
const previewDescription = command.summary ?? description ?? ''
const cleanPreview = previewDescription.replace(/`/g, '\\`').replace(/https:\/\/shopify\.dev/g, '')

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const category = hasTopic && !generalTopics.includes(topic!) ? topic : 'general commands'

const docString = `// This is an autogenerated file. Don't edit this file manually.
Expand Down Expand Up @@ -139,7 +138,7 @@ export async function writeCommandFlagInterface(
const flag = command.flags[flagName]
if (!flag) return
if (flag.hidden) return
const flagDescription = flag.description || ''
const flagDescription = flag.description ?? ''
const char = flag.char ? `-${flag.char}, ` : ''
const type = flag.type === 'option' ? 'string' : "''"
const value = flag.type === 'option' ? ' <value>' : ''
Expand Down
6 changes: 1 addition & 5 deletions packages/cli/src/cli/commands/store/auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import {describe, test, expect, vi, beforeEach} from 'vitest'
import StoreAuth from './auth.js'
import {authenticateStoreWithApp} from '../../services/store/auth.js'
import {describe, test, expect, vi} from 'vitest'

vi.mock('../../services/store/auth.js')

describe('store auth command', () => {
beforeEach(() => {
vi.clearAllMocks()
})

test('passes parsed flags through to the auth service', async () => {
await StoreAuth.run(['--store', 'shop.myshopify.com', '--scopes', 'read_products,write_products'])

Expand Down
6 changes: 4 additions & 2 deletions packages/cli/src/cli/commands/store/auth.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {authenticateStoreWithApp} from '../../services/store/auth.js'
import Command from '@shopify/cli-kit/node/base-command'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
import {Flags} from '@oclif/core'
import {authenticateStoreWithApp} from '../../services/store/auth.js'

export default class StoreAuth extends Command {
static summary = 'Authenticate an app against a store for store commands.'
Expand All @@ -13,7 +13,9 @@ Re-run this command if the stored token is missing, expires, or no longer has th

static description = this.descriptionWithoutMarkdown()

static examples = ['<%= config.bin %> <%= command.id %> --store shop.myshopify.com --scopes read_products,write_products']
static examples = [
'<%= config.bin %> <%= command.id %> --store shop.myshopify.com --scopes read_products,write_products',
]

static flags = {
...globalFlags,
Expand Down
6 changes: 1 addition & 5 deletions packages/cli/src/cli/commands/store/execute.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import {describe, test, expect, vi, beforeEach} from 'vitest'
import StoreExecute from './execute.js'
import {executeStoreOperation} from '../../services/store/execute.js'
import {describe, test, expect, vi} from 'vitest'

vi.mock('../../services/store/execute.js')

describe('store execute command', () => {
beforeEach(() => {
vi.clearAllMocks()
})

test('passes the inline query through to the service', async () => {
await StoreExecute.run(['--store', 'shop.myshopify.com', '--query', 'query { shop { name } }'])

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/cli/commands/store/execute.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {executeStoreOperation} from '../../services/store/execute.js'
import Command from '@shopify/cli-kit/node/base-command'
import {globalFlags} from '@shopify/cli-kit/node/cli'
import {normalizeStoreFqdn} from '@shopify/cli-kit/node/context/fqdn'
import {resolvePath} from '@shopify/cli-kit/node/path'
import {Flags} from '@oclif/core'
import {executeStoreOperation} from '../../services/store/execute.js'

export default class StoreExecute extends Command {
static summary = 'Execute GraphQL queries and mutations on a store.'
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/cli/services/kitchen-sink/static.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function staticService() {
})

renderInfo({
headline: 'About your app',
headline: 'About your app.',
customSections: [
{
body: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
import {beforeEach, describe, expect, test, vi} from 'vitest'
import {fetchApiVersions} from '@shopify/cli-kit/node/api/admin'
import {AbortError} from '@shopify/cli-kit/node/error'
import {fetch} from '@shopify/cli-kit/node/http'
import {
clearStoredStoreAppSession,
getStoredStoreAppSession,
Expand All @@ -10,11 +6,17 @@ import {
} from './session.js'
import {STORE_AUTH_APP_CLIENT_ID} from './auth-config.js'
import {prepareAdminStoreGraphQLContext} from './admin-graphql-context.js'
import {beforeEach, describe, expect, test, vi} from 'vitest'
import {fetchApiVersions} from '@shopify/cli-kit/node/api/admin'
import {AbortError} from '@shopify/cli-kit/node/error'
import {fetch} from '@shopify/cli-kit/node/http'

vi.mock('./session.js')
vi.mock('@shopify/cli-kit/node/http')
vi.mock('@shopify/cli-kit/node/api/admin', async () => {
const actual = await vi.importActual<typeof import('@shopify/cli-kit/node/api/admin')>('@shopify/cli-kit/node/api/admin')
const actual = await vi.importActual<typeof import('@shopify/cli-kit/node/api/admin')>(
'@shopify/cli-kit/node/api/admin',
)
return {
...actual,
fetchApiVersions: vi.fn(),
Expand All @@ -35,7 +37,6 @@ describe('prepareAdminStoreGraphQLContext', () => {
}

beforeEach(() => {
vi.clearAllMocks()
vi.mocked(getStoredStoreAppSession).mockReturnValue(storedSession)
vi.mocked(isSessionExpired).mockReturnValue(false)
vi.mocked(fetchApiVersions).mockResolvedValue([
Expand Down
10 changes: 5 additions & 5 deletions packages/cli/src/cli/services/store/admin-graphql-context.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import {fetchApiVersions} from '@shopify/cli-kit/node/api/admin'
import {AbortError} from '@shopify/cli-kit/node/error'
import {fetch} from '@shopify/cli-kit/node/http'
import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/output'
import {AdminSession} from '@shopify/cli-kit/node/session'
import {maskToken, STORE_AUTH_APP_CLIENT_ID} from './auth-config.js'
import {createStoredStoreAuthError, reauthenticateStoreAuthError} from './auth-recovery.js'
import {
Expand All @@ -12,6 +7,11 @@ import {
setStoredStoreAppSession,
StoredStoreAppSession,
} from './session.js'
import {fetchApiVersions} from '@shopify/cli-kit/node/api/admin'
import {AbortError} from '@shopify/cli-kit/node/error'
import {fetch} from '@shopify/cli-kit/node/http'
import {outputContent, outputDebug, outputToken} from '@shopify/cli-kit/node/output'
import {AdminSession} from '@shopify/cli-kit/node/session'

export interface AdminStoreGraphQLContext {
adminSession: AdminSession
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import {clearStoredStoreAppSession} from './session.js'
import {prepareStoreExecuteRequest} from './execute-request.js'
import {runAdminStoreGraphQLOperation} from './admin-graphql-transport.js'
import {beforeEach, describe, expect, test, vi} from 'vitest'
import {adminUrl} from '@shopify/cli-kit/node/api/admin'
import {graphqlRequest} from '@shopify/cli-kit/node/api/graphql'
import {renderSingleTask} from '@shopify/cli-kit/node/ui'
import {clearStoredStoreAppSession} from './session.js'
import {prepareStoreExecuteRequest} from './execute-request.js'
import {runAdminStoreGraphQLOperation} from './admin-graphql-transport.js'

vi.mock('./session.js')
vi.mock('@shopify/cli-kit/node/api/graphql')
vi.mock('@shopify/cli-kit/node/ui')
vi.mock('@shopify/cli-kit/node/api/admin', async () => {
const actual = await vi.importActual<typeof import('@shopify/cli-kit/node/api/admin')>('@shopify/cli-kit/node/api/admin')
const actual = await vi.importActual<typeof import('@shopify/cli-kit/node/api/admin')>(
'@shopify/cli-kit/node/api/admin',
)
return {
...actual,
adminUrl: vi.fn(),
Expand All @@ -22,7 +24,6 @@ describe('runAdminStoreGraphQLOperation', () => {
const adminSession = {token: 'token', storeFqdn: store}

beforeEach(() => {
vi.clearAllMocks()
vi.mocked(adminUrl).mockReturnValue('https://shop.myshopify.com/admin/api/2025-10/graphql.json')
vi.mocked(renderSingleTask).mockImplementation(async ({task}) => task(() => {}))
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {clearStoredStoreAppSession} from './session.js'
import {PreparedStoreExecuteRequest} from './execute-request.js'
import {reauthenticateStoreAuthError} from './auth-recovery.js'
import {adminUrl} from '@shopify/cli-kit/node/api/admin'
import {graphqlRequest} from '@shopify/cli-kit/node/api/graphql'
import {AbortError} from '@shopify/cli-kit/node/error'
import {outputContent} from '@shopify/cli-kit/node/output'
import {AdminSession} from '@shopify/cli-kit/node/session'
import {renderSingleTask} from '@shopify/cli-kit/node/ui'
import {reauthenticateStoreAuthError} from './auth-recovery.js'
import {PreparedStoreExecuteRequest} from './execute-request.js'
import {clearStoredStoreAppSession} from './session.js'

function isGraphQLClientError(error: unknown): error is {response: {errors?: unknown; status?: number}} {
if (!error || typeof error !== 'object' || !('response' in error)) return false
const response = (error as {response?: unknown}).response
return !!response && typeof response === 'object'
return Boolean(response) && typeof response === 'object'
}

export async function runAdminStoreGraphQLOperation(input: {
Expand Down
27 changes: 7 additions & 20 deletions packages/cli/src/cli/services/store/auth.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {createServer} from 'http'
import {describe, test, expect, vi, beforeEach, afterEach} from 'vitest'
import {
authenticateStoreWithApp,
buildStoreAuthUrl,
Expand All @@ -11,15 +9,17 @@ import {
} from './auth.js'
import {setStoredStoreAppSession} from './session.js'
import {STORE_AUTH_APP_CLIENT_ID} from './auth-config.js'
import {describe, test, expect, vi} from 'vitest'
import {fetch} from '@shopify/cli-kit/node/http'
import {createServer} from 'http'

vi.mock('./session.js')
vi.mock('@shopify/cli-kit/node/http')
vi.mock('@shopify/cli-kit/node/system', () => ({openURL: vi.fn().mockResolvedValue(true)}))
vi.mock('@shopify/cli-kit/node/crypto', () => ({randomUUID: vi.fn().mockReturnValue('state-123')}))

async function getAvailablePort(): Promise<number> {
return await new Promise<number>((resolve, reject) => {
return new Promise<number>((resolve, reject) => {
const server = createServer()

server.on('error', reject)
Expand All @@ -42,12 +42,7 @@ async function getAvailablePort(): Promise<number> {
})
}

function callbackParams(options?: {
code?: string
shop?: string
state?: string
error?: string
}): URLSearchParams {
function callbackParams(options?: {code?: string; shop?: string; state?: string; error?: string}): URLSearchParams {
const params = new URLSearchParams()
params.set('shop', options?.shop ?? 'shop.myshopify.com')
params.set('state', options?.state ?? 'state-123')
Expand All @@ -60,23 +55,15 @@ function callbackParams(options?: {
}

describe('store auth service', () => {
beforeEach(() => {
vi.clearAllMocks()
})

afterEach(() => {
vi.restoreAllMocks()
})

test('generateCodeVerifier produces a base64url string of 43 chars', () => {
const verifier = generateCodeVerifier()
expect(verifier).toMatch(/^[A-Za-z0-9_-]{43}$/)
})

test('generateCodeVerifier produces unique values', () => {
const a = generateCodeVerifier()
const b = generateCodeVerifier()
expect(a).not.toBe(b)
const firstVerifier = generateCodeVerifier()
const secondVerifier = generateCodeVerifier()
expect(firstVerifier).not.toBe(secondVerifier)
})

test('computeCodeChallenge produces a deterministic S256 hash', () => {
Expand Down
Loading
Loading