Skip to content

Commit 3a2f619

Browse files
authored
Merge pull request #6312 from Shopify/jb-multi-env-conditional-sessions
Only create sessions for multi env commands that require authentication
2 parents 3461431 + 80d8870 commit 3a2f619

File tree

3 files changed

+77
-14
lines changed

3 files changed

+77
-14
lines changed

.changeset/large-otters-dream.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@shopify/cli-kit': minor
3+
'@shopify/theme': minor
4+
---
5+
6+
Commands that don't require authentication should not create sessions when ran with multiple environments

packages/theme/src/cli/utilities/theme-command.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,32 @@ class TestThemeCommandWithUnionFlags extends TestThemeCommand {
8585
}
8686
}
8787

88+
class TestUnauthenticatedThemeCommand extends ThemeCommand {
89+
static flags = {
90+
environment: Flags.string({
91+
multiple: true,
92+
default: [],
93+
env: 'SHOPIFY_FLAG_ENVIRONMENT',
94+
}),
95+
store: Flags.string({
96+
env: 'SHOPIFY_FLAG_STORE',
97+
}),
98+
}
99+
100+
static multiEnvironmentsFlags: RequiredFlags = ['store']
101+
102+
commandCalls: {flags: any; session: AdminSession; multiEnvironment?: boolean; context?: any}[] = []
103+
104+
async command(
105+
flags: any,
106+
session: AdminSession,
107+
multiEnvironment?: boolean,
108+
context?: {stdout?: Writable; stderr?: Writable},
109+
): Promise<void> {
110+
this.commandCalls.push({flags, session, multiEnvironment, context})
111+
}
112+
}
113+
88114
describe('ThemeCommand', () => {
89115
let mockSession: AdminSession
90116

@@ -552,5 +578,31 @@ describe('ThemeCommand', () => {
552578
expect(liveEnvFlags?.live).toEqual(true)
553579
expect(liveEnvFlags?.['no-color']).toEqual(true)
554580
})
581+
582+
test('commands will only create a session object if the password flag is supported', async () => {
583+
// Given
584+
vi.mocked(loadEnvironment)
585+
.mockResolvedValueOnce({store: 'store1.myshopify.com'})
586+
.mockResolvedValueOnce({store: 'store2.myshopify.com'})
587+
588+
vi.mocked(renderConcurrent).mockImplementation(async ({processes}) => {
589+
for (const process of processes) {
590+
// eslint-disable-next-line no-await-in-loop
591+
await process.action({} as Writable, {} as Writable, {} as any)
592+
}
593+
})
594+
595+
await CommandConfig.load()
596+
const command = new TestUnauthenticatedThemeCommand(
597+
['--environment', 'store1', '--environment', 'store2'],
598+
CommandConfig,
599+
)
600+
601+
// When
602+
await command.run()
603+
604+
// Then
605+
expect(ensureAuthenticatedThemes).not.toHaveBeenCalled()
606+
})
555607
})
556608
})

packages/theme/src/cli/utilities/theme-command.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ interface PassThroughFlagsOptions {
2727
// Only pass on flags that are relevant to CLI2
2828
allowedFlags?: string[]
2929
}
30+
interface ValidEnvironment {
31+
environment: EnvironmentName
32+
flags: FlagValues
33+
requiresAuth: boolean
34+
}
3035
type EnvironmentName = string
3136
/**
3237
* Flags required to run a command in multiple environments
@@ -67,7 +72,7 @@ export default abstract class ThemeCommand extends Command {
6772

6873
async command(
6974
_flags: FlagValues,
70-
_session: AdminSession,
75+
_session?: AdminSession,
7176
_multiEnvironment = false,
7277
_context?: {stdout?: Writable; stderr?: Writable},
7378
): Promise<void> {}
@@ -84,18 +89,17 @@ export default abstract class ThemeCommand extends Command {
8489
}
8590
const requiredFlags = klass.multiEnvironmentsFlags
8691
const {flags} = await this.parse(klass)
87-
92+
const commandRequiresAuth = 'password' in klass.flags
8893
const environments = (Array.isArray(flags.environment) ? flags.environment : [flags.environment]).filter(Boolean)
8994

9095
// Single environment or no environment
9196
if (environments.length <= 1) {
92-
const session = await this.createSession(flags)
97+
const session = commandRequiresAuth ? await this.createSession(flags) : undefined
9398
const commandName = this.constructor.name.toLowerCase()
9499

95100
recordEvent(`theme-command:${commandName}:single-env:authenticated`)
96101

97102
await this.command(flags, session)
98-
await this.logAnalyticsData(session)
99103
return
100104
}
101105

@@ -112,7 +116,7 @@ export default abstract class ThemeCommand extends Command {
112116
}
113117

114118
const environmentsMap = await this.loadEnvironments(environments, flags, flagsWithoutDefaults)
115-
const validationResults = await this.validateEnvironments(environmentsMap, requiredFlags)
119+
const validationResults = await this.validateEnvironments(environmentsMap, requiredFlags, commandRequiresAuth)
116120

117121
const commandAllowsForceFlag = 'force' in klass.flags
118122

@@ -160,13 +164,15 @@ export default abstract class ThemeCommand extends Command {
160164
* Split environments into valid and invalid based on flags
161165
* @param environmentMap - The map of environments to validate
162166
* @param requiredFlags - The required flags to check for
167+
* @param requiresAuth - Whether the command requires authentication
163168
* @returns An object containing valid and invalid environment arrays
164169
*/
165170
private async validateEnvironments(
166171
environmentMap: Map<EnvironmentName, FlagValues>,
167172
requiredFlags: Exclude<RequiredFlags, null>,
173+
requiresAuth: boolean,
168174
) {
169-
const valid: {environment: EnvironmentName; flags: FlagValues}[] = []
175+
const valid: ValidEnvironment[] = []
170176
const invalid: {environment: EnvironmentName; reason: string}[] = []
171177

172178
for (const [environmentName, environmentFlags] of environmentMap) {
@@ -176,8 +182,7 @@ export default abstract class ThemeCommand extends Command {
176182
invalid.push({environment: environmentName, reason: `Missing flags: ${missingFlagsText}`})
177183
continue
178184
}
179-
180-
valid.push({environment: environmentName, flags: environmentFlags})
185+
valid.push({environment: environmentName, flags: environmentFlags, requiresAuth})
181186
}
182187

183188
return {valid, invalid}
@@ -194,7 +199,7 @@ export default abstract class ThemeCommand extends Command {
194199
commandName: string,
195200
requiredFlags: Exclude<RequiredFlags, null>,
196201
validationResults: {
197-
valid: {environment: string; flags: FlagValues}[]
202+
valid: ValidEnvironment[]
198203
invalid: {environment: string; reason: string}[]
199204
},
200205
) {
@@ -234,7 +239,7 @@ export default abstract class ThemeCommand extends Command {
234239
* Run the command in each valid environment concurrently
235240
* @param validEnvironments - The valid environments to run the command in
236241
*/
237-
private async runConcurrent(validEnvironments: {environment: EnvironmentName; flags: FlagValues}[]) {
242+
private async runConcurrent(validEnvironments: ValidEnvironment[]) {
238243
const abortController = new AbortController()
239244

240245
const stores = validEnvironments.map((env) => env.flags.store as string)
@@ -245,13 +250,13 @@ export default abstract class ThemeCommand extends Command {
245250
for (const runGroup of runGroups) {
246251
// eslint-disable-next-line no-await-in-loop
247252
await renderConcurrent({
248-
processes: runGroup.map(({environment, flags}) => ({
253+
processes: runGroup.map(({environment, flags, requiresAuth}) => ({
249254
prefix: environment,
250255
action: async (stdout: Writable, stderr: Writable, _signal) => {
251256
try {
252257
const store = flags.store as string
253258
await useThemeStoreContext(store, async () => {
254-
const session = await this.createSession(flags)
259+
const session = requiresAuth ? await this.createSession(flags) : undefined
255260

256261
const commandName = this.constructor.name.toLowerCase()
257262
recordEvent(`theme-command:${commandName}:multi-env:authenticated`)
@@ -280,8 +285,8 @@ export default abstract class ThemeCommand extends Command {
280285
* @param environments - The environments to group
281286
* @returns The environment groups
282287
*/
283-
private createSequentialGroups(environments: {environment: EnvironmentName; flags: FlagValues}[]) {
284-
const groups: {environment: EnvironmentName; flags: FlagValues}[][] = []
288+
private createSequentialGroups(environments: ValidEnvironment[]) {
289+
const groups: ValidEnvironment[][] = []
285290

286291
environments.forEach((environment) => {
287292
const groupWithoutStore = groups.find((arr) => !arr.some((env) => env.flags.store === environment.flags.store))

0 commit comments

Comments
 (0)