@@ -14,13 +14,16 @@ import {glob} from '@shopify/cli-kit/node/fs'
1414import { cwd } from '@shopify/cli-kit/node/path'
1515import { insideGitDirectory , isClean } from '@shopify/cli-kit/node/git'
1616import { recordTiming } from '@shopify/cli-kit/node/analytics'
17+ import { Writable } from 'stream'
1718
1819interface PullOptions {
1920 path : string
2021 nodelete : boolean
2122 force : boolean
2223 only ?: string [ ]
2324 ignore ?: string [ ]
25+ environment ?: string
26+ multiEnvironment ?: boolean
2427}
2528
2629export interface PullFlags {
@@ -83,6 +86,11 @@ export interface PullFlags {
8386 * Increase the verbosity of the output.
8487 */
8588 verbose ?: boolean
89+
90+ /**
91+ * The environment to pull from.
92+ */
93+ environment ?: string [ ]
8694}
8795
8896/**
@@ -91,19 +99,25 @@ export interface PullFlags {
9199 *
92100 * @param flags - All flags are optional.
93101 */
94- export async function pull ( flags : PullFlags ) : Promise < void > {
102+ export async function pull (
103+ flags : PullFlags ,
104+ session ?: AdminSession ,
105+ multiEnvironment ?: boolean ,
106+ context ?: { stdout ?: Writable ; stderr ?: Writable } ,
107+ ) : Promise < void > {
95108 recordTiming ( 'theme-service:pull:setup' )
96109 configureCLIEnvironment ( { verbose : flags . verbose , noColor : flags . noColor } )
97110
98- const store = ensureThemeStore ( { store : flags . store } )
99- const adminSession = await ensureAuthenticatedThemes ( store , flags . password )
111+ // when pull is used programmatically, we don't have an admin session, so need to create one
112+ const adminSession =
113+ session ?? ( await ensureAuthenticatedThemes ( ensureThemeStore ( { store : flags . store } ) , flags . password ) )
100114
101115 const developmentThemeManager = new DevelopmentThemeManager ( adminSession )
102116 const developmentTheme = await ( flags . development ? developmentThemeManager . find ( ) : developmentThemeManager . fetch ( ) )
103117
104- const { path, nodelete, live, development, only, ignore, force} = flags
118+ const { path, nodelete, live, development, only, ignore, force, environment } = flags
105119
106- if ( ! ( await validateDirectory ( path ?? cwd ( ) , force ?? false ) ) ) {
120+ if ( ! ( await validateDirectory ( path ?? cwd ( ) , force ?? false , environment ?. [ 0 ] , multiEnvironment ) ) ) {
107121 return
108122 }
109123
@@ -116,13 +130,20 @@ export async function pull(flags: PullFlags): Promise<void> {
116130 } )
117131 recordTiming ( 'theme-service:pull:setup' )
118132
119- await executePull ( theme , adminSession , {
120- path : path ?? cwd ( ) ,
121- nodelete : nodelete ?? false ,
122- only : only ?? [ ] ,
123- ignore : ignore ?? [ ] ,
124- force : force ?? false ,
125- } )
133+ await executePull (
134+ theme ,
135+ adminSession ,
136+ {
137+ environment : environment ?. [ 0 ] ,
138+ force : force ?? false ,
139+ ignore : ignore ?? [ ] ,
140+ multiEnvironment,
141+ nodelete : nodelete ?? false ,
142+ only : only ?? [ ] ,
143+ path : path ?? cwd ( ) ,
144+ } ,
145+ context ,
146+ )
126147}
127148
128149/**
@@ -132,7 +153,12 @@ export async function pull(flags: PullFlags): Promise<void> {
132153 * @param session - the admin session to access the API and download the theme
133154 * @param options - the options that modify how the theme gets downloaded
134155 */
135- async function executePull ( theme : Theme , session : AdminSession , options : PullOptions ) {
156+ async function executePull (
157+ theme : Theme ,
158+ session : AdminSession ,
159+ options : PullOptions ,
160+ context ?: { stdout ?: Writable ; stderr ?: Writable } ,
161+ ) {
136162 recordTiming ( 'theme-service:pull:file-system' )
137163 const themeFileSystem = mountThemeFileSystem ( options . path , { filters : options } )
138164 const [ remoteChecksums ] = await Promise . all ( [ fetchChecksums ( theme . id , session ) , themeFileSystem . ready ( ) ] )
@@ -142,9 +168,11 @@ async function executePull(theme: Theme, session: AdminSession, options: PullOpt
142168 const store = session . storeFqdn
143169 const themeId = theme . id
144170
145- await downloadTheme ( theme , session , themeChecksums , themeFileSystem , options )
171+ await downloadTheme ( theme , session , themeChecksums , themeFileSystem , options , context )
146172
173+ const header = options . environment ? `Environment: ${ options . environment } ` : ''
147174 renderSuccess ( {
175+ headline : header ,
148176 body : [ 'The theme' , ...themeComponent ( theme ) , 'has been pulled.' ] ,
149177 nextSteps : [
150178 [
@@ -190,7 +218,7 @@ export async function isEmptyDir(path: string) {
190218 * @param force - Whether to force the pull operation.
191219 * @returns Whether the directory is valid.
192220 */
193- async function validateDirectory ( path : string , force : boolean ) {
221+ async function validateDirectory ( path : string , force : boolean , environment ?: string , multiEnvironment ?: boolean ) {
194222 if ( force ) return true
195223
196224 /**
@@ -202,7 +230,7 @@ async function validateDirectory(path: string, force: boolean) {
202230 if (
203231 ! ( await isEmptyDir ( path ) ) &&
204232 ! ( await hasRequiredThemeDirectories ( path ) ) &&
205- ! ( await ensureDirectoryConfirmed ( force ) )
233+ ! ( await ensureDirectoryConfirmed ( force , undefined , environment , multiEnvironment ) )
206234 ) {
207235 return false
208236 }
@@ -217,7 +245,9 @@ async function validateDirectory(path: string, force: boolean) {
217245 dirtyDirectory &&
218246 ! ( await ensureDirectoryConfirmed (
219247 force ,
220- 'The current Git directory has uncommitted changes. Do you want to proceed?' ,
248+ 'The current Git directory has uncommitted changes.' ,
249+ environment ,
250+ multiEnvironment ,
221251 ) )
222252 ) {
223253 return false
0 commit comments