exec method

  1. @override
Future<void> exec(
  1. ExecContext context
)
override

Run command.

The contents of katana.yaml and the arguments of the command are passed to context.

コマンドを実行します。

contextにkatana.yamlの内容やコマンドの引数が渡されます。

Implementation

@override
Future<void> exec(ExecContext context) async {
  final args = context.args.skip(2).toList();
  String? deviceFilter;
  var last = "1h";
  String? bundleId;
  for (var i = 0; i < args.length; i++) {
    final argument = args[i];
    if (argument == "--device") {
      if (i + 1 >= args.length) {
        error("Invalid argument: --device requires one value.");
        return;
      }
      deviceFilter = args[++i];
    } else if (argument.startsWith("--device=")) {
      deviceFilter = argument.substring("--device=".length);
    } else if (argument == "--last") {
      if (i + 1 >= args.length) {
        error("Invalid argument: --last requires one value.");
        return;
      }
      last = args[++i];
    } else if (argument.startsWith("--last=")) {
      last = argument.substring("--last=".length);
    } else if (argument == "--bundle-id") {
      if (i + 1 >= args.length) {
        error("Invalid argument: --bundle-id requires one value.");
        return;
      }
      bundleId = args[++i];
    } else if (argument.startsWith("--bundle-id=")) {
      bundleId = argument.substring("--bundle-id=".length);
    } else {
      error(
        "Unknown argument for `katana debug app_check_token`: $argument",
      );
      return;
    }
  }

  final bin = context.yaml.getAsMap("bin");
  final adb = bin.get("adb", "adb");

  label("Enumerate connected devices.");
  final androidDevices = await listAndroidDevices(adb: adb);
  final iosSimulators = await listBootedIosSimulators();
  final all = [...androidDevices, ...iosSimulators];
  if (all.isEmpty) {
    error(
      "No connected Android devices or booted iOS simulators were found. Boot the target device and run the app first.",
    );
    return;
  }
  final targets = deviceFilter == null
      ? all
      : all.where((d) => d.id == deviceFilter).toList();
  if (targets.isEmpty) {
    error(
      "Device `$deviceFilter` was not found in connected devices: ${all.map((d) => d.id).join(", ")}",
    );
    return;
  }

  bundleId ??= resolveIosBundleId();
  final store = AppCheckDebugTokenStore.defaultFile();
  final captured = <AppCheckDebugTokenDevice>[];
  final unregistered = <AppCheckDebugTokenDevice>[];
  final missed = <AppCheckDebugTokenDevice>[];

  for (final device in targets) {
    label(
      "Capture debug token from ${device.platform}: ${device.id} (${device.name}).",
    );
    String? token;
    if (device.platform == "android") {
      token = await extractAndroidDebugToken(device.id, adb: adb);
    } else if (device.platform == "ios_simulator") {
      final defaults = bundleId == null
          ? null
          : await readIosSimulatorDebugTokenFromDefaults(
              device.id,
              bundleId: bundleId,
            );
      if (defaults != null) {
        token = defaults.token;
        if (!defaults.registered) {
          unregistered.add(device);
        }
        // ignore: avoid_print
        print(
          "  Read from UserDefaults of `$bundleId` (${defaults.registered ? "registered in Firebase" : "NOT registered in Firebase yet"}).",
        );
      } else {
        token = await extractIosSimulatorDebugToken(device.id, last: last);
      }
    }
    if (token == null) {
      missed.add(device);
      continue;
    }
    store.upsert(
      device: device,
      token: token,
      updatedAt: DateTime.now(),
    );
    captured.add(device);
    // ignore: avoid_print
    print("  -> ${device.id}: $token");
  }

  label("Update .gitignore.");
  _updateGitignore(context);

  // ignore: avoid_print
  print("");
  if (captured.isNotEmpty) {
    // ignore: avoid_print
    print(
      "Captured ${captured.length} debug token(s) into `.app_check_debug_tokens.json`.",
    );
    // ignore: avoid_print
    print(
      "Register each token in Firebase Console: Project settings -> App Check -> (App) -> Manage debug tokens.",
    );
  }
  if (unregistered.isNotEmpty) {
    // ignore: avoid_print
    print("");
    // ignore: avoid_print
    print(
      "The App Check SDK has not recorded these iOS simulator tokens as registered in Firebase:",
    );
    for (final device in unregistered) {
      // ignore: avoid_print
      print("  - ${device.platform}: ${device.id} (${device.name})");
    }
  }
  if (missed.isNotEmpty) {
    // ignore: avoid_print
    print("");
    // ignore: avoid_print
    print("Could not capture debug tokens from:");
    for (final device in missed) {
      // ignore: avoid_print
      print("  - ${device.platform}: ${device.id} (${device.name})");
    }
    // ignore: avoid_print
    print(
      "Restart the app so the debug token line is emitted, then re-run this command.",
    );
    // ignore: avoid_print
    print(
      "iOS note: tokens are read from the app's UserDefaults${bundleId == null ? " (bundle ID could not be resolved; pass --bundle-id)" : " of `$bundleId`"}. Launch the debug build once so App Check generates a token, then re-run this command.",
    );
  }
}