Skip to content

Commit 42d0650

Browse files
Merge pull request #5914 from Shopify/multi-session
Multi-session support
2 parents c5dc5f8 + c8b8ad8 commit 42d0650

File tree

31 files changed

+1334
-267
lines changed

31 files changed

+1334
-267
lines changed

.changeset/thirty-impalas-marry.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@shopify/cli-kit': minor
3+
'@shopify/app': minor
4+
'@shopify/cli': minor
5+
---
6+
7+
Add `auth login` command with multi-session support
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// This is an autogenerated file. Don't edit this file manually.
2+
import {ReferenceEntityTemplateSchema} from '@shopify/generate-docs'
3+
4+
const data: ReferenceEntityTemplateSchema = {
5+
name: 'auth login',
6+
description: `Logs you in to your Shopify account.`,
7+
overviewPreviewDescription: `Logs you in to your Shopify account.`,
8+
type: 'command',
9+
isVisualComponent: false,
10+
defaultExample: {
11+
codeblock: {
12+
tabs: [
13+
{
14+
title: 'auth login',
15+
code: './examples/auth-login.example.sh',
16+
language: 'bash',
17+
},
18+
],
19+
title: 'auth login',
20+
},
21+
},
22+
definitions: [
23+
{
24+
title: 'Flags',
25+
description: 'The following flags are available for the `auth login` command:',
26+
type: 'authlogin',
27+
},
28+
],
29+
category: 'general commands',
30+
related: [
31+
],
32+
}
33+
34+
export default data
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
shopify auth login [flags]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// This is an autogenerated file. Don't edit this file manually.
2+
export interface authlogin {
3+
/**
4+
* Alias of the session you want to login to.
5+
* @environment SHOPIFY_FLAG_AUTH_ALIAS
6+
*/
7+
'--alias <value>'?: string
8+
}

docs-shopify.dev/generated/generated_docs_data.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2436,6 +2436,53 @@
24362436
"category": "app",
24372437
"related": []
24382438
},
2439+
{
2440+
"name": "auth login",
2441+
"description": "Logs you in to your Shopify account.",
2442+
"overviewPreviewDescription": "Logs you in to your Shopify account.",
2443+
"type": "command",
2444+
"isVisualComponent": false,
2445+
"defaultExample": {
2446+
"codeblock": {
2447+
"tabs": [
2448+
{
2449+
"title": "auth login",
2450+
"code": "shopify auth login [flags]",
2451+
"language": "bash"
2452+
}
2453+
],
2454+
"title": "auth login"
2455+
}
2456+
},
2457+
"definitions": [
2458+
{
2459+
"title": "Flags",
2460+
"description": "The following flags are available for the `auth login` command:",
2461+
"type": "authlogin",
2462+
"typeDefinitions": {
2463+
"authlogin": {
2464+
"filePath": "docs-shopify.dev/commands/interfaces/auth-login.interface.ts",
2465+
"name": "authlogin",
2466+
"description": "",
2467+
"members": [
2468+
{
2469+
"filePath": "docs-shopify.dev/commands/interfaces/auth-login.interface.ts",
2470+
"syntaxKind": "PropertySignature",
2471+
"name": "--alias <value>",
2472+
"value": "string",
2473+
"description": "Alias of the session you want to login to.",
2474+
"isOptional": true,
2475+
"environmentValue": "SHOPIFY_FLAG_AUTH_ALIAS"
2476+
}
2477+
],
2478+
"value": "export interface authlogin {\n /**\n * Alias of the session you want to login to.\n * @environment SHOPIFY_FLAG_AUTH_ALIAS\n */\n '--alias <value>'?: string\n}"
2479+
}
2480+
}
2481+
}
2482+
],
2483+
"category": "general commands",
2484+
"related": []
2485+
},
24392486
{
24402487
"name": "auth logout",
24412488
"description": "Logs you out of the Shopify account or Partner account and store.",

packages/app/src/cli/utilities/developer-platform-client/partners-client.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -623,13 +623,13 @@ export class PartnersClient implements DeveloperPlatformClient {
623623

624624
if (!response.ok) {
625625
return {
626-
errors: data.errors || [`Request failed with status ${response.status}`],
626+
errors: data.errors ?? [`Request failed with status ${response.status}`],
627627
status: response.status,
628628
}
629629
}
630630

631631
return {
632-
app_logs: data.app_logs || [],
632+
app_logs: data.app_logs ?? [],
633633
cursor: data.cursor,
634634
status: response.status,
635635
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// UserEmail query for fetching user email from Business Platform API
2+
export interface UserEmailQuery {
3+
currentUserAccount?: {
4+
email: string
5+
} | null
6+
}
7+
8+
export const UserEmailQueryString = `
9+
query UserEmail {
10+
currentUserAccount {
11+
email
12+
}
13+
}
14+
`

packages/cli-kit/src/private/node/api/headers.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {buildHeaders, sanitizedHeadersOutput} from './headers.js'
1+
import {buildHeaders, sanitizedHeadersOutput, GraphQLClientError} from './headers.js'
22
import {CLI_KIT_VERSION} from '../../../public/common/version.js'
33
import {randomUUID} from '../../../public/node/crypto.js'
44
import {firstPartyDev, isUnitTest} from '../../../public/node/context/local.js'
@@ -97,3 +97,33 @@ describe('common API methods', () => {
9797
`)
9898
})
9999
})
100+
101+
describe('GraphQLClientError', () => {
102+
test('sets custom tryMessage for 403 status code', () => {
103+
// Given
104+
const message = 'Forbidden access'
105+
const statusCode = 403
106+
107+
// When
108+
const error = new GraphQLClientError(message, statusCode)
109+
110+
// Then
111+
expect(error.message).toBe(message)
112+
expect(error.statusCode).toBe(403)
113+
expect(error.tryMessage).toBe('Ensure you are using the correct account. You can switch with `shopify auth login`')
114+
})
115+
116+
test('does not set tryMessage for non-403 status codes', () => {
117+
// Given
118+
const message = 'Server error'
119+
const statusCode = 500
120+
121+
// When
122+
const error = new GraphQLClientError(message, statusCode)
123+
124+
// Then
125+
expect(error.message).toBe(message)
126+
expect(error.statusCode).toBe(500)
127+
expect(error.tryMessage).toBeNull()
128+
})
129+
})

packages/cli-kit/src/private/node/api/headers.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import {CLI_KIT_VERSION} from '../../../public/common/version.js'
22
import {firstPartyDev} from '../../../public/node/context/local.js'
3-
import {ExtendableError} from '../../../public/node/error.js'
3+
import {AbortError} from '../../../public/node/error.js'
44
import https from 'https'
55

6-
class RequestClientError extends ExtendableError {
6+
class RequestClientError extends AbortError {
77
statusCode: number
88
public constructor(message: string, statusCode: number) {
9-
super(message)
9+
const tryMessage =
10+
statusCode === 403
11+
? 'Ensure you are using the correct account. You can switch with `shopify auth login`'
12+
: undefined
13+
super(message, tryMessage)
1014
this.statusCode = statusCode
1115
}
1216
}

packages/cli-kit/src/private/node/conf-store.test.ts

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import {
22
ConfSchema,
33
cacheRetrieve,
44
cacheRetrieveOrRepopulate,
5-
getSession,
6-
removeSession,
7-
setSession,
5+
getSessions,
6+
removeSessions,
7+
setSessions,
8+
getCurrentSessionId,
9+
setCurrentSessionId,
10+
removeCurrentSessionId,
811
runAtMinimumInterval,
912
getConfigStoreForPartnerStatus,
1013
getCachedPartnerAccountStatus,
@@ -23,7 +26,7 @@ describe('getSession', () => {
2326
config.set('sessionStore', 'my-session')
2427

2528
// When
26-
const got = getSession(config)
29+
const got = getSessions(config)
2730

2831
// Then
2932
expect(got).toEqual('my-session')
@@ -39,7 +42,7 @@ describe('setSession', () => {
3942
config.set('sessionStore', 'my-session')
4043

4144
// When
42-
setSession('my-session', config)
45+
setSessions('my-session', config)
4346

4447
// Then
4548
expect(config.get('sessionStore')).toEqual('my-session')
@@ -55,14 +58,74 @@ describe('removeSession', () => {
5558
config.set('sessionStore', 'my-session')
5659

5760
// When
58-
removeSession(config)
61+
removeSessions(config)
5962

6063
// Then
6164
expect(config.get('sessionStore')).toEqual(undefined)
6265
})
6366
})
6467
})
6568

69+
describe('getCurrentSessionId', () => {
70+
test('returns the content of the currentSessionId key', async () => {
71+
await inTemporaryDirectory(async (cwd) => {
72+
// Given
73+
const config = new LocalStorage<ConfSchema>({cwd})
74+
config.set('currentSessionId', 'user-123')
75+
76+
// When
77+
const got = getCurrentSessionId(config)
78+
79+
// Then
80+
expect(got).toEqual('user-123')
81+
})
82+
})
83+
84+
test('returns undefined when currentSessionId is not set', async () => {
85+
await inTemporaryDirectory(async (cwd) => {
86+
// Given
87+
const config = new LocalStorage<ConfSchema>({cwd})
88+
89+
// When
90+
const got = getCurrentSessionId(config)
91+
92+
// Then
93+
expect(got).toBeUndefined()
94+
})
95+
})
96+
})
97+
98+
describe('setCurrentSessionId', () => {
99+
test('saves the desired content in the currentSessionId key', async () => {
100+
await inTemporaryDirectory(async (cwd) => {
101+
// Given
102+
const config = new LocalStorage<ConfSchema>({cwd})
103+
104+
// When
105+
setCurrentSessionId('user-456', config)
106+
107+
// Then
108+
expect(config.get('currentSessionId')).toEqual('user-456')
109+
})
110+
})
111+
})
112+
113+
describe('removeCurrentSessionId', () => {
114+
test('removes the currentSessionId key', async () => {
115+
await inTemporaryDirectory(async (cwd) => {
116+
// Given
117+
const config = new LocalStorage<ConfSchema>({cwd})
118+
config.set('currentSessionId', 'user-789')
119+
120+
// When
121+
removeCurrentSessionId(config)
122+
123+
// Then
124+
expect(config.get('currentSessionId')).toBeUndefined()
125+
})
126+
})
127+
})
128+
66129
describe('cacheRetrieveOrRepopulate', () => {
67130
test('returns the cached contents when they exist', async () => {
68131
await inTemporaryDirectory(async (cwd) => {

0 commit comments

Comments
 (0)