uploadBuild method

Future<void> uploadBuild({
  1. required AutoRefreshingAuthClient authClient,
  2. required String buildLocation,
})

Implementation

Future<void> uploadBuild({
  required final AutoRefreshingAuthClient authClient,
  required final String buildLocation,
}) async {
  stdout.writeln('Creating version...');
  final versionResponse = await authClient.post(
    Uri(
      scheme: 'https',
      host: 'firebasehosting.googleapis.com',
      path: 'v1beta1/sites/$_projectId/versions',
    ),
    headers: {'Content-Type': 'application/json'},
  );

  if (versionResponse.statusCode != 200) {
    logHttpResponse(versionResponse);
    throw Exception('Error creating version.');
  }
  final versionData =
      json.decode(versionResponse.body) as Map<String, dynamic>;
  final versionName = versionData['name'] as String;

  stdout.writeln('Populating release...');
  final fileMap = <String, String>{};

  await for (final file
      in Directory('build/web').list(recursive: true, followLinks: false)) {
    if (file is File) {
      final fileBytes = await file.readAsBytes();
      final compressedBytes = GZipCodec().encode(fileBytes);
      final compressedHash = sha256.convert(compressedBytes).toString();
      final relativePath = file.path.replaceFirst('build/web/', '');
      final filePath = '/$relativePath';

      fileMap[filePath] = compressedHash;
    }
  }

  final populateResponse = await authClient.post(
    Uri.parse(
      'https://firebasehosting.googleapis.com/v1beta1/$versionName:populateFiles',
    ),
    headers: {'Content-Type': 'application/json'},
    body: json.encode({'files': fileMap}),
  );

  if (populateResponse.statusCode != 200) {
    logHttpResponse(populateResponse);
    throw Exception('Error populating release.');
  }

  final releaseData =
      json.decode(populateResponse.body) as Map<String, dynamic>;
  final uploadUrl = releaseData['uploadUrl'];
  final uploadRequiredHashes = releaseData['uploadRequiredHashes'];

  stdout.writeln('Uploading files...');
  final uploadTasks = <Future>[];

  for (final hash in uploadRequiredHashes) {
    final filePath =
        fileMap.entries.firstWhere((final entry) => entry.value == hash).key;
    final file = File('build/web$filePath');
    final fileBytes = await file.readAsBytes();

    final compressedBytes = GZipCodec().encode(fileBytes);

    final uploadTask = authClient
        .put(
      Uri.parse('$uploadUrl/$hash'),
      headers: {'Content-Type': 'application/octet-stream'},
      body: compressedBytes,
    )
        .then((final response) {
      if (response.statusCode == 200) {
        stdout.writeln('File $filePath uploaded successfully.');
      } else {
        logHttpResponse(response);
      }
    });

    uploadTasks.add(uploadTask);
  }

  await Future.wait(uploadTasks);

  stdout.writeln('All files uploaded. Finalizing deployment...');
  final finalizeResponse = await authClient.patch(
    Uri.parse(
      'https://firebasehosting.googleapis.com/v1beta1/$versionName?update_mask=status',
    ),
    headers: {'Content-Type': 'application/json'},
    body: json.encode({
      'status': 'FINALIZED',
    }),
  );

  if (finalizeResponse.statusCode != 200) {
    logHttpResponse(finalizeResponse);
    throw Exception('Error finalizing deployment.');
  }

  stdout.writeln('Releasing...');
  final releaseResponse = await authClient.post(
    Uri(
      scheme: 'https',
      host: 'firebasehosting.googleapis.com',
      path: 'v1beta1/sites/$_projectId/releases',
      queryParameters: {
        'versionName': versionName,
      },
    ),
    headers: {'Content-Type': 'application/json'},
  );

  if (releaseResponse.statusCode != 200) {
    logHttpResponse(releaseResponse);
    throw Exception('Error releasing version.');
  }

  stdout.writeln('🎉 Deployment completed successfully!\n'
      'Deployed to: https://$_projectId.web.app');
}