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 firebase = context.yaml.getAsMap("firebase");
final projectId = firebase.get("project_id", "");
final authentication = firebase.getAsMap("authentication");
final providers = authentication.getAsMap("providers");
final apple = providers.getAsMap("apple");
final facebook = providers.getAsMap("facebook");
final google = providers.getAsMap("google");
final deleteUser = authentication.getAsMap("delete_user");
final enableDeleteUser = deleteUser.get("enable", false);
final functions = firebase.getAsMap("functions");
final enableFunctions = functions.get("enable", false);
final enableApple = apple.get("enable", false);
final enableGoogle = google.get("enable", false);
final enableFacebook = facebook.get("enable", false);
final facebookAppId = facebook.get<int?>("app_id", null)?.toString() ??
facebook.get("app_id", "") ??
"";
final facebookAppSecret =
facebook.get<int?>("app_secret", null)?.toString() ??
facebook.get("app_secret", "") ??
"";
final facebookClientToken = facebook.get("client_token", "");
if (projectId.isEmpty) {
error(
"The item [firebase]->[project_id] is missing. Please provide the Firebase project ID for the configuration.",
);
return;
}
if (enableFacebook &&
(facebookAppId.isEmpty ||
facebookAppSecret.isEmpty ||
facebookClientToken.isEmpty)) {
error(
"The item [facebook]->[app_id] or [facebook]->[app_secret], [facebook]->[client_token] is missing. Please provide the Facebook App ID and App Secret, Client Token for the configuration.",
);
return;
}
if (enableDeleteUser && !enableFunctions) {
error(
"The item [firebase]->[functions]->[enable] is missing. Please provide the Firebase functions configuration.",
);
return;
}
await addFlutterImport(
[
if (enableApple) ...[
"masamune_auth_apple",
],
if (enableFacebook) ...[
"masamune_auth_facebook",
],
if (enableGoogle) ...[
"masamune_auth_google",
],
if (enableDeleteUser) ...[
"masamune_auth_firebase",
],
],
);
if (enableGoogle || enableFacebook) {
label("Load GoogleService-Info.plist.");
final googleServicePlist = File("ios/Runner/GoogleService-Info.plist");
final googleServiceDocument =
XmlDocument.parse(await googleServicePlist.readAsString());
final googleServiceDict =
googleServiceDocument.findAllElements("dict").firstOrNull;
if (googleServiceDict == null) {
throw Exception(
"Could not find `dict` element in `ios/Runner/GoogleService-Info.plist`. File is corrupt.",
);
}
final clientIdNode = googleServiceDict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "CLIENT_ID";
});
if (clientIdNode == null) {
throw Exception(
"Could not find `CLIENT_ID` element in `ios/Runner/GoogleService-Info.plist`. File is corrupt.",
);
}
final clientId = clientIdNode.nextElementSibling?.innerText;
final reversedClientIdNode =
googleServiceDict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "REVERSED_CLIENT_ID";
});
if (reversedClientIdNode == null) {
throw Exception(
"Could not find `REVERSED_CLIENT_ID` element in `ios/Runner/GoogleService-Info.plist`. File is corrupt.",
);
}
final reversedClientId =
reversedClientIdNode.nextElementSibling?.innerText;
if (clientId.isEmpty || reversedClientId.isEmpty) {
throw Exception(
"Could not find `CLIENT_ID` or `REVERSED_CLIENT_ID` element in `ios/Runner/GoogleService-Info.plist`. File is corrupt.",
);
}
label("Edit Info.plist.");
final plist = File("ios/Runner/Info.plist");
final iosDocument = XmlDocument.parse(await plist.readAsString());
final dict = iosDocument.findAllElements("dict").firstOrNull;
if (dict == null) {
throw Exception(
"Could not find `dict` element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
final bundleUrlTypes = dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "CFBundleURLTypes";
});
if (bundleUrlTypes == null) {
dict.children.addAll(
[
XmlElement(
XmlName("key"),
[],
[
XmlText("CFBundleURLTypes"),
],
),
XmlElement(
XmlName("array"),
[],
[
XmlElement(
XmlName("dict"),
[],
[
XmlElement(
XmlName("key"),
[],
[
XmlText("CFBundleTypeRole"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText("Editor"),
],
),
XmlElement(
XmlName("key"),
[],
[
XmlText("CFBundleURLSchemes"),
],
),
XmlElement(
XmlName("array"),
[],
[
if (enableGoogle)
XmlElement(
XmlName("string"),
[],
[
XmlText(reversedClientId!),
],
),
if (enableFacebook)
XmlElement(
XmlName("string"),
[],
[
XmlText("fb$facebookAppId"),
],
),
],
),
],
),
],
),
],
);
} else {
final urlSchemeArray = bundleUrlTypes.nextElementSibling;
if (urlSchemeArray == null) {
throw Exception(
"Could not find `CFBundleURLTypes` value element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
if (enableGoogle &&
!urlSchemeArray.children.any(
(p1) =>
p1 is XmlElement &&
p1.children.any((p2) =>
p2 is XmlElement &&
p2.name.toString() == "array" &&
p2.children.any((p3) =>
p3 is XmlElement &&
p3.name.toString() == "string" &&
p3.innerText == reversedClientId)),
)) {
urlSchemeArray.children.add(
XmlElement(
XmlName("dict"),
[],
[
XmlElement(
XmlName("key"),
[],
[
XmlText("CFBundleTypeRole"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText("Editor"),
],
),
XmlElement(
XmlName("key"),
[],
[
XmlText("CFBundleURLSchemes"),
],
),
XmlElement(
XmlName("array"),
[],
[
XmlElement(
XmlName("string"),
[],
[
XmlText(reversedClientId!),
],
),
],
),
],
),
);
}
if (enableFacebook &&
!urlSchemeArray.children.any(
(p1) =>
p1 is XmlElement &&
p1.children.any((p2) =>
p2 is XmlElement &&
p2.name.toString() == "array" &&
p2.children.any((p3) =>
p3 is XmlElement &&
p3.name.toString() == "string" &&
p3.innerText == "fb$facebookAppId")),
)) {
urlSchemeArray.children.add(
XmlElement(
XmlName("dict"),
[],
[
XmlElement(
XmlName("key"),
[],
[
XmlText("CFBundleTypeRole"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText("Editor"),
],
),
XmlElement(
XmlName("key"),
[],
[
XmlText("CFBundleURLSchemes"),
],
),
XmlElement(
XmlName("array"),
[],
[
XmlElement(
XmlName("string"),
[],
[
XmlText("fb$facebookAppId"),
],
),
],
),
],
),
);
}
}
if (enableFacebook) {
final lsApplicationQueriesSchemes =
dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "LSApplicationQueriesSchemes";
});
if (lsApplicationQueriesSchemes == null) {
dict.children.addAll(
[
XmlElement(
XmlName("key"),
[],
[
XmlText("LSApplicationQueriesSchemes"),
],
),
XmlElement(
XmlName("array"),
[],
[
XmlElement(
XmlName("string"),
[],
[
XmlText("fbapi"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText("fb-messenger-share-api"),
],
),
],
),
],
);
} else {
final lsApplicationQueriesSchemesArray =
lsApplicationQueriesSchemes.nextElementSibling;
if (lsApplicationQueriesSchemesArray == null) {
throw Exception(
"Could not find `LSApplicationQueriesSchemes` value element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
if (enableFacebook &&
!lsApplicationQueriesSchemesArray.children.any((p1) =>
p1 is XmlElement &&
p1.name.toString() == "string" &&
p1.innerText == "fbapi")) {
lsApplicationQueriesSchemesArray.children.addAll(
[
XmlElement(
XmlName("string"),
[],
[
XmlText("fbapi"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText("fb-messenger-share-api"),
],
),
],
);
}
}
final facebookAppIdElement = dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "FacebookAppID";
});
if (facebookAppIdElement == null) {
dict.children.addAll(
[
XmlElement(
XmlName("key"),
[],
[
XmlText("FacebookAppID"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText(facebookAppId),
],
),
],
);
} else {
final facebookAppIdElementString =
facebookAppIdElement.nextElementSibling;
if (facebookAppIdElementString == null) {
throw Exception(
"Could not find `FacebookAppID` value element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
facebookAppIdElementString.innerText = facebookAppId;
}
final facebookClientTokenElement = dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "FacebookClientToken";
});
if (facebookClientTokenElement == null) {
dict.children.addAll(
[
XmlElement(
XmlName("key"),
[],
[
XmlText("FacebookClientToken"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText(facebookClientToken),
],
),
],
);
} else {
final facebookClientTokenElementString =
facebookClientTokenElement.nextElementSibling;
if (facebookClientTokenElementString == null) {
throw Exception(
"Could not find `FacebookClientToken` value element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
facebookClientTokenElementString.innerText = facebookClientToken;
}
final displayNameElement = dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "CFBundleDisplayName";
});
final displayNameElementString = displayNameElement?.nextElementSibling;
if (displayNameElementString == null) {
throw Exception(
"Could not find `CFBundleDisplayName` value element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
final facebookDisplayNameElement = dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "FacebookDisplayName";
});
if (facebookDisplayNameElement == null) {
dict.children.addAll(
[
XmlElement(
XmlName("key"),
[],
[
XmlText("FacebookDisplayName"),
],
),
XmlElement(
XmlName("string"),
[],
[
XmlText(displayNameElementString.innerText),
],
),
],
);
} else {
final facebookDisplayNameElementString =
facebookDisplayNameElement.nextElementSibling;
if (facebookDisplayNameElementString == null) {
throw Exception(
"Could not find `FacebookDisplayName` value element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
facebookDisplayNameElementString.innerText =
displayNameElementString.innerText;
}
}
await plist.writeAsString(
iosDocument.toXmlString(pretty: true, indent: "\t", newLine: "\n"),
);
}
if (enableFacebook) {
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");
final gradle = AppGradle();
await gradle.load();
if (!gradle.loadProperties.any((e) => e.name == "configProperties")) {
gradle.loadProperties.add(
GradleLoadProperties(
path: "config.properties",
name: "configProperties",
file: "configPropertiesFile",
),
);
}
gradle.android?.defaultConfig.minSdkVersion =
"configProperties[\"flutter.minSdkVersion\"].toInteger()";
await gradle.save();
label("Edit AndroidManifest.xml.");
final file = File("android/app/src/main/AndroidManifest.xml");
if (!file.existsSync()) {
throw Exception(
"AndroidManifest does not exist in `android/app/src/main/AndroidManifest.xml`. Do `katana create` to complete the initial setup of the project.",
);
}
final document = XmlDocument.parse(await file.readAsString());
final application = document.findAllElements("application");
if (application.isEmpty) {
throw Exception(
"The structure of AndroidManifest.xml is broken. Do `katana create` to complete the initial setup of the project.",
);
}
if (!application.first.children.any(
(p0) =>
p0 is XmlElement &&
p0.name.toString() == "meta-data" &&
p0.attributes.any(
(p1) =>
p1.name.toString() == "android:name" &&
p1.value == "com.facebook.sdk.ApplicationId",
),
)) {
application.first.children.add(
XmlElement(
XmlName("meta-data"),
[
XmlAttribute(
XmlName("android:name"),
"com.facebook.sdk.ApplicationId",
),
XmlAttribute(
XmlName("android:value"),
"fb$facebookAppId",
),
],
[],
),
);
}
if (!application.first.children.any(
(p0) =>
p0 is XmlElement &&
p0.name.toString() == "meta-data" &&
p0.attributes.any(
(p1) =>
p1.name.toString() == "android:name" &&
p1.value == "com.facebook.sdk.ClientToken",
),
)) {
application.first.children.add(
XmlElement(
XmlName("meta-data"),
[
XmlAttribute(
XmlName("android:name"),
"com.facebook.sdk.ClientToken",
),
XmlAttribute(
XmlName("android:value"),
facebookAppSecret,
),
],
[],
),
);
}
await file.writeAsString(
document.toXmlString(pretty: true, indent: " ", newLine: "\n"),
);
}
if (enableGoogle) {
label("Load google-services.json");
final googleServicesJson = File("android/app/google-services.json");
if (!googleServicesJson.existsSync()) {
throw Exception(
"google-services.json does not exist in `android/app/google-services.json`. Do `katana create` to complete the initial setup of the project.",
);
}
final googleServices = jsonDecodeAsMap(
await googleServicesJson.readAsString(),
);
final oauthClient = googleServices
.getAsList("client")
.cast<DynamicMap>()
.firstOrNull
?.getAsList("oauth_client")
.cast<DynamicMap>()
.firstWhereOrNull((item) => item.get("client_type", 0) == 3);
final clientId = oauthClient.get("client_id", "");
if (clientId.isEmpty) {
throw Exception(
"Could not find `client_id` in `android/app/google-services.json`. File is corrupt.",
);
}
label("Edit index.html");
final indexHtmlFile = File("web/index.html");
if (!indexHtmlFile.existsSync()) {
throw Exception(
"index.html does not exist in `web/index.html`. Do `katana create` to complete the initial setup of the project.",
);
}
final htmlDocument = parse(await indexHtmlFile.readAsString());
final head = htmlDocument.head;
if (head == null) {
throw Exception(
"The structure of index.html is broken. Do `katana create` to complete the initial setup of the project.",
);
}
if (!head.children.any((e0) =>
e0.localName == "meta" &&
e0.attributes["name"] == "google-signin-client_id")) {
head.append(
Element.tag("meta")
..attributes["name"] = "google-signin-client_id"
..attributes["content"] = clientId,
);
}
await indexHtmlFile.writeAsString(htmlDocument.outerHtml);
}
if (enableApple) {
label("Edit Runner.entitlements.");
final runnerEntitlements = File("ios/Runner/Runner.entitlements");
if (!runnerEntitlements.existsSync()) {
await const RunnerEntitlementsCliCode()
.generateFile("Runner.entitlements");
}
final runnerDocument =
XmlDocument.parse(await runnerEntitlements.readAsString());
final dict = runnerDocument.findAllElements("dict").firstOrNull;
if (dict == null) {
throw Exception(
"Could not find `dict` element in `ios/Runner/Info.plist`. File is corrupt.",
);
}
final appleSignIn = dict.children.firstWhereOrNull((p0) {
return p0 is XmlElement &&
p0.name.toString() == "key" &&
p0.innerText == "com.apple.developer.applesignin";
});
if (appleSignIn == null) {
dict.children.add(
XmlElement(
XmlName("key"),
[],
[
XmlText("com.apple.developer.applesignin"),
],
),
);
dict.children.add(
XmlElement(
XmlName("array"),
[],
[
XmlElement(
XmlName("string"),
[],
[
XmlText("Default"),
],
),
],
),
);
await runnerEntitlements.writeAsString(
runnerDocument.toXmlString(
pretty: true, indent: " ", newLine: "\n"),
);
}
label("Edit project.pbxproj");
final xcode = XCode();
await xcode.load();
final fileId = XCode.generateId();
if (!xcode.pbxFileReference.any((e) => e.path == "Runner.entitlements")) {
xcode.pbxFileReference.add(
PBXFileReference(
id: fileId,
comment: "Runner.entitlements",
lastKnownFileType: "text.plist.entitlements",
path: "Runner.entitlements",
sourceTree: "\"<group>\"",
),
);
final runner =
xcode.pbxGroup.firstWhereOrNull((item) => item.comment == "Runner");
if (runner == null) {
throw Exception(
"Could not find `Runner` group in `ios/Runner.xcodeproj/project.pbxproj`. File is corrupt.",
);
}
runner.children
.add(PBXGroupChild(comment: "Runner.entitlements", id: fileId));
for (final buildConfiguration in xcode.pbxBuildConfiguration) {
if (buildConfiguration.buildSettings
.any((e) => e.key == "CODE_SIGN_ENTITLEMENTS")) {
continue;
}
if (buildConfiguration.baseConfigurationReference
?.contains("/* Release.xcconfig */") ==
true ||
buildConfiguration.baseConfigurationReference
?.contains("/* Debug.xcconfig */") ==
true) {
buildConfiguration.buildSettings.add(
PBXBuildConfigurationSettings(
key: "CODE_SIGN_ENTITLEMENTS",
value: "Runner/Runner.entitlements"),
);
}
}
}
await xcode.save();
}
if (enableDeleteUser) {
label("Add firebase functions");
final functions = Fuctions();
await functions.load();
if (!functions.functions.any((e) => e.startsWith("delete_user"))) {
functions.functions.add("delete_user()");
}
await functions.save();
}
}