validateLocal method

Future<void> validateLocal(
  1. ExecContext context
)

外部接続や依存変更の前にローカル適用の前提を検証します。

Implementation

Future<void> validateLocal(ExecContext context) async {
  final firebase = context.yaml.getAsMap("firebase");
  final flavor = context.flavorContext?.flavor.name ?? "prod";
  final projectId = firebase.get("project_id", "");
  final android =
      File("android/app/src/katanaFirebase/$flavor/google-services.json");
  final ios = File("ios/Runner/Firebase/$flavor/GoogleService-Info.plist");
  final macos =
      File("macos/Runner/Firebase/$flavor/GoogleService-Info.plist");
  final options = File("lib/katana/firebase/$flavor/firebase_options.dart");
  for (final file in [android, ios, macos, options]) {
    if (!file.existsSync() ||
        file.readAsStringSync().contains("KATANA FIREBASE STUB")) {
      throw StateError("--local に必要な既存の Firebase 設定がありません: ${file.path}");
    }
  }
  _validateFirebaseProject(
      projectId: projectId,
      googleServicesJson: android,
      applePlists: [ios, macos]);
  final jsonFile = File("firebase/firebase.json");
  final config = jsonFile.existsSync()
      ? jsonDecode(jsonFile.readAsStringSync()) as Map
      : {};
  for (final service in [
    "firestore",
    "dataconnect",
    "storage",
    "hosting",
    "functions"
  ]) {
    if (firebase.getAsMap(service).get("enable", false) &&
        !config.containsKey(service)) {
      throw StateError("--local に必要な Firebase $service の初期設定がありません。");
    }
  }
  final pubspec = loadYaml(File("pubspec.yaml").readAsStringSync()) as Map;
  for (final section in ["dependencies", "dev_dependencies"]) {
    validateLocalFlutterDependencies(
        (pubspec[section] as Map? ?? {}).keys.cast<String>().toList(),
        development: section == "dev_dependencies");
  }
  if (firebase.getAsMap("functions").get("enable", false)) {
    final packageFile = File("firebase/functions/package.json");
    final lockFile = File("firebase/functions/package-lock.json");
    if (!packageFile.existsSync() || !lockFile.existsSync()) {
      throw StateError(
          "--local に必要な Functions の package.json / package-lock.json がありません。");
    }
    final package = jsonDecode(packageFile.readAsStringSync()) as Map;
    final lock = jsonDecode(lockFile.readAsStringSync()) as Map;
    final lockedRoot = (lock["packages"] as Map?)?[""] as Map?;
    if (lockedRoot == null) {
      throw StateError("--local は root package 情報を持つ既存の npm lock を必要とします。");
    }
    for (final section in [
      "dependencies",
      "devDependencies",
      "optionalDependencies"
    ]) {
      final declared = package[section] as Map? ?? {};
      final locked = lockedRoot[section] as Map? ?? {};
      if (declared.length != locked.length ||
          declared.entries.any((e) => locked[e.key] != e.value)) {
        throw StateError(
            "Functions の $section と package-lock.json が不整合です。依存変更を承認・解決してから再実行してください。");
      }
    }
    for (final name in [
      "@mathrunet/masamune",
      "jest",
      "ts-jest",
      "@types/jest",
      "typescript",
      "eslint"
    ]) {
      if (!(package["dependencies"] as Map? ?? {}).containsKey(name) &&
          !(package["devDependencies"] as Map? ?? {}).containsKey(name)) {
        throw StateError("--local に必要な Functions 依存がありません: $name");
      }
    }
    for (final name in ["eslint", "ts-jest"]) {
      if (!File(
              "firebase/functions/node_modules/.bin/$name${Platform.isWindows ? ".cmd" : ""}")
          .existsSync()) {
        throw StateError("--local に必要なローカル実行ファイルがありません: $name");
      }
    }
    await command(
        "Functions の導入済み依存を検証",
        [
          context.yaml.getAsMap("bin").get("npm", "npm"),
          "ls",
          "--offline",
          "--all"
        ],
        workingDirectory: "firebase/functions",
        catchError: true,
        failOnStderr: false);
  }
}