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 npm = bin.get("npm", "npm");
  final wrangler = bin.get("wrangler", "wrangler");
  final cloudflare = context.yaml.getAsMap("cloudflare");
  final storage = cloudflare.getAsMap("storage");
  final binding = storage.get("binding", "R2_BUCKET");
  final bucketName = storage.get("bucket_name", "");
  final previewBucketName = storage.get("preview_bucket_name", "");
  final publicBaseUrl = storage.get("public_base_url", "");
  final configuredDownloadUrlSecret = storage.get("download_url_secret", "");
  final backup = storage.getAsMap("backup");
  final backupEnabled = backup.get("enable", false);
  final backupBinding = backup.get("binding", "R2_BACKUP_BUCKET");
  final backupBucketName = backup.get("bucket_name", "");
  final backupPreviewBucketName = backup.get("preview_bucket_name", "");
  final backupQueueName = backup.get(
    "queue_name",
    bucketName.isEmpty ? "" : "$bucketName-backup",
  );
  final backupMaxBatchSize = backup.get("max_batch_size", 10);
  final backupMaxBatchTimeout = backup.get("max_batch_timeout", 5);
  final backupMaxRetries = backup.get("max_retries", 3);
  final backupDeadLetterQueue = backup.get(
    "dead_letter_queue",
    backupQueueName.isEmpty ? "" : "$backupQueueName-dlq",
  );
  if (binding.isEmpty) {
    error(
      "If [cloudflare]->[storage]->[enable] is enabled, please include [cloudflare]->[storage]->[binding].",
    );
    return;
  }
  if (bucketName.isEmpty) {
    error(
      "If [cloudflare]->[storage]->[enable] is enabled, please include [cloudflare]->[storage]->[bucket_name]. Create it with `wrangler r2 bucket create <bucket_name>`.",
    );
    return;
  }
  if (publicBaseUrl.isEmpty) {
    error(
      "If [cloudflare]->[storage]->[enable] is enabled, please include [cloudflare]->[storage]->[public_base_url].",
    );
    return;
  }
  if (backupEnabled) {
    if (backupBinding.isEmpty) {
      error(
        "If [cloudflare]->[storage]->[backup]->[enable] is enabled, please include [binding].",
      );
      return;
    }
    if (backupBucketName.isEmpty) {
      error(
        "If [cloudflare]->[storage]->[backup]->[enable] is enabled, please include [bucket_name]. Create it with `wrangler r2 bucket create <bucket_name>`.",
      );
      return;
    }
    if (backupQueueName.isEmpty) {
      error(
        "If [cloudflare]->[storage]->[backup]->[enable] is enabled, please include [queue_name].",
      );
      return;
    }
    if (binding == backupBinding || bucketName == backupBucketName) {
      error(
        "Cloudflare R2 backup must use a different binding and bucket from the source storage.",
      );
      return;
    }
    if (backupMaxBatchSize <= 0 ||
        backupMaxBatchTimeout < 0 ||
        backupMaxRetries < 0) {
      error(
        "Cloudflare R2 backup queue settings must be non-negative, and [max_batch_size] must be greater than zero.",
      );
      return;
    }
  }
  final downloadUrlSecret =
      await _loadOrCreateDownloadUrlSecret(configuredDownloadUrlSecret);
  final cloudflareDir = Directory("cloudflare");
  if (!cloudflareDir.existsSync()) {
    error(
      "The directory `cloudflare` does not exist. Initialize Cloudflare Workers by enabling [cloudflare]->[workers]->[enable] and executing `katana apply`.",
    );
    return;
  }
  final indexFile = File("cloudflare/src/index.ts");
  if (!indexFile.existsSync()) {
    error(
      "The file `cloudflare/src/index.ts` does not exist. Initialize Cloudflare Workers by enabling [cloudflare]->[workers]->[enable] and executing `katana apply`.",
    );
    return;
  }
  final wranglerFile = File("cloudflare/wrangler.jsonc");
  if (!wranglerFile.existsSync()) {
    error(
      "The file `cloudflare/wrangler.jsonc` does not exist. Initialize Cloudflare Workers by enabling [cloudflare]->[workers]->[enable] and executing `katana apply`.",
    );
    return;
  }

  label("Add Cloudflare Workers functions");
  final source = await indexFile.readAsString();
  final updated = _updateStorageFunctions(
    source,
    binding: binding,
    bucketName: bucketName,
    publicBaseUrl: publicBaseUrl,
    backupEnabled: backupEnabled,
    backupBinding: backupBinding,
  );
  if (updated == null) {
    return;
  }
  await indexFile.writeAsString(updated);
  label("Add Cloudflare R2 bucket binding");
  var wranglerSource = _updateWranglerR2Bucket(
    await wranglerFile.readAsString(),
    binding: binding,
    bucketName: bucketName,
    previewBucketName: previewBucketName,
    backupEnabled: backupEnabled,
    backupBinding: backupBinding,
    backupBucketName: backupBucketName,
    backupPreviewBucketName: backupPreviewBucketName,
  );
  if (backupEnabled) {
    wranglerSource = _updateWranglerQueueConsumer(
      wranglerSource,
      queueName: backupQueueName,
      maxBatchSize: backupMaxBatchSize,
      maxBatchTimeout: backupMaxBatchTimeout,
      maxRetries: backupMaxRetries,
      deadLetterQueue: backupDeadLetterQueue,
    );
  }
  await wranglerFile.writeAsString(wranglerSource);
  await command(
    "Package installation.",
    [
      npm,
      "install",
      "@mathrunet/masamune_cloudflare_storage",
    ],
    workingDirectory: "cloudflare",
    runInShell: true,
  );
  await addFlutterImport(
    [
      "masamune_storage_cloudflare",
    ],
  );
  await _putWranglerSecret(
    wrangler: wrangler,
    key: "STORAGE_DOWNLOAD_URL_SECRET",
    value: downloadUrlSecret,
  );
  if (backupEnabled) {
    await _ensureQueue(wrangler: wrangler, queueName: backupQueueName);
    if (backupDeadLetterQueue.isNotEmpty) {
      await _ensureQueue(
        wrangler: wrangler,
        queueName: backupDeadLetterQueue,
      );
    }
    await _ensureR2CreateNotification(
      wrangler: wrangler,
      bucketName: bucketName,
      queueName: backupQueueName,
    );
  }
}