@@ -2,7 +2,6 @@ import {basename, dirname} from 'node:path'
22import { styleText } from 'node:util'
33import { createGzip } from 'node:zlib'
44
5- import { CLIError } from '@oclif/core/errors'
65import { exitCodes } from '@sanity/cli-core'
76import { spinner } from '@sanity/cli-core/ux'
87import { getWorkbench } from '@sanity/workbench-cli/deploy'
@@ -25,72 +24,42 @@ import {
2524 checkBuild ,
2625 checkPackageVersion ,
2726 type CheckReporter ,
28- createFailFastReporter ,
2927 verifyOutputDir ,
3028} from './deployChecks.js'
3129import { deployDebug } from './deployDebug.js'
30+ import { runDeploy } from './deployRunner.js'
3231import { findUserApplication } from './findUserApplication.js'
3332import { type DeployAppOptions } from './types.js'
3433
35- type Workbench = ReturnType < typeof getWorkbench >
34+ export function deployApp ( options : DeployAppOptions ) : Promise < void > {
35+ return runDeploy ( options , { run : runAppDeployment , type : 'coreApp' } )
36+ }
3637
37- export async function deployApp ( options : DeployAppOptions ) : Promise < void > {
38- const { cliConfig, output} = options
38+ /**
39+ * Validates the deploy, syncs the title from the manifest, and ships the build.
40+ * Every step reports through `reporter`, and the first failure exits — so
41+ * reaching any later step means the earlier ones passed.
42+ */
43+ async function runAppDeployment ( options : DeployAppOptions , reporter : CheckReporter ) : Promise < void > {
44+ const { cliConfig, flags, output, sourceDir} = options
45+ const workDir = options . projectRoot . directory
46+ const organizationId = cliConfig . app ?. organizationId
3947 const workbench = getWorkbench ( cliConfig )
4048
4149 // A federated app with no entry, view or service would ship a remote with
42- // nothing to load — fail before any prompts or API calls .
50+ // nothing to load — reported first so it fails before any prompt or API call .
4351 if ( workbench ) {
4452 try {
4553 workbench . assertDeployable ( )
4654 } catch ( err ) {
47- output . error ( getErrorMessage ( err ) , { exit : exitCodes . USAGE_ERROR } )
48- return
49- }
50- }
51-
52- try {
53- const reporter = createFailFastReporter ( output )
54- await createAppDeployment ( options , reporter , workbench )
55- } catch ( error ) {
56- // Don't throw a generic error when the user cancels a prompt
57- if ( error . name === 'ExitPromptError' ) {
58- output . error ( 'Deployment cancelled by user' , { exit : 1 } )
59- return
60- }
61- if ( error instanceof CLIError ) {
62- const { message, ...errorOptions } = error
63- output . error ( message , { ...errorOptions , exit : 1 } )
64- return
55+ reporter . report ( {
56+ exitCode : exitCodes . USAGE_ERROR ,
57+ message : getErrorMessage ( err ) ,
58+ name : 'deployable' ,
59+ status : 'fail' ,
60+ } )
6561 }
66- deployDebug ( 'Error deploying application' , error )
67- output . error ( `Error deploying application: ${ error } ` , { exit : 1 } )
6862 }
69- }
70-
71- interface AppDeployment {
72- application : UserApplication | null
73- isAutoUpdating : boolean
74- manifest : CoreAppManifest | undefined
75- version : string | null
76- }
77-
78- /**
79- * Validates the deploy, syncs the title from the manifest, and ships the build.
80- *
81- * Every step reports through `reporter`. In a real deploy a failed check exits
82- * immediately, so reaching any later step means the earlier ones passed. A dry
83- * run collects the failures instead and returns before shipping (the guard
84- * below), so the steps read as one straight sequence in both modes.
85- */
86- async function createAppDeployment (
87- options : DeployAppOptions ,
88- reporter : CheckReporter ,
89- workbench : Workbench ,
90- ) : Promise < AppDeployment > {
91- const { cliConfig, flags, output, projectRoot, sourceDir} = options
92- const workDir = projectRoot . directory
93- const organizationId = cliConfig . app ?. organizationId
9463
9564 const isAutoUpdating = checkAutoUpdates ( reporter , { cliConfig, flags} )
9665
@@ -145,39 +114,84 @@ async function createAppDeployment(
145114 } )
146115 }
147116
148- // Sync the application title from the manifest when it has changed
149- const titleUpdate = application ? resolveTitleUpdate ( manifest , application ) : null
150- if ( application && titleUpdate ) {
151- deployDebug ( 'Updating application title from manifest' , titleUpdate )
152- output . log (
153- titleUpdate . from
154- ? `Updating title from "${ titleUpdate . from } " to "${ titleUpdate . to } "`
155- : `Setting application title to "${ titleUpdate . to } "` ,
156- )
157- const spin = spinner ( 'Updating application title' ) . start ( )
158- try {
159- application = await updateUserApplication ( {
160- applicationId : application . id ,
161- appType : 'coreApp' ,
162- body : { title : titleUpdate . to } ,
163- } )
164- spin . succeed ( )
165- } catch ( err ) {
166- spin . fail ( )
167- const message = getErrorMessage ( err )
168- deployDebug ( 'Error updating application title' , { message} )
169- output . warn ( `Error updating application title: ${ message } ` )
170- }
117+ // A real deploy has already exited if anything failed; landing here without a
118+ // resolved application or version means the deploy target was never resolved.
119+ if ( ! application || ! version ) return
120+
121+ application = await syncApplicationTitle ( { application, manifest, output} )
122+ await shipAppDeployment ( { application, isAutoUpdating, manifest, sourceDir, version} )
123+
124+ logAppDeployed ( { application, cliConfig, output} )
125+ }
126+
127+ /** Finds the application a deploy targets, creating one when none is configured. */
128+ async function resolveAppApplication ( options : DeployAppOptions ) : Promise < UserApplication | null > {
129+ const { cliConfig, output} = options
130+ const organizationId = cliConfig . app ?. organizationId ?? ''
131+
132+ let application = await findUserApplication ( { cliConfig, organizationId, output} )
133+ deployDebug ( 'User application found' , application )
134+
135+ if ( ! application ) {
136+ deployDebug ( 'No user application found. Creating a new one' )
137+ application = await createUserApplication ( organizationId )
138+ deployDebug ( 'User application created' , application )
171139 }
172140
173- // A real deploy has already exited if anything failed; landing here without a
174- // resolved application or version means a dry run collected problems — stop
175- // before shipping.
176- if ( ! application || ! version ) return { application, isAutoUpdating, manifest, version}
141+ return application
142+ }
177143
178- const parentDir = dirname ( sourceDir )
179- const base = basename ( sourceDir )
180- const tarball = pack ( parentDir , { entries : [ base ] } ) . pipe ( createGzip ( ) )
144+ /** Syncs the application title from the manifest when it has changed. */
145+ async function syncApplicationTitle ( {
146+ application,
147+ manifest,
148+ output,
149+ } : {
150+ application : UserApplication
151+ manifest : CoreAppManifest | undefined
152+ output : DeployAppOptions [ 'output' ]
153+ } ) : Promise < UserApplication > {
154+ const titleUpdate = resolveTitleUpdate ( manifest , application )
155+ if ( ! titleUpdate ) return application
156+
157+ deployDebug ( 'Updating application title from manifest' , titleUpdate )
158+ output . log (
159+ titleUpdate . from
160+ ? `Updating title from "${ titleUpdate . from } " to "${ titleUpdate . to } "`
161+ : `Setting application title to "${ titleUpdate . to } "` ,
162+ )
163+ const spin = spinner ( 'Updating application title' ) . start ( )
164+ try {
165+ const updated = await updateUserApplication ( {
166+ applicationId : application . id ,
167+ appType : 'coreApp' ,
168+ body : { title : titleUpdate . to } ,
169+ } )
170+ spin . succeed ( )
171+ return updated
172+ } catch ( err ) {
173+ spin . fail ( )
174+ const message = getErrorMessage ( err )
175+ deployDebug ( 'Error updating application title' , { message} )
176+ output . warn ( `Error updating application title: ${ message } ` )
177+ return application
178+ }
179+ }
180+
181+ async function shipAppDeployment ( {
182+ application,
183+ isAutoUpdating,
184+ manifest,
185+ sourceDir,
186+ version,
187+ } : {
188+ application : UserApplication
189+ isAutoUpdating : boolean
190+ manifest : CoreAppManifest | undefined
191+ sourceDir : string
192+ version : string
193+ } ) : Promise < void > {
194+ const tarball = pack ( dirname ( sourceDir ) , { entries : [ basename ( sourceDir ) ] } ) . pipe ( createGzip ( ) )
181195
182196 const spin = spinner ( 'Deploying...' ) . start ( )
183197 try {
@@ -194,15 +208,26 @@ async function createAppDeployment(
194208 throw error
195209 }
196210 spin . succeed ( )
211+ }
197212
213+ function logAppDeployed ( {
214+ application,
215+ cliConfig,
216+ output,
217+ } : {
218+ application : UserApplication
219+ cliConfig : DeployAppOptions [ 'cliConfig' ]
220+ output : DeployAppOptions [ 'output' ]
221+ } ) : void {
198222 output . log ( `\n🚀 ${ styleText ( 'bold' , 'Success!' ) } Application deployed` )
199223
200- if ( ! getAppId ( cliConfig ) ) {
201- output . log ( `\n════ ${ styleText ( 'bold' , 'Next step:' ) } ════` )
202- output . log (
203- styleText ( 'bold' , '\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:' ) ,
204- )
205- output . log ( `
224+ if ( getAppId ( cliConfig ) ) return
225+
226+ output . log ( `\n════ ${ styleText ( 'bold' , 'Next step:' ) } ════` )
227+ output . log (
228+ styleText ( 'bold' , '\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:' ) ,
229+ )
230+ output . log ( `
206231${ styleText (
207232 'dim' ,
208233 `app: {
@@ -215,22 +240,4 @@ ${styleText(
215240 appId: '${ application . id } ',
216241}\n` ,
217242) } `)
218- }
219-
220- return { application, isAutoUpdating, manifest, version}
221- }
222-
223- async function resolveAppApplication ( options : DeployAppOptions ) : Promise < UserApplication | null > {
224- const { cliConfig, output} = options
225- const organizationId = cliConfig . app ?. organizationId ?? ''
226- let application = await findUserApplication ( { cliConfig, organizationId, output} )
227- deployDebug ( 'User application found' , application )
228-
229- if ( ! application ) {
230- deployDebug ( 'No user application found. Creating a new one' )
231- application = await createUserApplication ( organizationId )
232- deployDebug ( 'User application created' , application )
233- }
234-
235- return application
236243}
0 commit comments