@@ -28,6 +28,14 @@ class _TypeCheckers {
2828 TypeChecker .fromRuntime (ff.FirestoreNamespace );
2929 static final databaseNamespace =
3030 TypeChecker .fromRuntime (ff.DatabaseNamespace );
31+ static final alertsNamespace = TypeChecker .fromRuntime (ff.AlertsNamespace );
32+ static final crashlyticsNamespace =
33+ TypeChecker .fromRuntime (ff.CrashlyticsNamespace );
34+ static final billingNamespace = TypeChecker .fromRuntime (ff.BillingNamespace );
35+ static final appDistributionNamespace =
36+ TypeChecker .fromRuntime (ff.AppDistributionNamespace );
37+ static final performanceNamespace =
38+ TypeChecker .fromRuntime (ff.PerformanceNamespace );
3139}
3240
3341/// The main builder that generates functions.yaml.
@@ -144,6 +152,33 @@ class _FirebaseFunctionsVisitor extends RecursiveAstVisitor<void> {
144152 }
145153 }
146154
155+ // Check for Alerts function declarations (main namespace)
156+ if (target != null && _isAlertsNamespace (target)) {
157+ if (methodName == 'onAlertPublished' ) {
158+ _extractGenericAlertFunction (node);
159+ }
160+ }
161+
162+ // Check for Crashlytics alert declarations
163+ if (target != null && _isCrashlyticsNamespace (target)) {
164+ _extractCrashlyticsAlertFunction (node, methodName);
165+ }
166+
167+ // Check for Billing alert declarations
168+ if (target != null && _isBillingNamespace (target)) {
169+ _extractBillingAlertFunction (node, methodName);
170+ }
171+
172+ // Check for App Distribution alert declarations
173+ if (target != null && _isAppDistributionNamespace (target)) {
174+ _extractAppDistributionAlertFunction (node, methodName);
175+ }
176+
177+ // Check for Performance alert declarations
178+ if (target != null && _isPerformanceNamespace (target)) {
179+ _extractPerformanceAlertFunction (node, methodName);
180+ }
181+
147182 // Check for parameter definitions (top-level function calls with no target)
148183 if (target == null && _isParamDefinition (methodName)) {
149184 _extractParameterFromMethod (node, methodName);
@@ -255,6 +290,41 @@ class _FirebaseFunctionsVisitor extends RecursiveAstVisitor<void> {
255290 return _TypeCheckers .databaseNamespace.isExactlyType (staticType);
256291 }
257292
293+ /// Checks if the target is firebase.alerts.
294+ bool _isAlertsNamespace (Expression target) {
295+ final staticType = target.staticType;
296+ if (staticType == null ) return false ;
297+ return _TypeCheckers .alertsNamespace.isExactlyType (staticType);
298+ }
299+
300+ /// Checks if the target is firebase.alerts.crashlytics.
301+ bool _isCrashlyticsNamespace (Expression target) {
302+ final staticType = target.staticType;
303+ if (staticType == null ) return false ;
304+ return _TypeCheckers .crashlyticsNamespace.isExactlyType (staticType);
305+ }
306+
307+ /// Checks if the target is firebase.alerts.billing.
308+ bool _isBillingNamespace (Expression target) {
309+ final staticType = target.staticType;
310+ if (staticType == null ) return false ;
311+ return _TypeCheckers .billingNamespace.isExactlyType (staticType);
312+ }
313+
314+ /// Checks if the target is firebase.alerts.appDistribution.
315+ bool _isAppDistributionNamespace (Expression target) {
316+ final staticType = target.staticType;
317+ if (staticType == null ) return false ;
318+ return _TypeCheckers .appDistributionNamespace.isExactlyType (staticType);
319+ }
320+
321+ /// Checks if the target is firebase.alerts.performance.
322+ bool _isPerformanceNamespace (Expression target) {
323+ final staticType = target.staticType;
324+ if (staticType == null ) return false ;
325+ return _TypeCheckers .performanceNamespace.isExactlyType (staticType);
326+ }
327+
258328 /// Checks if this is a parameter definition function.
259329 bool _isParamDefinition (String name) =>
260330 name == 'defineString' ||
@@ -393,6 +463,152 @@ class _FirebaseFunctionsVisitor extends RecursiveAstVisitor<void> {
393463 );
394464 }
395465
466+ /// Extracts a generic alert function declaration (onAlertPublished).
467+ void _extractGenericAlertFunction (MethodInvocation node) {
468+ // Extract alertType from named argument
469+ final alertTypeArg = _findNamedArg (node, 'alertType' );
470+ if (alertTypeArg == null ) return ;
471+
472+ final alertTypeValue = _extractAlertTypeValue (alertTypeArg);
473+ if (alertTypeValue == null ) return ;
474+
475+ // Extract appId from options if present
476+ final optionsArg = _findNamedArg (node, 'options' );
477+ String ? appId;
478+
479+ if (optionsArg is InstanceCreationExpression ) {
480+ appId = _extractStringField (optionsArg, 'appId' );
481+ }
482+
483+ // Generate function name
484+ final sanitizedAlertType =
485+ alertTypeValue.replaceAll ('.' , '_' ).replaceAll ('-' , '' );
486+ final functionName = 'onAlertPublished_$sanitizedAlertType ' ;
487+
488+ endpoints[functionName] = _EndpointSpec (
489+ name: functionName,
490+ type: 'alert' ,
491+ alertType: alertTypeValue,
492+ appId: appId,
493+ options: optionsArg is InstanceCreationExpression ? optionsArg : null ,
494+ variableToParamName: _variableToParamName,
495+ );
496+ }
497+
498+ /// Extracts Crashlytics alert function declarations.
499+ void _extractCrashlyticsAlertFunction (
500+ MethodInvocation node,
501+ String methodName,
502+ ) {
503+ // Map method names to alert types
504+ final alertType = switch (methodName) {
505+ 'onNewFatalIssuePublished' => 'crashlytics.newFatalIssue' ,
506+ 'onNewNonfatalIssuePublished' => 'crashlytics.newNonfatalIssue' ,
507+ 'onRegressionAlertPublished' => 'crashlytics.regression' ,
508+ 'onStabilityDigestPublished' => 'crashlytics.stabilityDigest' ,
509+ 'onVelocityAlertPublished' => 'crashlytics.velocity' ,
510+ 'onNewAnrIssuePublished' => 'crashlytics.newAnrIssue' ,
511+ _ => null ,
512+ };
513+
514+ if (alertType == null ) return ;
515+
516+ _extractAlertEndpoint (node, alertType);
517+ }
518+
519+ /// Extracts Billing alert function declarations.
520+ void _extractBillingAlertFunction (MethodInvocation node, String methodName) {
521+ final alertType = switch (methodName) {
522+ 'onPlanUpdatePublished' => 'billing.planUpdate' ,
523+ 'onPlanAutomatedUpdatePublished' => 'billing.planAutomatedUpdate' ,
524+ _ => null ,
525+ };
526+
527+ if (alertType == null ) return ;
528+
529+ _extractAlertEndpoint (node, alertType);
530+ }
531+
532+ /// Extracts App Distribution alert function declarations.
533+ void _extractAppDistributionAlertFunction (
534+ MethodInvocation node,
535+ String methodName,
536+ ) {
537+ final alertType = switch (methodName) {
538+ 'onNewTesterIosDevicePublished' => 'appDistribution.newTesterIosDevice' ,
539+ 'onInAppFeedbackPublished' => 'appDistribution.inAppFeedback' ,
540+ _ => null ,
541+ };
542+
543+ if (alertType == null ) return ;
544+
545+ _extractAlertEndpoint (node, alertType);
546+ }
547+
548+ /// Extracts Performance alert function declarations.
549+ void _extractPerformanceAlertFunction (
550+ MethodInvocation node,
551+ String methodName,
552+ ) {
553+ final alertType = switch (methodName) {
554+ 'onThresholdAlertPublished' => 'performance.threshold' ,
555+ _ => null ,
556+ };
557+
558+ if (alertType == null ) return ;
559+
560+ _extractAlertEndpoint (node, alertType);
561+ }
562+
563+ /// Helper to extract alert endpoint from a method invocation.
564+ void _extractAlertEndpoint (MethodInvocation node, String alertType) {
565+ // Extract appId from options if present
566+ final optionsArg = _findNamedArg (node, 'options' );
567+ String ? appId;
568+
569+ if (optionsArg is InstanceCreationExpression ) {
570+ appId = _extractStringField (optionsArg, 'appId' );
571+ }
572+
573+ // Generate function name
574+ final sanitizedAlertType =
575+ alertType.replaceAll ('.' , '_' ).replaceAll ('-' , '' );
576+ final functionName = 'onAlertPublished_$sanitizedAlertType ' ;
577+
578+ endpoints[functionName] = _EndpointSpec (
579+ name: functionName,
580+ type: 'alert' ,
581+ alertType: alertType,
582+ appId: appId,
583+ options: optionsArg is InstanceCreationExpression ? optionsArg : null ,
584+ variableToParamName: _variableToParamName,
585+ );
586+ }
587+
588+ /// Extracts alert type value from an expression.
589+ String ? _extractAlertTypeValue (Expression expression) {
590+ if (expression is InstanceCreationExpression ) {
591+ // Extract from constructor: const CrashlyticsNewFatalIssue()
592+ final typeName = expression.constructorName.type.name2.lexeme;
593+ return switch (typeName) {
594+ 'CrashlyticsNewFatalIssue' => 'crashlytics.newFatalIssue' ,
595+ 'CrashlyticsNewNonfatalIssue' => 'crashlytics.newNonfatalIssue' ,
596+ 'CrashlyticsRegression' => 'crashlytics.regression' ,
597+ 'CrashlyticsStabilityDigest' => 'crashlytics.stabilityDigest' ,
598+ 'CrashlyticsVelocity' => 'crashlytics.velocity' ,
599+ 'CrashlyticsNewAnrIssue' => 'crashlytics.newAnrIssue' ,
600+ 'BillingPlanUpdate' => 'billing.planUpdate' ,
601+ 'BillingPlanAutomatedUpdate' => 'billing.planAutomatedUpdate' ,
602+ 'AppDistributionNewTesterIosDevice' =>
603+ 'appDistribution.newTesterIosDevice' ,
604+ 'AppDistributionInAppFeedback' => 'appDistribution.inAppFeedback' ,
605+ 'PerformanceThreshold' => 'performance.threshold' ,
606+ _ => null ,
607+ };
608+ }
609+ return null ;
610+ }
611+
396612 /// Extracts a parameter definition from FunctionExpressionInvocation.
397613 void _extractParameter (
398614 FunctionExpressionInvocation node,
@@ -598,11 +814,14 @@ class _EndpointSpec {
598814 this .databaseEventType,
599815 this .refPath,
600816 this .instance,
817+ this .alertType,
818+ this .appId,
601819 this .options,
602820 this .variableToParamName = const {},
603821 });
604822 final String name;
605- final String type; // 'https', 'callable', 'pubsub', 'firestore', 'database'
823+ // 'https', 'callable', 'pubsub', 'firestore', 'database', 'alert'
824+ final String type;
606825 final String ? topic; // For Pub/Sub functions
607826 final String ? firestoreEventType; // For Firestore: onDocumentCreated, etc.
608827 final String ? documentPath; // For Firestore: users/{userId}
@@ -611,6 +830,8 @@ class _EndpointSpec {
611830 final String ? databaseEventType; // For Database: onValueCreated, etc.
612831 final String ? refPath; // For Database: /users/{userId}
613832 final String ? instance; // For Database: database instance or '*'
833+ final String ? alertType; // For Alerts: crashlytics.newFatalIssue, etc.
834+ final String ? appId; // For Alerts: optional app ID filter
614835 final InstanceCreationExpression ? options;
615836 final Map <String , String > variableToParamName;
616837
@@ -1345,6 +1566,17 @@ String _generateYaml(
13451566 buffer.writeln (' ref: "$normalizedRef "' );
13461567 buffer.writeln (' instance: "${endpoint .instance ?? '*' }"' );
13471568
1569+ buffer.writeln (' retry: false' );
1570+ } else if (endpoint.type == 'alert' && endpoint.alertType != null ) {
1571+ buffer.writeln (' eventTrigger:' );
1572+ buffer.writeln (
1573+ ' eventType: "google.firebase.firebasealerts.alerts.v1.published"' ,
1574+ );
1575+ buffer.writeln (' eventFilters:' );
1576+ buffer.writeln (' alerttype: "${endpoint .alertType }"' );
1577+ if (endpoint.appId != null ) {
1578+ buffer.writeln (' appid: "${endpoint .appId }"' );
1579+ }
13481580 buffer.writeln (' retry: false' );
13491581 }
13501582 }
0 commit comments