enableFirebaseCoreApis method

Future<List<String>> enableFirebaseCoreApis()

Enable the core Firebase APIs needed by every Firebase-enabled project.

We enable these BEFORE running any gcloud firestore, gcloud storage, or firebase apps:list calls, because the most common cause of those commands failing on a fresh project is that the underlying API has never been enabled. Returns the list of APIs that failed to enable (empty when everything succeeded).

After enabling, this method waits (probes) for the Firebase Management API to be queryable from the firebase CLI. This closes the propagation gap between gcloud services enable returning success and the API actually answering firebase apps:list calls — without this gate, the next step (Configure Firebase client wiring) will hit SERVICE_DISABLED on the very first call.

All these APIs are available on Spark — Blaze is only required for actually using the bucket / database beyond the free quota, not for enabling the API itself.

Implementation

Future<List<String>> enableFirebaseCoreApis() async {
  if (config.firebaseProjectId == null) {
    error('Firebase project ID not set');
    return const <String>['*'];
  }

  info('Enabling core Firebase APIs...');

  const List<String> apis = <String>[
    'firebase.googleapis.com', // Firebase Management API (apps:list, etc.)
    'firestore.googleapis.com', // Cloud Firestore
    'firebasestorage.googleapis.com', // Firebase Storage
    'firebasehosting.googleapis.com', // Firebase Hosting
    'identitytoolkit.googleapis.com', // Firebase Auth
    'serviceusage.googleapis.com', // self-referential, needed for batch enable
  ];

  final List<String> failed = <String>[];
  for (final String api in apis) {
    verbose('  Enabling $api...');
    final ProcessResult result = await _runner.run('gcloud', <String>[
      'services',
      'enable',
      api,
      '--project',
      config.firebaseProjectId!,
    ], environment: _authEnvironment);

    if (!result.success) {
      final String detail = _stripAnsi(result.stderr).trim();
      warn(
        'Failed to enable $api: ${detail.isEmpty ? 'unknown error' : detail}',
      );
      failed.add(api);
    }
  }

  // Even when `gcloud services enable` returns success, the API can take
  // 10-60s to propagate before `firebase apps:list` actually works.
  // Wait here so step 5.5 (Configure Firebase client wiring) doesn't hit
  // SERVICE_DISABLED on its very first call.
  if (!failed.contains('firebase.googleapis.com')) {
    info('Waiting for Firebase Management API to become queryable...');
    final bool ready = await _waitForFirebaseManagementApi();
    if (!ready) {
      warn(
        'Firebase Management API enabled but did not become queryable '
        'within the propagation window. Subsequent steps will retry on '
        'demand, but you may want to wait a minute and re-run if they '
        'fail.',
      );
      // We don't add it back to `failed` — the API IS enabled, just not
      // visible yet. The auto-recovery loop in `_runFirebaseWithRecovery`
      // will continue retrying as needed.
    }
  }

  if (failed.isEmpty) {
    success('All core Firebase APIs are enabled.');
  }
  return failed;
}