exec method
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 bin = context.yaml.getAsMap("bin");
final npm = bin.get("npm", "npm");
final wrangler = bin.get("wrangler", "wrangler");
final flavor = context.flavorContext?.flavor.name ?? "prod";
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 configuredConsumerFlavor =
backup.get("consumer_flavor", "").toString().trim();
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 (configuredConsumerFlavor.isNotEmpty &&
configuredConsumerFlavor != KatanaFlavor.dev.name &&
configuredConsumerFlavor != KatanaFlavor.prod.name) {
error(
"Cloudflare R2 backup [consumer_flavor] must be `dev` or `prod`.",
);
return;
}
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 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;
}
try {
await ensureStorageManagedStateIsGitIgnored();
} on StateError catch (exception) {
error(exception.message.toString());
return;
}
final secrets = await _loadSecretsRoot();
late final StorageManagedStateLoadResult managed;
try {
managed = await loadAndMigrateStorageManagedState(
secrets,
configuredSecret: configuredDownloadUrlSecret,
);
} on StateError catch (exception) {
error(exception.message.toString());
return;
}
if (managed.stateChanged) {
await saveStorageManagedState(managed.state);
}
if (managed.secretsChanged) {
await _saveSecretsRoot(secrets);
}
final downloadUrlSecret = managed.downloadUrlSecret;
final ownsBackupConsumer = _ownsBackupQueueConsumer(
context,
queueName: backupQueueName,
configuredConsumerFlavor: configuredConsumerFlavor,
);
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");
final wranglerSource = WranglerEnvironmentSynchronizer.transformEnvironment(
WranglerEnvironmentSynchronizer.ensureEnvironment(
await wranglerFile.readAsString(),
flavor: flavor,
workerName: cloudflare.get("project_id", ""),
),
flavor: flavor,
transform: (environment) {
var updated = _updateWranglerR2Bucket(
environment,
binding: binding,
bucketName: bucketName,
previewBucketName: previewBucketName,
backupEnabled: backupEnabled,
backupBinding: backupBinding,
backupBucketName: backupBucketName,
backupPreviewBucketName: backupPreviewBucketName,
);
if (backupEnabled) {
updated = ownsBackupConsumer
? _updateWranglerQueueConsumer(
updated,
queueName: backupQueueName,
maxBatchSize: backupMaxBatchSize,
maxBatchTimeout: backupMaxBatchTimeout,
maxRetries: backupMaxRetries,
deadLetterQueue: backupDeadLetterQueue,
)
: _removeWranglerQueueConsumer(
updated,
queueName: backupQueueName,
);
}
return updated;
},
);
await wranglerFile.writeAsString(wranglerSource);
await installMissingCloudflarePackages(
npm: npm,
packages: const ["@mathrunet/masamune_cloudflare_storage"],
);
await addFlutterImport(
[
"masamune_storage_cloudflare",
],
);
await _putWranglerSecret(
wrangler: wrangler,
environment: flavor,
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,
);
}
}