@@ -107,26 +107,53 @@ pub(super) async fn handle_subscribe(
107107 matches : & ArgMatches ,
108108) -> Result < ( ) , GwsError > {
109109 let config = parse_subscribe_args ( matches) ?;
110+ let dry_run = matches. get_flag ( "dry-run" ) ;
111+
112+ if dry_run {
113+ eprintln ! ( "🏃 DRY RUN — no changes will be made\n " ) ;
114+ }
110115
111116 if let Some ( ref dir) = config. output_dir {
112- std:: fs:: create_dir_all ( dir) . context ( "Failed to create output dir" ) ?;
117+ if !dry_run {
118+ std:: fs:: create_dir_all ( dir) . context ( "Failed to create output dir" ) ?;
119+ }
113120 }
114121
115122 let client = crate :: client:: build_client ( ) ?;
116123 let pubsub_token_provider = auth:: token_provider ( & [ PUBSUB_SCOPE ] ) ;
117124
118- // Get Pub/Sub token
119- let pubsub_token = auth:: get_token ( & [ PUBSUB_SCOPE ] )
120- . await
121- . map_err ( |e| GwsError :: Auth ( format ! ( "Failed to get Pub/Sub token: {e}" ) ) ) ?;
122-
123125 let ( pubsub_subscription, topic_name, ws_subscription_name, created_resources) =
124126 if let Some ( ref sub_name) = config. subscription {
125127 // Use existing subscription — no setup needed
128+ // (don't fetch Pub/Sub token since we won't need it for existing subscriptions)
129+ if dry_run {
130+ eprintln ! ( "Would listen to existing subscription: {}" , sub_name. 0 ) ;
131+ let result = json ! ( {
132+ "dry_run" : true ,
133+ "action" : "Would listen to existing subscription" ,
134+ "subscription" : sub_name. 0 ,
135+ "note" : "Run without --dry-run to actually start listening"
136+ } ) ;
137+ println ! ( "{}" , serde_json:: to_string_pretty( & result) . context( "Failed to serialize dry-run output" ) ?) ;
138+ return Ok ( ( ) ) ;
139+ }
126140 ( sub_name. 0 . clone ( ) , None , None , false )
127141 } else {
142+ // Get Pub/Sub token only when creating new subscription
143+ let pubsub_token = if dry_run {
144+ None
145+ } else {
146+ Some (
147+ auth:: get_token ( & [ PUBSUB_SCOPE ] )
148+ . await
149+ . map_err ( |e| GwsError :: Auth ( format ! ( "Failed to get Pub/Sub token: {e}" ) ) ) ?,
150+ )
151+ } ;
152+
128153 // Full setup: create Pub/Sub topic + subscription + Workspace Events subscription
129- let target = config. target . clone ( ) . unwrap ( ) ;
154+ // Validate target before use in both dry-run and actual execution paths
155+ let target = crate :: validate:: validate_resource_name ( & config. target . clone ( ) . unwrap ( ) ) ?
156+ . to_string ( ) ;
130157 let project =
131158 crate :: validate:: validate_resource_name ( & config. project . clone ( ) . unwrap ( ) . 0 ) ?
132159 . to_string ( ) ;
@@ -140,11 +167,34 @@ pub(super) async fn handle_subscribe(
140167 let topic = format ! ( "projects/{project}/topics/gws-{slug}-{suffix}" ) ;
141168 let sub = format ! ( "projects/{project}/subscriptions/gws-{slug}-{suffix}" ) ;
142169
170+ // Dry-run: print what would be created and exit
171+ if dry_run {
172+ eprintln ! ( "Would create Pub/Sub topic: {topic}" ) ;
173+ eprintln ! ( "Would create Pub/Sub subscription: {sub}" ) ;
174+ eprintln ! ( "Would create Workspace Events subscription for target: {target}" ) ;
175+ eprintln ! ( "Would listen for event types: {}" , config. event_types. join( ", " ) ) ;
176+
177+ let result = json ! ( {
178+ "dry_run" : true ,
179+ "action" : "Would create Workspace Events subscription" ,
180+ "pubsub_topic" : topic,
181+ "pubsub_subscription" : sub,
182+ "target" : target,
183+ "event_types" : config. event_types,
184+ "note" : "Run without --dry-run to actually create subscription"
185+ } ) ;
186+ println ! ( "{}" , serde_json:: to_string_pretty( & result) . context( "Failed to serialize dry-run output" ) ?) ;
187+ return Ok ( ( ) ) ;
188+ }
189+
143190 // 1. Create Pub/Sub topic
144191 eprintln ! ( "Creating Pub/Sub topic: {topic}" ) ;
192+ let token = pubsub_token
193+ . as_ref ( )
194+ . ok_or_else ( || GwsError :: Auth ( "Token unavailable in non-dry-run mode. This indicates a bug." . to_string ( ) ) ) ?;
145195 let resp = client
146196 . put ( format ! ( "{PUBSUB_API_BASE}/{topic}" ) )
147- . bearer_auth ( & pubsub_token )
197+ . bearer_auth ( token )
148198 . header ( "Content-Type" , "application/json" )
149199 . body ( "{}" )
150200 . send ( )
@@ -169,7 +219,7 @@ pub(super) async fn handle_subscribe(
169219 } ) ;
170220 let resp = client
171221 . put ( format ! ( "{PUBSUB_API_BASE}/{sub}" ) )
172- . bearer_auth ( & pubsub_token )
222+ . bearer_auth ( token )
173223 . header ( "Content-Type" , "application/json" )
174224 . json ( & sub_body)
175225 . send ( )
0 commit comments