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 {
if (isLocalApply) {
validateLocal(context);
}
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 workers = cloudflare.getAsMap("workers");
final enabledWorkers = workers.get("enable", false);
final enableFirebaseAuth = workers.get("enable_firebase_auth", false);
final smartPlacement = workers.get("smart_placement", false);
final enabledRegion = isCloudflareRegionWorkerEnabled(context.yaml);
final regionPlacement = _placementRegion(
workers.getAsMap("region")["placement"],
);
final edgeCustomDomain = workers.get("custom_domain", "").trim();
final regionCustomDomain =
workers.getAsMap("region").get("custom_domain", "").trim();
final rootRegionPlacement = _placementRegion(
context.flavorContext?.yamlValue(
const ["cloudflare", "workers", "region", "placement"],
flavor: KatanaFlavor.prod,
) ??
workers.getAsMap("region")["placement"],
);
final firebase = context.yaml.getAsMap("firebase");
final firebaseProjectId = firebase.get("project_id", "");
final pages = cloudflare.getAsMap("pages");
final enabledPages = pages.get("enable", false);
final pubspec = File("pubspec.yaml");
final pubspecs = loadYaml(await pubspec.readAsString()) as Map;
final projectIdFromPubspec = pubspecs.get("name", "");
final projectIdFromYaml = cloudflare.get("project_id", "");
final projectId =
projectIdFromYaml.isEmpty ? projectIdFromPubspec : projectIdFromYaml;
final rootProjectIdFromYaml = context.flavorContext?.yamlValue(
const ["cloudflare", "project_id"],
flavor: KatanaFlavor.prod,
)?.toString() ??
projectIdFromYaml;
final rootProjectId = rootProjectIdFromYaml.isEmpty
? projectIdFromPubspec
: rootProjectIdFromYaml;
final flavor = context.flavorContext?.flavor.name ?? "prod";
if ((enabledWorkers || enabledPages) && projectId.isEmpty) {
error(
"Project ID is not specified. Please enter [project_id] in `katana.yaml`.");
return;
}
if (enableFirebaseAuth && firebaseProjectId.isEmpty) {
error(
"Firebase project ID is not specified. Please enter [project_id] in `katana.yaml`.",
);
return;
}
// Legacy entrypoint (also the file generated by `wrangler init`).
final workerIndexFile = File(cloudflareLegacyEntryPath);
final edgeEntryFile = File(cloudflareEdgeEntryPath);
final regionEntryFile = File(cloudflareRegionEntryPath);
final pagesIndexFile = File("cloudflare/public/index.html");
final wranglerJsonc = File("cloudflare/wrangler.jsonc");
final regionWranglerJsonc =
File("cloudflare/$cloudflareRegionWranglerConfig");
if (enabledRegion) {
for (final name in ["$projectId-region", "$rootProjectId-region"]) {
if (name.length > 63) {
throw StateError(
"The region Worker name `$name` exceeds 63 characters. Shorten [cloudflare]->[project_id]. "
"region Worker名`$name`が63文字を超えています。[cloudflare]->[project_id]を短くしてください。",
);
}
}
}
if (enabledWorkers) {
// Existing projects used `src/index.ts`; move it to `src/edge.ts` first.
await migrateCloudflareLegacyEntry();
}
final previousWranglerSource =
wranglerJsonc.existsSync() ? await wranglerJsonc.readAsString() : null;
final cloudflareDir = Directory("cloudflare");
final workerRulesFile = File("cloudflare/src/rules.json");
if (!cloudflareDir.existsSync()) {
await cloudflareDir.create();
}
if (enabledWorkers || enabledPages) {
if (!wranglerJsonc.existsSync()) {
final generatedDir = Directory("cloudflare/$projectId");
if (!generatedDir.existsSync()) {
await command(
"Initialize Cloudflare Workers",
[wrangler, "init", projectId, "--yes", "--cwd=cloudflare"],
runInShell: true,
);
}
// Keep existing application files when merging the Wrangler scaffold.
label("Rename files");
await mergeCloudflareScaffold(generatedDir, cloudflareDir);
await generatedDir.delete(recursive: true);
if (workerIndexFile.existsSync()) {
await workerIndexFile.delete();
}
if (pagesIndexFile.existsSync()) {
await pagesIndexFile.delete();
}
}
label("Rewrite `.gitignore`.");
final gitignore = File("cloudflare/.gitignore");
if (!gitignore.existsSync()) {
error("Cannot find `cloudflare/.gitignore`. Project is broken.");
return;
}
final gitignores = await gitignore.readAsLines();
if (context.yaml.getAsMap("git").get("ignore_secure_file", true)) {
if (!gitignores.any((e) => e.startsWith("*-*-*.json"))) {
gitignores.add("*-*-*.json");
}
} else {
gitignores.removeWhere((e) => e.startsWith("*-*-*.json"));
}
await gitignore.writeAsString(gitignores.join("\n"));
}
if (enabledWorkers) {
final functionsDir = Directory("cloudflare/src/functions");
if (!functionsDir.existsSync()) {
await functionsDir.create();
}
if (!edgeEntryFile.existsSync()) {
await CloudflareWorkersEntryCliCode(
entry: "edge",
firebaseProjectId: enableFirebaseAuth ? firebaseProjectId : null,
).generateFile("edge.ts");
}
if (!workerRulesFile.existsSync()) {
await const CloudflareWorkersRulesCliCode().generateFile("rules.json");
}
await CloudflareWranglerCliCode(
projectId: rootProjectId,
smartPlacement: smartPlacement,
).generateFile("wrangler.jsonc");
final generatedWranglerSource = await wranglerJsonc.readAsString();
// Existing Wrangler files are the source of truth. Rebuilding from the
// template would discard routes, triggers, bindings, and other custom
// root settings that Katana does not own.
final restoredWranglerSource =
previousWranglerSource ?? generatedWranglerSource;
var synchronizedWranglerSource =
WranglerEnvironmentSynchronizer.synchronize(
restoredWranglerSource,
flavor: flavor,
workerName: projectId,
rootWorkerName: rootProjectId,
);
if (enableFirebaseAuth) {
synchronizedWranglerSource =
WranglerEnvironmentSynchronizer.upsertVariables(
synchronizedWranglerSource,
flavor: flavor,
values: {"FIREBASE_PROJECT_ID": firebaseProjectId},
);
}
synchronizedWranglerSource = _syncCustomDomainRoutes(
synchronizedWranglerSource,
flavor: flavor,
customDomain: edgeCustomDomain,
);
await wranglerJsonc.writeAsString(synchronizedWranglerSource);
await installMissingCloudflarePackages(
npm: npm,
packages: const [
"hono",
"@mathrunet/masamune",
"@mathrunet/masamune_cloudflare",
],
);
await addFlutterImport(
[
"masamune_functions_cloudflare",
],
);
if (enabledRegion) {
await _applyRegionWorker(
wrangler: wrangler,
flavor: flavor,
projectId: projectId,
rootProjectId: rootProjectId,
regionEntryFile: regionEntryFile,
regionWranglerJsonc: regionWranglerJsonc,
firebaseProjectId: enableFirebaseAuth ? firebaseProjectId : null,
placement: regionPlacement,
rootPlacement: rootRegionPlacement,
customDomain: regionCustomDomain,
);
}
}
if (enabledPages) {
if (!pagesIndexFile.existsSync()) {
await const CloudflarePagesIndexCliCode().generateFile("index.html");
}
}
}