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 firebaseCommand = bin.get("firebase", "firebase");
final gsutil = bin.get("gsutil", "gsutil");
final flutterfireCommand = bin.get("flutterfire", "flutterfire");
final firebase = context.yaml.getAsMap("firebase");
final github = context.yaml.getAsMap("github");
final action = github.getAsMap("action");
final enableGithubAction = action.get("enable", false);
final platformGithubAction = action.get("platform", "");
final webGithubAction = action.getAsMap("web");
final repositoryFirebaseWebGithubAction =
webGithubAction.get("repository", "");
final projectId = firebase.get("project_id", "");
final hosting = firebase.getAsMap("hosting");
final useFlutter = hosting.get("use_flutter", false);
final firestore = firebase.getAsMap("firestore");
// final overwriteFirestoreRule = firestore.get("overwrite_rule", false);
final enabledFirestore = firestore.get("enable", false);
final enabledAuthentication =
firebase.getAsMap("authentication").get("enable", false);
final enabledFunctions =
firebase.getAsMap("functions").get("enable", false);
final storage = firebase.getAsMap("storage");
// final overwriteStorageRule = storage.get("overwrite_rule", false);
final enabledStorage = storage.get("enable", false);
final enabledCors = storage.get("cors", false);
final enabledHosting = hosting.get("enable", false);
final enableActions = hosting.get("github_actions", false);
final enabledLogger = firebase.getAsMap("logger").get("enable", false);
if (projectId.isEmpty) {
error(
"The item [firebase]->[project_id] is missing. Please provide the Firebase project ID for the configuration.",
);
return;
}
if (enableActions && !enableGithubAction) {
error(
"The item [hosting]->[github_actions] is true, but the item [github]->[action]->[enable] is false. Please set the item [github]->[action]->[enable] to true.",
);
return;
}
if (enableActions && !platformGithubAction.contains("web")) {
error(
"The item [hosting]->[github_actions] is true, but the item [github]->[action]->[platform] does not contain `web`. Please set the item [github]->[action]->[platform] to `web`.",
);
return;
}
if (enableActions && repositoryFirebaseWebGithubAction.isEmpty) {
error(
"The item [github]->[action]->[web]->[repository] is missing. Please provide the repository name for the Firebase Web configuration.",
);
return;
}
label("Create firebase directory");
final firebaseDir = Directory("firebase");
if (!firebaseDir.existsSync()) {
await firebaseDir.create();
}
label("Check firebase.json");
final firebaseJsonFile = File("firebase/firebase.json");
final firebaseJsonFileExists = firebaseJsonFile.existsSync();
final firebaseJson = firebaseJsonFileExists
? jsonDecodeAsMap(await firebaseJsonFile.readAsString())
: <String, dynamic>{};
label("Check status");
final firebaseFunctionsIndex = File("firebase/functions/src/index.ts");
final firebaseFunctionsIndexExists = firebaseFunctionsIndex.existsSync();
label("Load data");
final gradle = AppGradle();
await gradle.load();
final androidApplicationId = gradle.android?.defaultConfig.applicationId
.replaceAll('"', "")
.replaceAll("'", "");
if (androidApplicationId.isEmpty) {
error(
"Application ID is not set in [android]->[defaultConfig]->[applicationId] in `android/app/build.gradle`.",
);
return;
}
final xcode = XCode();
await xcode.load();
final bundleId = xcode.pbxBuildConfiguration
.map(
(e) => e.buildSettings
.firstWhereOrNull((e) => e.key == "PRODUCT_BUNDLE_IDENTIFIER")
?.value,
)
.firstWhereOrNull((e) => e != null)
?.replaceAll('"', "")
.replaceAll("'", "");
if (bundleId.isEmpty) {
error(
"Bundle ID is not set in your XCode project. Please open `ios/Runner.xcodeproj` and check the settings.",
);
return;
}
if (!firebaseJsonFileExists) {
await command(
"Run flutterfire configure",
[
flutterfireCommand,
"configure",
"-y",
"--project=$projectId",
"--ios-bundle-id=$bundleId",
"--macos-bundle-id=$bundleId",
"--android-package-name=$androidApplicationId",
],
);
}
await addFlutterImport(
[
"firebase_core",
if (enabledAuthentication) ...[
"firebase_auth",
"katana_auth_firebase",
],
if (enabledFirestore) ...[
"cloud_firestore",
"katana_model_firestore",
],
if (enabledStorage) ...[
"firebase_storage",
"katana_storage_firebase",
],
if (enabledFunctions) ...[
"katana_functions_firebase",
],
if (enabledLogger) ...[
"firebase_analytics",
"firebase_crashlytics",
"firebase_performance",
"masamune_logger_firebase",
]
],
);
final commandStack = <String>[];
if (enabledFirestore) {
if (!firebaseJson.containsKey("firestore")) {
final firestoreProcess = await Process.start(
firebaseCommand,
[
"init",
"firestore",
"--project",
projectId,
],
runInShell: true,
workingDirectory: "firebase",
mode: ProcessStartMode.normal,
);
firestoreProcess.stdout.transform(utf8.decoder).forEach((line) {
// ignore: avoid_print
print(line);
_runCommandStack(
line,
"? Are you ready to proceed?",
commandStack,
() => firestoreProcess.stdin.write("y\n"),
);
_runCommandStack(
line,
"? What file should be used for Firestore Rules?",
commandStack,
() => firestoreProcess.stdin.write("\n"),
);
_runCommandStack(
line,
"? File firestore.indexes.json already exists.",
commandStack,
() => firestoreProcess.stdin.write("n\n"),
);
_runCommandStack(
line,
"? File firestore.rules already exists.",
commandStack,
() => firestoreProcess.stdin.write("n\n"),
);
_runCommandStack(
line,
"? Would you like to delete these indexes?",
commandStack,
() => firestoreProcess.stdin.write("n\n"),
);
_runCommandStack(
line,
"? What file should be used for Firestore indexes?",
commandStack,
() => firestoreProcess.stdin.write("\n"),
);
});
await firestoreProcess.exitCode;
label("Rewriting Rules");
await const FirestoreRulesCliCode().generateFile("firestore.rules");
}
}
if (enabledStorage) {
if (!firebaseJson.containsKey("storage")) {
final storageProcess = await Process.start(
firebaseCommand,
[
"init",
"storage",
"--project",
projectId,
],
runInShell: true,
workingDirectory: "firebase",
mode: ProcessStartMode.normal,
);
storageProcess.stdout.transform(utf8.decoder).forEach((line) {
// ignore: avoid_print
print(line);
_runCommandStack(
line,
"? Are you ready to proceed?",
commandStack,
() => storageProcess.stdin.write("y\n"),
);
_runCommandStack(
line,
"? File storage.rules already exists.",
commandStack,
() => storageProcess.stdin.write("y\n"),
);
_runCommandStack(
line,
"? What file should be used for Storage Rules?",
commandStack,
() => storageProcess.stdin.write("\n"),
);
});
await storageProcess.exitCode;
label("Rewriting Rules");
await const FirebaseStorageRulesCliCode().generateFile("storage.rules");
if (enabledCors) {
label("Set the cors.json");
await const FirebaseStorageCorsCliCode().generateFile("cors.json");
await command(
"Run cors.json deploy",
[
gsutil,
"cors",
"set",
"cors.json",
"gs://$projectId.appspot.com",
],
workingDirectory: "firebase",
);
}
}
}
if (enabledHosting) {
if (!firebaseJson.containsKey("hosting")) {
final hostingProcess = await Process.start(
firebaseCommand,
[
"init",
"hosting",
"--project",
projectId,
],
runInShell: true,
workingDirectory: "firebase",
mode: ProcessStartMode.normal,
);
hostingProcess.stdout.transform(utf8.decoder).forEach((line) {
// ignore: avoid_print
print(line);
_runCommandStack(
line,
"? Are you ready to proceed?",
commandStack,
() => hostingProcess.stdin.write("y\n"),
);
_runCommandStack(
line,
"? What do you want to use as your public directory?",
commandStack,
() => hostingProcess.stdin.write("hosting\n"),
);
_runCommandStack(
line,
"? Configure as a single-page app",
commandStack,
() {
if (useFlutter) {
hostingProcess.stdin.write("y\n");
} else {
hostingProcess.stdin.write("n\n");
}
},
);
_runCommandStack(
line,
"? Set up automatic builds and deploys with GitHub?",
commandStack,
() => hostingProcess.stdin.write(
enableActions ? "y\n" : "n\n",
),
);
_runCommandStack(
line,
"? File hosting/index.html already exists. Overwrite?",
commandStack,
() => hostingProcess.stdin.write("n\n"),
);
_runCommandStack(
line,
"? File hosting/404.html already exists. Overwrite?",
commandStack,
() => hostingProcess.stdin.write("n\n"),
);
_runCommandStack(
line,
"? For which GitHub repository would you like to set up a GitHub workflow?",
commandStack,
() => hostingProcess.stdin
.write("$repositoryFirebaseWebGithubAction\n"),
);
_runCommandStack(
line,
"? Set up the workflow to run a build script before every deploy?",
commandStack,
() => hostingProcess.stdin.write("y\n"),
);
_runCommandStack(
line,
"? What script should be run before every deploy?",
commandStack,
() => hostingProcess.stdin.write("\n"),
);
_runCommandStack(
line,
"? Set up automatic deployment to your site's live channel when a PR is merged?",
commandStack,
() => hostingProcess.stdin.write("n\n"),
);
});
await hostingProcess.exitCode;
}
}
if (enabledFunctions) {
if (!firebaseJson.containsKey("functions")) {
final functionsProcess = await Process.start(
firebaseCommand,
[
"init",
"functions",
"--project",
projectId,
],
runInShell: true,
workingDirectory: "firebase",
mode: ProcessStartMode.normal,
);
functionsProcess.stdout.transform(utf8.decoder).forEach((line) {
// ignore: avoid_print
print(line);
_runCommandStack(
line,
"? Are you ready to proceed?",
commandStack,
() => functionsProcess.stdin.write("y\n"),
);
_runCommandStack(
line,
"? What language would you like to use to write Cloud Functions?",
commandStack,
() => functionsProcess.stdin.write("j\n"),
);
_runCommandStack(
line,
"? Do you want to use ESLint to catch probable bugs and enforce style?",
commandStack,
() => functionsProcess.stdin.write("y\n"),
);
_runCommandStack(
line,
"? Do you want to install dependencies with npm now?",
commandStack,
() => functionsProcess.stdin.write("y\n"),
);
});
await functionsProcess.exitCode;
}
await command(
"Package installation.",
[
npm,
"install",
"@mathrunet/masamune",
],
workingDirectory: "firebase/functions",
);
if (!firebaseFunctionsIndexExists) {
label("Data replacement for Firebase Functions.");
await const FirebaseFunctionsIndexCliCode().generateFile("index.ts");
}
await command(
"Fix lint errors.",
[
"eslint",
"--ext",
".js,.ts",
"--fix",
".",
],
workingDirectory: "firebase/functions",
);
}
label("Edit config.properties");
final configPropertiesFile = File("android/config.properties");
if (!configPropertiesFile.existsSync()) {
await configPropertiesFile.writeAsString("");
}
final configProperties = await configPropertiesFile.readAsLines();
if (!configProperties
.any((element) => element.startsWith("flutter.minSdkVersion"))) {
await configPropertiesFile.writeAsString([
...configProperties,
"flutter.minSdkVersion=${Config.firebaseMinSdkVersion}",
].join("\n"));
}
label("Edit build.gradle");
if (!gradle.loadProperties.any((e) => e.name == "configProperties")) {
gradle.loadProperties.add(
GradleLoadProperties(
path: "config.properties",
name: "configProperties",
file: "configPropertiesFile",
),
);
}
if (!gradle.plugins
.any((e) => e.plugin == "com.google.gms.google-services")) {
gradle.plugins.add(
GradlePlugin(
plugin: "com.google.gms.google-services",
),
);
}
gradle.android?.defaultConfig.minSdkVersion =
"configProperties[\"flutter.minSdkVersion\"]";
await gradle.save();
label("Rewrite `.gitignore`.");
final gitignore = File("firebase/.gitignore");
if (!gitignore.existsSync()) {
error("Cannot find `firebase/.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(".env"))) {
gitignores.add(".env");
}
} else {
gitignores.removeWhere((e) => e.startsWith(".env"));
}
await gitignore.writeAsString(gitignores.join("\n"));
label("Import firestore.indexes.json");
final firestoreIndexes = File("firebase/firestore.indexes.json");
final indexData = await command(
"Import indexes.",
[
firebaseCommand,
"firestore:indexes",
],
workingDirectory: "firebase",
);
await firestoreIndexes.writeAsString(indexData);
if (!firebaseJsonFileExists) {
await command(
"Run firebase deploy",
[
firebaseCommand,
"deploy",
if (enableActions) ...[
"--except",
"hosting",
]
],
workingDirectory: "firebase",
);
}
}