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 melos = bin.get("melos", "melos");
  final packageName = context.args.get(1, "");
  final options = context.args.get(2, "");
  final moduleName = context.args.get(3, nullOfString);
  final repositoryName = context.args.get(4, nullOfString);
  if (packageName.isEmpty) {
    error(
      "Please provide the name of the package.\r\nパッケージ名を記載してください。\r\n\r\nkatana create [package name]",
    );
    return;
  }
  if (options == "-m" && (moduleName.isEmpty || repositoryName.isEmpty)) {
    error(
      "When creating a module, please include [module name] and [repository name].\r\n\r\nモジュールを作成する場合は[module name]と[repository 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;
  }
  if (options == "-m") {
    await command(
      "Create a Flutter package project.",
      [
        flutter,
        "create",
        "--org",
        domain,
        "--template=package",
        "--project-name",
        moduleName!.toSnakeCase(),
        ".",
      ],
    );
    await command(
      "Import packages.",
      [
        flutter,
        "pub",
        "add",
        ...importPackages,
        ...allOptionsImportPackage,
        "masamune_module",
      ],
    );
    await command(
      "Import dev packages.",
      [
        flutter,
        "pub",
        "add",
        "--dev",
        ...importDevPackages,
        "melos",
        "import_sorter",
      ],
    );
    label("Replace lib/${moduleName.toSnakeCase()}.dart");
    await const ModuleCliCode().generateDartCode(
      "lib/${moduleName.toSnakeCase()}",
      moduleName.toSnakeCase(),
    );
    label("Create home.dart");
    await HomePageCliCode(module: moduleName)
        .generateDartCode("lib/pages/home", "home");
    label("Create counter.dart");
    await CounterModelCliCode(module: moduleName)
        .generateDartCode("lib/models/counter", "counter");
    label("Edit a ${moduleName.toSnakeCase()}_test.dart");
    await const WidgetTestCliCode()
        .generateFile("${moduleName.toSnakeCase()}_test.dart");
    label("Generate file for VSCode");
    for (final file in otherFiles.entries) {
      await file.value.generateFile(file.key);
    }
    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("Replace README.md");
    await ModuleReadMeCliCode(module: moduleName).generateFile("README.md");
    label("Create melos.yaml");
    await MelosCliCode(module: moduleName, repository: repositoryName)
        .generateFile("melos.yaml");
    label("Replace pubspec.yaml");
    final pubspecFile = File("pubspec.yaml");
    final pubspec = await pubspecFile.readAsString();
    await pubspecFile.writeAsString(
      pubspec.replaceAll(
        RegExp(
          r"homepage:",
        ),
        "homepage: https://mathru.net",
      ),
    );
    label("Add a .pubignore");
    await const PubignoreCliCode().generateFile(".pubignore");
    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 (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",
      ],
    );
    label("Create example.");
    final exampleDirectory = Directory("example");
    if (!exampleDirectory.existsSync()) {
      await exampleDirectory.create();
    }
    await command(
      "Run the project's build_runner to generate code.",
      [
        "katana",
        "create",
        packageName,
        "-e",
        moduleName.toSnakeCase(),
      ],
      workingDirectory: "example",
    );
    label("Run melos.");
    await command(
      "Run the project's build_runner to generate code.",
      [
        melos,
        "bs",
      ],
    );
  } else {
    await command(
      "Create a Flutter project.",
      [
        flutter,
        "create",
        "--org",
        domain,
        "--project-name",
        projectName!,
        ".",
      ],
    );
    await command(
      "Import packages.",
      [
        flutter,
        "pub",
        "add",
        ...importPackages,
        if (options == "-a" || options == "-e") ...allOptionsImportPackage,
      ],
    );
    if (moduleName.isNotEmpty) {
      await command(
        "Import packages.",
        [
          flutter,
          "pub",
          "add",
          "--directory=.",
          if (moduleName.isNotEmpty)
            if (options == "-e")
              "${moduleName!.toSnakeCase()}:{'path':'../'}"
            else
              moduleName!.toSnakeCase(),
        ],
      );
    }
    await command(
      "Import dev packages.",
      [
        flutter,
        "pub",
        "add",
        "--dev",
        ...importDevPackages,
        "import_sorter",
      ],
    );
    label("Replace lib/main.dart");
    await MainCliCode(module: moduleName)
        .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");
    if (moduleName.isNotEmpty) {
      label("Replace lib/module.dart");
      await MainModuleCliCode(module: moduleName!)
          .generateDartCode("lib/module", "module");
    } else {
      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(context.args.get(2, "") == "-a" ||
            context.args.get(2, "") == "-e")
        .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("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);
    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 (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",
        ".",
      ],
    );
  }
}