@@ -4,8 +4,7 @@ import {Input} from '@oclif/core/interfaces'
44import Command , { ArgOutput , FlagOutput } from '@shopify/cli-kit/node/base-command'
55import { AdminSession , ensureAuthenticatedThemes } from '@shopify/cli-kit/node/session'
66import { loadEnvironment } from '@shopify/cli-kit/node/environments'
7- import { renderWarning , renderConcurrent } from '@shopify/cli-kit/node/ui'
8- import { AbortError } from '@shopify/cli-kit/node/error'
7+ import { renderWarning , renderConcurrent , renderError } from '@shopify/cli-kit/node/ui'
98import { AbortController } from '@shopify/cli-kit/node/abort'
109import type { Writable } from 'stream'
1110
@@ -16,6 +15,7 @@ interface PassThroughFlagsOptions {
1615 // Only pass on flags that are relevant to CLI2
1716 allowedFlags ?: string [ ]
1817}
18+ type EnvironmentName = string
1919
2020export default abstract class ThemeCommand extends Command {
2121 passThroughFlags ( flags : FlagValues , { allowedFlags} : PassThroughFlagsOptions ) : string [ ] {
@@ -41,6 +41,7 @@ export default abstract class ThemeCommand extends Command {
4141 async command (
4242 _flags : FlagValues ,
4343 _session : AdminSession ,
44+ _multiEnvironment = false ,
4445 _context ?: { stdout ?: Writable ; stderr ?: Writable } ,
4546 ) : Promise < void > { }
4647
@@ -60,72 +61,125 @@ export default abstract class ThemeCommand extends Command {
6061
6162 // Single environment or no environment
6263 if ( environments . length <= 1 ) {
63- const session = await this . ensureAuthenticated ( flags )
64+ const session = await this . createSession ( flags )
65+
6466 await this . command ( flags , session )
6567 return
6668 }
6769
6870 // Multiple environments
69- const sessions : { [ storeFqdn : string ] : AdminSession } = { }
71+ const environmentsMap = await this . loadEnvironments ( environments , flags )
72+ const validationResults = await this . validateEnvironments ( environmentsMap , requiredFlags )
73+
74+ await this . runConcurrent ( validationResults . valid )
75+ }
76+
77+ /**
78+ * Create a map of environments from the shopify.theme.toml file
79+ * @param environments - Names of environments to load
80+ * @param flags - Flags provided via the CLI
81+ * @returns The map of environments
82+ */
83+ private async loadEnvironments (
84+ environments : EnvironmentName [ ] ,
85+ flags : FlagValues ,
86+ ) : Promise < Map < EnvironmentName , FlagValues > > {
87+ const environmentMap = new Map < EnvironmentName , FlagValues > ( )
7088
71- // Authenticate on all environments sequentially to avoid race conditions,
72- // with authentication happening in parallel.
7389 for ( const environmentName of environments ) {
7490 // eslint-disable-next-line no-await-in-loop
75- const environmentConfig = await loadEnvironment ( environmentName , 'shopify.theme.toml' , {
76- from : flags . path ,
91+ const environmentFlags = await loadEnvironment ( environmentName , 'shopify.theme.toml' , {
92+ from : flags . path as string ,
7793 silent : true ,
7894 } )
95+
96+ environmentMap . set ( environmentName , {
97+ ...environmentFlags ,
98+ ...flags ,
99+ environment : [ environmentName ] ,
100+ } )
101+ }
102+
103+ return environmentMap
104+ }
105+
106+ /**
107+ * Split environments into valid and invalid based on flags
108+ * @param environmentMap - The map of environments to validate
109+ * @param requiredFlags - The required flags to check for
110+ * @returns An object containing valid and invalid environment arrays
111+ */
112+ private async validateEnvironments ( environmentMap : Map < EnvironmentName , FlagValues > , requiredFlags : string [ ] ) {
113+ const valid : { environment : EnvironmentName ; flags : FlagValues ; session : AdminSession } [ ] = [ ]
114+ const invalid : { environment : EnvironmentName ; reason : string } [ ] = [ ]
115+
116+ for ( const [ environmentName , environmentFlags ] of environmentMap ) {
79117 // eslint-disable-next-line no-await-in-loop
80- sessions [ environmentName ] = await this . ensureAuthenticated ( environmentConfig as FlagValues )
118+ const session = await this . createSession ( environmentFlags )
119+
120+ const validationResult = this . validConfig ( environmentFlags , requiredFlags , environmentName )
121+ if ( validationResult !== true ) {
122+ const missingFlagsText = validationResult . join ( ', ' )
123+ invalid . push ( { environment : environmentName , reason : `Missing flags: ${ missingFlagsText } ` } )
124+ continue
125+ }
126+
127+ valid . push ( { environment : environmentName , flags : environmentFlags , session} )
81128 }
82129
83- // Use renderConcurrent for multi-environment execution
130+ return { valid, invalid}
131+ }
132+
133+ /**
134+ * Run the command in each valid environment concurrently
135+ * @param validEnvironments - The valid environments to run the command in
136+ */
137+ private async runConcurrent (
138+ validEnvironments : { environment : EnvironmentName ; flags : FlagValues ; session : AdminSession } [ ] ,
139+ ) {
84140 const abortController = new AbortController ( )
85141
86142 await renderConcurrent ( {
87- processes : environments . map ( ( environment : string ) => ( {
143+ processes : validEnvironments . map ( ( { environment, flags , session } ) => ( {
88144 prefix : environment ,
89145 action : async ( stdout : Writable , stderr : Writable , _signal ) => {
90- const environmentConfig = await loadEnvironment ( environment , 'shopify.theme.toml' , {
91- from : flags . path ,
92- silent : true ,
93- } )
94- const environmentFlags = {
95- ...flags ,
96- ...environmentConfig ,
97- environment : [ environment ] ,
146+ try {
147+ await this . command ( flags , session , true , { stdout, stderr} )
148+
149+ // eslint-disable-next-line no-catch-all/no-catch-all
150+ } catch ( error ) {
151+ if ( error instanceof Error ) {
152+ error . message = `Environment ${ environment } failed: \n\n${ error . message } `
153+ renderError ( { body : [ error . message ] } )
154+ }
98155 }
99-
100- if ( ! this . validConfig ( environmentConfig as FlagValues , requiredFlags , environment ) ) return
101-
102- const session = sessions [ environment ]
103-
104- if ( ! session ) {
105- throw new AbortError ( `No session found for environment ${ environment } ` )
106- }
107-
108- await this . command ( environmentFlags , session , { stdout, stderr} )
109156 } ,
110157 } ) ) ,
111158 abortSignal : abortController . signal ,
112159 showTimestamps : true ,
113160 } )
114161 }
115162
116- private async ensureAuthenticated ( flags : FlagValues ) {
163+ /**
164+ * Create an authenticated session object from the flags
165+ * @param flags - The environment flags containing store and password
166+ * @returns The unauthenticated session object
167+ */
168+ private async createSession ( flags : FlagValues ) {
117169 const store = flags . store as string
118170 const password = flags . password as string
119171 return ensureAuthenticatedThemes ( ensureThemeStore ( { store} ) , password )
120172 }
121173
122- private validConfig ( environmentConfig : FlagValues , requiredFlags : string [ ] , environmentName : string ) : boolean {
123- if ( ! environmentConfig ) {
124- renderWarning ( { body : 'Environment configuration is empty.' } )
125- return false
126- }
127- const required = [ ...requiredFlags ]
128- const missingFlags = required . filter ( ( flag ) => ! environmentConfig [ flag ] )
174+ /**
175+ * Ensure that all required flags are present
176+ * @param environmentFlags - The environment flags
177+ * @param requiredFlags - The flags required by the command
178+ * @param environmentName - The name of the environment
179+ * @returns The missing flags or true if the environment has all required flags
180+ */
181+ private validConfig ( environmentFlags : FlagValues , requiredFlags : string [ ] , environmentName : string ) : string [ ] | true {
182+ const missingFlags = requiredFlags . filter ( ( flag ) => ! environmentFlags [ flag ] )
129183
130184 if ( missingFlags . length > 0 ) {
131185 renderWarning ( {
@@ -134,7 +188,7 @@ export default abstract class ThemeCommand extends Command {
134188 { list : { items : missingFlags } } ,
135189 ] ,
136190 } )
137- return false
191+ return missingFlags
138192 }
139193
140194 return true
0 commit comments