runEnableFirebaseApis method
Enable the Firebase APIs every project needs (firebase, firestore, firebasestorage, firebasehosting, identitytoolkit, serviceusage).
This step is idempotent — re-running on a project where the APIs are already enabled is a no-op (gcloud short-circuits on the server side).
When interactive is true, this method first walks the user through
granting the IAM roles the active gcloud principal needs (see
_ensureServiceAccountIamGate). If the gate cannot be satisfied
(probe still fails after retries, or the user gives up), the step
returns a failed result without attempting the API enablement,
so we don't spam the user with six identical PERMISSION_DENIED
warnings before bailing out.
Implementation
Future<SetupStepResult> runEnableFirebaseApis({
bool interactive = true,
}) async {
final bool gateOk = await _ensureServiceAccountIamGate(
interactive: interactive,
);
if (!gateOk) {
final String? projectId = config.firebaseProjectId;
final String url = projectId == null
? ''
: ' (open ${_iamPageUrl(projectId)} and grant the missing roles)';
return SetupStepResult.failed(
WizardSubStep.enableFirebaseApis,
message:
'IAM gate not satisfied — '
'the active gcloud principal cannot enable Google APIs$url',
fixHint: _fixHintFor(WizardSubStep.enableFirebaseApis),
);
}
final List<String> failed = await firebase.enableFirebaseCoreApis();
if (failed.isEmpty) {
return SetupStepResult.success(
WizardSubStep.enableFirebaseApis,
message: 'Enabled core Firebase APIs',
);
}
final String firstApi = failed.first;
final String? projectId = config.firebaseProjectId;
final String url = projectId == null
? ''
: ' (open ${FirebaseInitializer.apiEnableUrl(projectId, firstApi)})';
return SetupStepResult.failed(
WizardSubStep.enableFirebaseApis,
message: 'Failed to enable: ${failed.join(', ')}$url',
fixHint: _fixHintFor(WizardSubStep.enableFirebaseApis),
);
}