cloneStarterKitZipCached function

Future<void> cloneStarterKitZipCached(
  1. String projectName, {
  2. String? repoUrl,
  3. String? tag,
  4. bool forceDownload = false,
  5. bool verbose = false,
})

Clone starter kit dari GitHub dengan caching ZIP repoUrl = optional custom repo tag = optional tag atau branch forceDownload = jika true, download ulang meskipun cache ada

Implementation

Future<void> cloneStarterKitZipCached(
  String projectName, {
  String? repoUrl,
  String? tag,
  bool forceDownload = false,
  bool verbose = false,
}) async {
  // Default repo
  final defaultRepo = 'https://github.com/lyrihkaesa/flutter_starter_kit';
  final repo = repoUrl ?? defaultRepo;

  // Tentukan cache directory
  final homeDir = Platform.isWindows ? Platform.environment['USERPROFILE'] : Platform.environment['HOME'];
  final cacheDir = Directory('${homeDir ?? '.'}/.flast_cache');
  if (!cacheDir.existsSync()) cacheDir.createSync(recursive: true);

// Gunakan sanitizeTag
  final formattedTag = sanitizeTag(tag);

// Tentukan nama ZIP sesuai repo/tag
  String zipFileName;
  if (repoUrl == null || repoUrl == defaultRepo) {
    zipFileName = formattedTag != 'main' ? 'kit_${formattedTag.replaceAll('/', '_')}.zip' : 'kit_main.zip';
  } else {
    final uri = Uri.parse(repo);
    final segments = uri.pathSegments.where((s) => s.isNotEmpty).toList();
    String repoPart = 'custom_repo';
    if (segments.length >= 2) {
      repoPart = '${segments[0]}_${segments[1].replaceAll('.git', '')}';
    }
    zipFileName =
        formattedTag != 'main' ? '${repoPart}_${formattedTag.replaceAll('/', '_')}.zip' : '${repoPart}_main.zip';
  }

  final zipFile = File('${cacheDir.path}/$zipFileName');

  // Tentukan URL download
  final downloadUrl =
      formattedTag != 'main' ? '$repo/archive/refs/tags/$formattedTag.zip' : '$repo/archive/refs/heads/main.zip';

  // Download jika belum ada atau forceDownload = true
  if (!zipFile.existsSync() || forceDownload) {
    if (forceDownload && zipFile.existsSync()) {
      printBoxMessage('→ Force download enabled, deleting cached file:\n→ ${zipFile.path}');
      zipFile.deleteSync();
    }

    printBoxMessage('↓ Downloading starter kit...\nRepo: $repo\nVersion: ${tag ?? 'main'}\nPath: ${zipFile.path}');
    final result = await Process.run('curl', ['-L', downloadUrl, '-o', zipFile.path]);
    if (result.exitCode != 0) {
      printBoxMessage('○ Failed to download starter kit: \n→ ${result.stderr}');
      exit(1);
    }
    printBoxMessage('→ Download completed: \n→ ${zipFile.path}');
  } else {
    printBoxMessage('→ Using cached starter kit: \n→ ${zipFile.path}');
  }

  // Cek ukuran file ZIP minimal (GitHub 404 biasanya <10KB)
  if (zipFile.lengthSync() < 10 * 1024) {
    printBoxMessage('○ Warning: download kemungkinan gagal, ZIP terlalu kecil.');
    zipFile.deleteSync();
    exit(1);
  }

  // Ekstraksi
  printBoxMessage('→ Extracting starter kit to ./$projectName ...');

  // Cek apakah ZIP valid
  final bytes = zipFile.readAsBytesSync();
  Archive? archive;
  try {
    archive = ZipDecoder().decodeBytes(bytes);
    if (archive.isEmpty) {
      printBoxMessage('○ Warning: ZIP archive kosong, download gagal.');
      zipFile.deleteSync();
      exit(1);
    }
  } catch (e) {
    printBoxMessage('○ Warning: ZIP corrupt, download gagal.');
    zipFile.deleteSync();
    exit(1);
  }

  // Root folder di dalam ZIP (GitHub menaruh semua di folder <repo>-<branch/tag>)
  final rootFolder = archive.first.name.split('/').first;

  for (int i = 0; i < archive.length; i++) {
    final file = archive[i];

    // Hilangkan folder root GitHub
    final relativePath = file.name.startsWith(rootFolder) ? file.name.substring(rootFolder.length + 1) : file.name;
    if (relativePath.isEmpty) continue;

    final outPath = '$projectName/$relativePath';
    if (file.isFile) {
      final outFile = File(outPath);
      outFile.createSync(recursive: true);
      outFile.writeAsBytesSync(file.content as List<int>);
    } else {
      Directory(outPath).createSync(recursive: true);
    }

    // Progress sederhana
    if (verbose) {
      stdout.write('\r    $outPath\n  → Extracting: ${i + 1}/${archive.length} files...');
    } else {
      stdout.write('\r  → Extracting: ${i + 1}/${archive.length} files...');
    }
  }

  stdout.writeln('');
  // Pindah ke project directory
  Directory.current = projectName;
  printBoxMessage('→ Extraction complete!\n→ ${Directory.current.path}');
}