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.

コマンドを実行します。

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

Implementation

@override
Future<void> exec(ExecContext context) async {
  final bin = context.yaml.getAsMap("bin");
  final flutter = bin.get("flutter", "flutter");
  final dart = bin.get("dart", "dart");
  final packageName = context.args.get(1, "");
  if (packageName.isEmpty) {
    error(
      "Please provide the name of the package.\r\nパッケージ名を記載してください。\r\n\r\nkatana create [package name]",
    );
    return;
  }
  final projectName = packageName.split(".").lastOrNull;
  final domain = packageName
      .split(".")
      .sublist(0, packageName.split(".").length - 1)
      .join(".");
  if (projectName.isEmpty || domain.isEmpty) {
    error(
      "The format of the package name should be specified in the following format.\r\nパッケージ名の形式は下記の形式で指定してください。\r\n\r\n[Domain].[ProjectName]\r\ne.g. net.mathru.website",
    );
    return;
  }
  await command(
    "Create a Flutter project.",
    [
      flutter,
      "create",
      "--org",
      domain,
      "--project-name",
      projectName!,
      ".",
    ],
  );
  await command(
    "Import packages.",
    [
      flutter,
      "pub",
      "add",
      ...importPackages,
    ],
  );
  await command(
    "Import dev packages.",
    [
      flutter,
      "pub",
      "add",
      "--dev",
      ...importDevPackages,
      "import_sorter",
    ],
  );
  label("Replace lib/main.dart");
  await MainCliCode().generateDartCode("lib/main", "main");
  label("Replace lib/theme.dart");
  await const MainThemeCliCode().generateDartCode("lib/theme", "theme");
  label("Replace lib/router.dart");
  await const MainRouterCliCode().generateDartCode("lib/router", "router");
  label("Replace lib/localize.dart");
  await const MainLocalizeCliCode()
      .generateDartCode("lib/localize", "localize");
  label("Replace lib/adapter.dart");
  await const MainAdapterCliCode().generateDartCode("lib/adapter", "adapter");
  label("Replace lib/config.dart");
  await const MainConfigCliCode().generateDartCode("lib/config", "config");
  label("Create home.dart");
  await const HomePageCliCode().generateDartCode("lib/pages/home", "home");
  label("Create counter.dart");
  await const CounterModelCliCode()
      .generateDartCode("lib/models/counter", "counter");
  label("Generate file for VSCode");
  for (final file in otherFiles.entries) {
    await file.value.generateFile(file.key);
  }
  label("Create a katana.yaml");
  await KatanaCliCode(false).generateFile("katana.yaml");
  label("Replace LICENSE");
  await const LicenseCliCode().generateFile("LICENSE");
  label("Create a katana_secrets.yaml");
  await const KatanaSecretsCliCode().generateFile("katana_secrets.yaml");
  label("Create a pubspec_overrides.yaml");
  await const PubspecOverridesCliCode()
      .generateFile("pubspec_overrides.yaml");
  label("Create a build.yaml");
  await const BuildCliCode().generateFile("build.yaml");
  label("Edit a analysis_options.yaml");
  await const AnalysisOptionsCliCode().generateFile("analysis_options.yaml");
  label("Edit a widget_test.dart");
  await const WidgetTestCliCode().generateFile("widget_test.dart");
  label("Create a loader.css");
  await const LoaderCssCliCode().generateFile("loader.css");
  label("Create a flutter_bootstrap.js");
  await const BootstrapJsCliCode().generateFile("flutter_bootstrap.js");
  label("Edit as index.html");
  final indexHtmlFile = File("web/index.html");
  final htmlDocument = parse(await indexHtmlFile.readAsString());
  final body = htmlDocument.body;
  final head = htmlDocument.head;
  if (body != null) {
    if (!body.children.any((element) =>
        element.localName == "div" && element.classes.contains("loading"))) {
      body.children.insertFirst(
        Element.tag("div")
          ..classes.add("loading")
          ..children.add(
            Element.tag("div")
              ..children.addAll(
                [
                  Element.tag("img")
                    ..attributes["src"] = "icons/Icon-192.png"
                    ..classes.add("logo"),
                  Element.tag("div")..classes.add("loader-bar")
                ],
              ),
          ),
      );
    }
  }
  if (head != null) {
    if (!head.children.any((element) =>
        element.localName == "link" &&
        element.attributes["rel"] == "stylesheet" &&
        element.attributes["href"] == "loader.css")) {
      head.children.add(Element.tag("link")
        ..attributes["rel"] = "stylesheet"
        ..attributes["href"] = "loader.css"
        ..attributes["type"] = "text/css"
        ..attributes["media"] = "all");
    }
    final icon = head.children.firstWhereOrNull((item) =>
        item.localName == "link" && item.attributes["rel"] == "icon");
    if (icon == null) {
      head.children.add(Element.tag("link")
        ..attributes["rel"] = "icon"
        ..attributes["href"] = "favicon.ico");
    } else if (icon.attributes["href"] != "favicon.ico") {
      icon.attributes["href"] = "favicon.ico";
      icon.attributes.remove("type");
    }
  }
  await indexHtmlFile.writeAsString(
    htmlDocument.outerHtml.replaceAll(
      "<script src=\"flutter_bootstrap.js\" async=\"\"></script>",
      "<script>{{flutter_bootstrap_js}}</script>",
    ),
  );
  label("Create a favicon.ico");
  final iconFile = File("web/icons/Icon-512.png");
  final iconImage = decodeImage(iconFile.readAsBytesSync())!;
  final icoPngFile = File("web/favicon.png");
  if (icoPngFile.existsSync()) {
    await icoPngFile.delete();
  }
  final icoFile = File("web/favicon.ico");
  if (icoFile.existsSync()) {
    await icoFile.delete();
  }
  final ico = IcoEncoder();
  await icoFile.writeAsBytes(
    ico.encodeImages(_faviconSize.map((e) {
      return copyResize(
        iconImage,
        height: e,
        width: e,
        interpolation: Interpolation.average,
      );
    }).toList()),
  );
  label("Create a feature.png");
  final featurePngFile = File("web/feature.png");
  if (!featurePngFile.existsSync()) {
    await featurePngFile.writeAsBytes(
      encodePng(
        copyResize(
          iconImage,
          height: 512,
          width: 512,
          interpolation: Interpolation.average,
        ),
      ),
    );
  }
  label("Create a assets directory");
  final assetsDirectory = Directory("assets");
  if (!assetsDirectory.existsSync()) {
    await assetsDirectory.create();
  }
  label("Edit AndroidManifest.xml.");
  await AndroidManifestPermissionType.internet.enablePermission();
  await AndroidManifestQueryType.openLinkHttps.enableQuery();
  await AndroidManifestQueryType.dialTel.enableQuery();
  await AndroidManifestQueryType.sendEmail.enableQuery();
  await AndroidManifestQueryType.sendAny.enableQuery();
  label("Edit DebugProfile.entitlements.");
  final debugEntitlements = File("macos/Runner/DebugProfile.entitlements");
  if (debugEntitlements.existsSync()) {
    final document =
        XmlDocument.parse(await debugEntitlements.readAsString());
    final dict = document.findAllElements("dict").firstOrNull;
    if (dict == null) {
      throw Exception(
        "Could not find `dict` element in `macos/Runner/DebugProfile.entitlements`. File is corrupt.",
      );
    }
    final node = dict.children.firstWhereOrNull((p0) {
      return p0 is XmlElement &&
          p0.name.toString() == "key" &&
          p0.innerText == "com.apple.security.network.client";
    });
    if (node == null) {
      dict.children.addAll(
        [
          XmlElement(
            XmlName("key"),
            [],
            [XmlText("com.apple.security.network.client")],
          ),
          XmlElement(
            XmlName("true"),
            [],
            [],
          ),
        ],
      );
    }
    await debugEntitlements.writeAsString(
      document.toXmlString(pretty: true, indent: "\t", newLine: "\n"),
    );
  }
  label("Replace pubspec.yaml");
  final pubspecFile = File("pubspec.yaml");
  final pubspec = await pubspecFile.readAsString();
  await pubspecFile.writeAsString(
    pubspec.replaceAll(
      RegExp(
        r"# assets:[\s\S]+#   - images/a_dot_burr.jpeg[\s\S]+#   - images/a_dot_ham.jpeg",
      ),
      "assets:\n    - assets/\n",
    ),
  );
  label("Rewrite `.gitignore`.");
  final gitignore = File(".gitignore");
  if (!gitignore.existsSync()) {
    error("Cannot find `.gitignore`. Project is broken.");
    return;
  }
  final gitignores = await gitignore.readAsLines();
  if (!gitignores.any((e) => e.startsWith("secrets.dart"))) {
    gitignores.add("secrets.dart");
  }
  if (!gitignores.any((e) => e.startsWith("pubspec_overrides.yaml"))) {
    gitignores.add("pubspec_overrides.yaml");
  }
  if (!gitignores.any((e) => e.startsWith("/android/app/.cxx/"))) {
    gitignores.add("/android/app/.cxx/");
  }
  if (context.yaml.getAsMap("git").get("ignore_secure_file", true)) {
    if (!gitignores.any((e) => e.startsWith("katana_secrets.yaml"))) {
      gitignores.add("katana_secrets.yaml");
    }
  } else {
    gitignores.removeWhere((e) => e.startsWith("katana_secrets.yaml"));
  }
  await gitignore.writeAsString(gitignores.join("\n"));
  await Future.delayed(const Duration(seconds: 5));
  await command(
    "Run the project's build_runner to generate code.",
    [
      flutter,
      "packages",
      "pub",
      "run",
      "build_runner",
      "build",
      "--delete-conflicting-outputs",
    ],
  );
  if (Platform.isMacOS) {
    await command(
      "Run `pod install`.",
      [
        "pod",
        "install",
      ],
      workingDirectory: "ios",
    );
  }
  label("Create PrivacyInfo.xcprivacy.");
  await XCode.createPrivacyManifests();
  await command(
    "Run dart format",
    [
      dart,
      "format",
      ".",
    ],
  );
  await command(
    "Run import sorter",
    [
      flutter,
      "pub",
      "run",
      "import_sorter:main",
      ".",
    ],
  );
}