Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ test_generation/
test_generation_dev/

# melos
pubspec_overrides.yaml
pubspec_overrides.yaml

context/
56 changes: 28 additions & 28 deletions packages/flutterfire_cli/lib/src/firebase.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Future<String?> getDefaultFirebaseProjectId() async {
final fileContents = firebaseRcFile.readAsStringSync();
try {
final jsonMap =
const JsonDecoder().convert(fileContents) as Map<String, dynamic>;
const JsonDecoder().convert(fileContents) as Map<String, dynamic>;
if (jsonMap['projects'] != null &&
(jsonMap['projects'] as Map)['default'] != null) {
return (jsonMap['projects'] as Map)['default'] as String;
Expand All @@ -67,11 +67,11 @@ Future<String?> getDefaultFirebaseProjectId() async {
/// final result = await runFirebaseCommand(['projects:list']);
/// print(result);
Future<Map<String, dynamic>> runFirebaseCommand(
List<String> commandAndArgs, {
String? project,
String? account,
String? serviceAccount,
}) async {
List<String> commandAndArgs, {
String? project,
String? account,
String? serviceAccount,
}) async {
final cliExists = await exists();
if (!cliExists) {
throw FirebaseCommandException(
Expand Down Expand Up @@ -116,7 +116,7 @@ Future<Map<String, dynamic>> runFirebaseCommand(
if (jsonString.length > characterLimit) {
// If the JSON string is large, write it to a temporary file
final tempFile =
File('${Directory.systemTemp.path}/firebase_output.json');
File('${Directory.systemTemp.path}/firebase_output.json');
await tempFile.writeAsString(jsonString);

// Read from the temporary file to create a Dart object
Expand Down Expand Up @@ -171,8 +171,8 @@ Future<List<FirebaseProject>> getProjects({
return result
.map<FirebaseProject>(
(Map<String, dynamic> e) =>
FirebaseProject.fromJson(Map<String, dynamic>.from(e)),
)
FirebaseProject.fromJson(Map<String, dynamic>.from(e)),
)
.where((project) => project.state == 'ACTIVE')
.toList();
} catch (e) {
Expand Down Expand Up @@ -231,8 +231,8 @@ Future<List<FirebaseApp>> getApps({
return result
.map<FirebaseApp>(
(Map<String, dynamic> e) =>
FirebaseApp.fromJson(Map<String, dynamic>.from(e)),
)
FirebaseApp.fromJson(Map<String, dynamic>.from(e)),
)
.toList();
}

Expand Down Expand Up @@ -300,7 +300,7 @@ Future<FirebaseApp> findOrCreateFirebaseApp({

_assertFirebaseSupportedPlatform(platformFirebase);
final fetchingAppsSpinner = spinner(
(done) {
(done) {
final loggingAppName =
packageNameOrBundleIdentifier ?? webAppId ?? displayNameWithPlatform;
if (!done) {
Expand Down Expand Up @@ -333,7 +333,7 @@ Future<FirebaseApp> findOrCreateFirebaseApp({
final flagOption = platform == kWeb ? kWebAppIdFlag : kWindowsAppIdFlag;
// Find provided web app id for web and windows, otherwise, throw Exception that it doesn't exist
final webApp = unfilteredFirebaseApps.firstWhere(
(firebaseApp) => firebaseApp.appId == webAppId,
(firebaseApp) => firebaseApp.appId == webAppId,
orElse: () {
fetchingAppsSpinner.done();
throw Exception(
Expand All @@ -347,7 +347,7 @@ Future<FirebaseApp> findOrCreateFirebaseApp({
}
// Find web app for web and windows using display name with this signature: "flutter_app_name (platform)
filteredFirebaseApps = unfilteredFirebaseApps.where(
(firebaseApp) {
(firebaseApp) {
if (firebaseApp.displayName == displayNameWithPlatform) {
return true;
}
Expand All @@ -357,17 +357,17 @@ Future<FirebaseApp> findOrCreateFirebaseApp({
// Find any for that platform if no web app found with display name
if (filteredFirebaseApps.isEmpty) {
filteredFirebaseApps = unfilteredFirebaseApps.where(
(firebaseApp) {
(firebaseApp) {
return firebaseApp.platform == platform;
},
);
}
} else {
filteredFirebaseApps = unfilteredFirebaseApps.where(
(firebaseApp) {
(firebaseApp) {
if (packageNameOrBundleIdentifier != null) {
return firebaseApp.packageNameOrBundleIdentifier ==
packageNameOrBundleIdentifier &&
packageNameOrBundleIdentifier &&
firebaseApp.platform == platformFirebase;
}
return false;
Expand Down Expand Up @@ -405,7 +405,7 @@ Future<FirebaseApp> findOrCreateFirebaseApp({
);
break;
case kWeb:
// This is used to also create windows app, Firebase has no concept of a windows app
// This is used to also create windows app, Firebase has no concept of a windows app
createFirebaseAppFuture = createWebApp(
project: project,
displayName: displayNameWithPlatform,
Expand All @@ -419,7 +419,7 @@ Future<FirebaseApp> findOrCreateFirebaseApp({
}

final creatingAppSpinner = spinner(
(done) {
(done) {
if (!done) {
return AnsiStyles.bold(
'Registering new Firebase ${AnsiStyles.cyan(platform)} app on Firebase project ${AnsiStyles.cyan(project)}.',
Expand Down Expand Up @@ -519,7 +519,7 @@ Future<String> getAccessToken() async {
: Platform.environment['HOME']!;
// Path to 'firebase-tools.json'
final configPath =
path.join(homeDir, '.config', 'configstore', 'firebase-tools.json');
path.join(homeDir, '.config', 'configstore', 'firebase-tools.json');
final configFile = File(configPath);
if (!configFile.existsSync()) {
throw Exception(
Expand All @@ -536,7 +536,7 @@ Future<String> getAccessToken() async {
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
// Values for obtaining the access token are taken from the Firebase CLI source code: https://github.com/firebase/firebase-tools/blob/b14b5f38fe23da6543778a588811b0e2391427c0/src/api.ts#L18
body:
'grant_type=refresh_token&client_id=563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com&client_secret=j9iVZfS8kkCEFUPaAeJV0sAi&refresh_token=$refreshToken',
'grant_type=refresh_token&client_id=563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com&client_secret=j9iVZfS8kkCEFUPaAeJV0sAi&refresh_token=$refreshToken',
);

if (response.statusCode == 200) {
Expand All @@ -551,19 +551,19 @@ Future<String> getAccessToken() async {

// Return string value of "GoogleService-Info.plist" or "google-services.json" file for relevant platform
Future<String> getServiceFileContent(
String projectId,
String appId,
String accessToken,
String platform,
) async {
String projectId,
String appId,
String accessToken,
String platform,
) async {
String? uri;

if (platform == kIos || platform == kMacos) {
uri =
'https://firebase.googleapis.com/v1beta1/projects/$projectId/iosApps/$appId/config';
'https://firebase.googleapis.com/v1beta1/projects/$projectId/iosApps/$appId/config';
} else if (platform == kAndroid) {
uri =
'https://firebase.googleapis.com/v1beta1/projects/$projectId/androidApps/$appId/config';
'https://firebase.googleapis.com/v1beta1/projects/$projectId/androidApps/$appId/config';
} else {
throw ServiceFileException(
platform,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ String _debugSymbolsScript(
require 'xcodeproj'
xcodeFile='${getXcodeProjectPath(platform)}'
runScriptName='$debugSymbolScriptName'
bundleScriptName='$bundleServiceScriptName'
project = Xcodeproj::Project.open(xcodeFile)


Expand All @@ -410,21 +411,74 @@ fi
${isDevDependency ? 'dart run flutterfire_cli:flutterfire' : 'flutterfire'} upload-crashlytics-symbols --upload-symbols-script-path="\$PATH_TO_CRASHLYTICS_UPLOAD_SCRIPT" --platform=$platform --apple-project-path="\${SRCROOT}" --env-platform-name="\${PLATFORM_NAME}" --env-configuration="\${CONFIGURATION}" --env-project-dir="\${PROJECT_DIR}" --env-built-products-dir="\${BUILT_PRODUCTS_DIR}" --env-dwarf-dsym-folder-path="\${DWARF_DSYM_FOLDER_PATH}" --env-dwarf-dsym-file-name="\${DWARF_DSYM_FILE_NAME}" --env-infoplist-path="\${INFOPLIST_PATH}" $projectType
)

def ensure_phase_is_after(target, phase, preceding_phase)
return if preceding_phase.nil?

allPhases = target.build_phases
precedingIndex = allPhases.index(preceding_phase)
phaseIndex = allPhases.index(phase)

return if precedingIndex.nil? || phaseIndex.nil?
return if phaseIndex == precedingIndex + 1

target.build_phases.delete(phase)

insertionIndex =
if phaseIndex < precedingIndex
precedingIndex
else
precedingIndex + 1
end

target.build_phases.insert(insertionIndex, phase)
end

for target in project.targets
if (target.name == '$target')
# Find existing debug symbols phase
phase = target.shell_script_build_phases().find do |item|
if defined? item && item.name
item.name == runScriptName
end
end

# Find bundle-service-file phase to determine insertion position
bundlePhase = target.shell_script_build_phases().find do |item|
if defined? item && item.name
item.name == bundleScriptName
end
end

if phase.nil?
# Create new phase
phase = target.new_shell_script_build_phase(runScriptName)
phase.shell_script = bashScript

# If bundle-service-file exists, ensure debug symbols is placed right after it
if (!bundlePhase.nil?)
ensure_phase_is_after(target, phase, bundlePhase)
end

project.save()
elsif phase.shell_script != bashScript
# Update existing phase
phase.shell_script = bashScript

# Ensure correct ordering: debug symbols should be right after bundle-service-file
if (!bundlePhase.nil?)
ensure_phase_is_after(target, phase, bundlePhase)
end

project.save()
else
# Script exists and content is correct, but check ordering
if (!bundlePhase.nil?)
currentOrdering = target.build_phases.dup
ensure_phase_is_after(target, phase, bundlePhase)
if (target.build_phases != currentOrdering)
project.save()
end
end
end
end
end
Expand Down
79 changes: 79 additions & 0 deletions packages/flutterfire_cli/test/configure_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,85 @@ void main() {
),
);

test(
'flutterfire configure: build configuration - verify script ordering after second configure',
() async {
// Add crashlytics dependency so debug symbols script gets added
final addCrashlyticsResult = Process.runSync(
'flutter',
['pub', 'add', 'firebase_crashlytics'],
workingDirectory: projectPath,
);

if (addCrashlyticsResult.exitCode != 0) {
fail(addCrashlyticsResult.stderr as String);
}

// First configure run
final result = Process.runSync(
'flutterfire',
[
'configure',
'--yes',
'--project=$firebaseProjectId',
'--platforms=macos',
'--macos-out=macos/$buildType',
'--macos-build-config=$appleBuildConfiguration',
],
workingDirectory: projectPath,
runInShell: true,
);

if (result.exitCode != 0) {
fail(result.stderr as String);
}

// Second configure run - this should trigger reordering if needed
final result2 = Process.runSync(
'flutterfire',
[
'configure',
'--yes',
'--project=$firebaseProjectId',
'--platforms=macos',
'--macos-out=macos/$buildType',
'--macos-build-config=$appleBuildConfiguration',
],
workingDirectory: projectPath,
runInShell: true,
);

if (result2.exitCode != 0) {
fail(result2.stderr as String);
}

// Verify script ordering for macOS
final scriptOrderCheckMacos = rubyScriptForCheckingScriptOrdering(
projectPath!,
kMacos,
);

final macosOrderResult = Process.runSync(
'ruby',
[
'-e',
scriptOrderCheckMacos,
],
runInShell: true,
);

if (macosOrderResult.exitCode != 0) {
fail(macosOrderResult.stderr as String);
}

expect(macosOrderResult.stdout, 'success');
},
skip: !Platform.isMacOS,
timeout: const Timeout(
Duration(minutes: 2),
),
);

test(
'flutterfire configure: android - "default" Apple - "target"',
() async {
Expand Down
Loading
Loading