run static method

Future<SankofaBootstrapResult> run({
  1. SankofaBootstrapOptions options = const SankofaBootstrapOptions(),
})

Run the bootstrap. Idempotent — calling twice is harmless (the second call is a no-op because Sankofa.instance is already initialized).

Implementation

static Future<SankofaBootstrapResult> run({
  SankofaBootstrapOptions options = const SankofaBootstrapOptions(),
}) async {
  final config = await _readSankofaYaml(options.yamlAssetPath);
  final apiKey = config['api_key']?.trim() ?? _kFallbackApiKey;
  final appId = config['app_id']?.trim() ?? _kFallbackAppId;
  final endpoint = config['base_url']?.trim() ?? options.fallbackEndpoint;
  final engineVersion = config['engine_version']?.trim() ?? '';
  // signing_pubkey is written by `sankofa keys generate` and never
  // edited by hand — customer doesn't need to know it exists.
  final signingPubkey = config['signing_pubkey']?.trim();

  if (apiKey.isEmpty && kDebugMode) {
    debugPrint(
      '[Sankofa.bootstrap] WARNING: api_key missing in '
      '${options.yamlAssetPath} — SDK will initialize with empty apiKey '
      'and the server will refuse all calls.',
    );
  }

  // Auto-enable Deploy when a loader was provided.
  final enableDeploy = options.enableDeploy ?? (options.loader != null);

  // If sankofa.yaml carried an engine_version + signing_pubkey, forward
  // them as deploy defaults so customer code never has to repeat
  // engine identity / signing keys on every call. Both fields are
  // written by the Sankofa CLI (`sankofa init` / `sankofa keys
  // generate`) — customers do not edit them by hand.
  SankofaDeployOptions effectiveDeployOptions = options.deployOptions;
  if (engineVersion.isNotEmpty &&
      (effectiveDeployOptions.engineVersion == null ||
          effectiveDeployOptions.engineVersion!.isEmpty)) {
    effectiveDeployOptions =
        effectiveDeployOptions.copyWith(engineVersion: engineVersion);
  }
  if (signingPubkey != null &&
      signingPubkey.isNotEmpty &&
      (effectiveDeployOptions.signingPubkeyB64 == null ||
          effectiveDeployOptions.signingPubkeyB64!.isEmpty)) {
    effectiveDeployOptions =
        effectiveDeployOptions.copyWith(signingPubkeyB64: signingPubkey);
  }

  await Sankofa.instance.init(
    apiKey: apiKey,
    endpoint: endpoint,
    debug: options.debug,
    enableDeploy: enableDeploy,
    deployOptions: effectiveDeployOptions,
    // Respect overrides; otherwise leave init() defaults alone.
    enableCatch: options.enableCatch ?? true,
    enableAnalytics: options.enableAnalytics ?? true,
  );

  KbcPatchResult? staged;
  if (enableDeploy && options.loader != null) {
    try {
      staged = await Sankofa.instance.deploy?.tryApplyStagedKbcPatch(
        loader: options.loader!,
      );
    } catch (e, st) {
      if (kDebugMode) {
        debugPrint('[Sankofa.bootstrap] tryApplyStagedKbcPatch threw: $e\n$st');
      }
    }

    if (options.scheduleNotifyOnFirstFrame && staged != null) {
      // Schedule notify after the first frame paints. The deploy
      // module's auto-confirm timer is a safety net; this is the
      // explicit path that lets the host signal "rendering worked"
      // without writing the addPostFrameCallback boilerplate.
      SchedulerBinding.instance.addPostFrameCallback((_) {
        // Schedule on the next microtask so we don't block frame work.
        Future<void>.delayed(Duration.zero, () async {
          try {
            await Sankofa.instance.deploy?.notifyKbcPatchReady();
          } catch (e) {
            if (kDebugMode) {
              debugPrint('[Sankofa.bootstrap] notifyKbcPatchReady failed: $e');
            }
          }
        });
      });
    }
  }

  return SankofaBootstrapResult(
    apiKey: apiKey,
    endpoint: endpoint,
    appId: appId,
    engineVersion: engineVersion,
    didApplyStagedPatch: staged != null,
    stagedPatch: staged,
  );
}