@@ -34,11 +34,11 @@ const DEFAULT_CLOUD_RUN_OPTIONS: Partial<CloudRunOptions> = {
3434
3535const spawnAsync = async (
3636 command : string ,
37+ args : string [ ] ,
3738 options ?: SpawnOptionsWithoutStdio
3839) =>
3940 new Promise < Buffer > ( ( resolve , reject ) => {
40- const [ spawnCommand , ...args ] = command . split ( / \s + / ) ;
41- const spawnProcess = spawn ( spawnCommand , args , options ) ;
41+ const spawnProcess = spawn ( command , args , options ) ;
4242 const chunks : Buffer [ ] = [ ] ;
4343 const errorChunks : Buffer [ ] = [ ] ;
4444 spawnProcess . stdout . on ( 'data' , ( data ) => {
@@ -53,7 +53,7 @@ const spawnAsync = async (
5353 reject ( error ) ;
5454 } ) ;
5555 spawnProcess . on ( 'close' , ( code ) => {
56- if ( code === 1 ) {
56+ if ( code !== 0 ) {
5757 reject ( Buffer . concat ( errorChunks ) . toString ( ) ) ;
5858 return ;
5959 }
@@ -356,6 +356,34 @@ export const deployToFunction = async (
356356} ;
357357
358358
359+ // Exported (rather than kept private) so the argv shape can be asserted directly in tests,
360+ // without having to mock child_process.spawn.
361+ export const buildCloudRunBuildsSubmitArgs = (
362+ cloudRunOut : string ,
363+ serviceId : string ,
364+ options : DeployBuilderOptions
365+ ) : string [ ] => [
366+ 'builds' , 'submit' , cloudRunOut ,
367+ '--tag' , `gcr.io/${ options . firebaseProject } /${ serviceId } ` ,
368+ '--project' , options . firebaseProject ,
369+ '--quiet' ,
370+ ] ;
371+
372+ export const buildCloudRunDeployArgs = (
373+ serviceId : string ,
374+ options : DeployBuilderOptions ,
375+ deployArguments : string [ ]
376+ ) : string [ ] => [
377+ 'run' , 'deploy' , serviceId ,
378+ '--image' , `gcr.io/${ options . firebaseProject } /${ serviceId } ` ,
379+ '--project' , options . firebaseProject ,
380+ ...deployArguments ,
381+ '--platform' , 'managed' ,
382+ '--allow-unauthenticated' ,
383+ '--region' , options . region ,
384+ '--quiet' ,
385+ ] ;
386+
359387export const deployToCloudRun = async (
360388 firebaseTools : FirebaseTools ,
361389 context : BuilderContext ,
@@ -430,25 +458,23 @@ export const deployToCloudRun = async (
430458 throw new SchematicsException ( 'Cloud Run preview not supported.' ) ;
431459 }
432460
433- const deployArguments : any [ ] = [ ] ;
461+ const deployArguments : string [ ] = [ ] ;
434462 const cloudRunOptions = options . cloudRunOptions || { } ;
435463 Object . entries ( DEFAULT_CLOUD_RUN_OPTIONS ) . forEach ( ( [ k , v ] ) => {
436464 cloudRunOptions [ k ] ||= v ;
437465 } ) ;
438466 // lean on the schema for validation (rather than sanitize)
439- if ( cloudRunOptions . cpus ) { deployArguments . push ( '--cpu' , cloudRunOptions . cpus ) ; }
440- if ( cloudRunOptions . maxConcurrency ) { deployArguments . push ( '--concurrency' , cloudRunOptions . maxConcurrency ) ; }
441- if ( cloudRunOptions . maxInstances ) { deployArguments . push ( '--max-instances' , cloudRunOptions . maxInstances ) ; }
442- if ( cloudRunOptions . memory ) { deployArguments . push ( '--memory' , cloudRunOptions . memory ) ; }
443- if ( cloudRunOptions . minInstances ) { deployArguments . push ( '--min-instances' , cloudRunOptions . minInstances ) ; }
444- if ( cloudRunOptions . timeout ) { deployArguments . push ( '--timeout' , cloudRunOptions . timeout ) ; }
467+ if ( cloudRunOptions . cpus ) { deployArguments . push ( '--cpu' , cloudRunOptions . cpus . toString ( ) ) ; }
468+ if ( cloudRunOptions . maxConcurrency ) { deployArguments . push ( '--concurrency' , cloudRunOptions . maxConcurrency . toString ( ) ) ; }
469+ if ( cloudRunOptions . maxInstances ) { deployArguments . push ( '--max-instances' , cloudRunOptions . maxInstances . toString ( ) ) ; }
470+ if ( cloudRunOptions . memory ) { deployArguments . push ( '--memory' , cloudRunOptions . memory . toString ( ) ) ; }
471+ if ( cloudRunOptions . minInstances ) { deployArguments . push ( '--min-instances' , cloudRunOptions . minInstances . toString ( ) ) ; }
472+ if ( cloudRunOptions . timeout ) { deployArguments . push ( '--timeout' , cloudRunOptions . timeout . toString ( ) ) ; }
445473 if ( cloudRunOptions . vpcConnector ) { deployArguments . push ( '--vpc-connector' , cloudRunOptions . vpcConnector ) ; }
446474
447- // TODO validate serviceId, firebaseProject, and vpcConnector both to limit errors and opp for injection
448-
449475 context . logger . info ( `📦 Deploying to Cloud Run` ) ;
450- await spawnAsync ( ` gcloud builds submit ${ cloudRunOut } --tag gcr.io/ ${ options . firebaseProject } / ${ serviceId } --project ${ options . firebaseProject } --quiet` ) ;
451- await spawnAsync ( ` gcloud run deploy ${ serviceId } --image gcr.io/ ${ options . firebaseProject } / ${ serviceId } --project ${ options . firebaseProject } ${ deployArguments . join ( ' ' ) } --platform managed --allow-unauthenticated --region= ${ options . region } --quiet` ) ;
476+ await spawnAsync ( ' gcloud' , buildCloudRunBuildsSubmitArgs ( cloudRunOut , serviceId , options ) ) ;
477+ await spawnAsync ( ' gcloud' , buildCloudRunDeployArgs ( serviceId , options , deployArguments ) ) ;
452478
453479 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
454480 const siteTarget = options . target ?? context . target ! . project ;
@@ -482,7 +508,7 @@ export default async function deploy(
482508 }
483509
484510 if ( ! firebaseToken && process . env . GOOGLE_APPLICATION_CREDENTIALS ) {
485- await spawnAsync ( ` gcloud auth activate-service-account --key-file ${ process . env . GOOGLE_APPLICATION_CREDENTIALS } ` ) ;
511+ await spawnAsync ( ' gcloud' , [ ' auth' , ' activate-service-account' , ' --key-file' , process . env . GOOGLE_APPLICATION_CREDENTIALS ] ) ;
486512 console . log ( `Using Google Application Credentials.` ) ;
487513 }
488514
0 commit comments