executePipeline static method

Future<void> executePipeline({
  1. String? pipelineName,
})

Execute a named pipeline by key.

If pipelineName is null, uses the first (or only) pipeline. Supported formats: pipelineName1,pipelineName2.

Implementation

static Future<void> executePipeline({String? pipelineName}) async {
  final config = FlutterReleaseXConfig().config;
  final resolvedPipelines = config.resolvedPipelines;

  if (resolvedPipelines == null || resolvedPipelines.isEmpty) {
    print('āŒ No pipelines configured.');
    print('   Add "pipelines:" or "pipeline_steps:" to your config.yaml');
    return;
  }

  // Resolve which pipelines to run
  final List<PipelineModel> pipelinesToRun = [];

  if (pipelineName != null) {
    final names = pipelineName
        .split(',')
        .map((e) => e.trim())
        .where((e) => e.isNotEmpty)
        .toList();

    for (final name in names) {
      if (!resolvedPipelines.containsKey(name)) {
        print('āŒ Pipeline "$name" not found.');
        print('   Available pipelines: ${resolvedPipelines.keys.join(', ')}');
        print(
            '   Use "frx pipeline list" to see all pipelines with descriptions.');
        return;
      }
      pipelinesToRun.add(resolvedPipelines[name]!);
    }
  } else if (resolvedPipelines.length == 1) {
    pipelinesToRun.add(resolvedPipelines.values.first);
  } else {
    // Multiple pipelines, no selection — show interactive menu
    print('\nšŸ“¦ Multiple pipelines available:\n');
    final keys = resolvedPipelines.keys.toList();
    for (int i = 0; i < keys.length; i++) {
      final p = resolvedPipelines[keys[i]]!;
      final desc = p.description != null ? ' — ${p.description}' : '';
      print('   ${i + 1}. ${keys[i]}$desc (${p.steps.length} steps)');
    }
    print('');
    stdout.write(
        'Enter the comma-separated numbers of the pipelines to run (e.g., 2,5,1) or just a single number: ');
    final choice = stdin.readLineSync()?.trim();

    if (choice == null || choice.isEmpty) {
      print('āŒ No pipeline selected. Exiting.');
      return;
    }

    final choices = choice
        .split(',')
        .map((e) => e.trim())
        .where((e) => e.isNotEmpty)
        .toList();

    for (final c in choices) {
      final index = int.tryParse(c);
      if (index == null || index < 1 || index > keys.length) {
        print('āŒ Invalid choice "$c". Expected 1-${keys.length}.');
        return;
      }
      pipelinesToRun.add(resolvedPipelines[keys[index - 1]]!);
    }
  }

  if (pipelinesToRun.isEmpty) {
    print('āŒ No valid pipelines selected. Exiting.');
    return;
  }

  final globalStopwatch = Stopwatch()..start();
  bool globalFailed = false;

  for (int pIndex = 0; pIndex < pipelinesToRun.length; pIndex++) {
    final pipeline = pipelinesToRun[pIndex];

    // Print pipeline header
    print('');
    print(
        '═══════════════════════════════════════════════════════════════════════');
    print(
        '  šŸš€ Pipeline [${pIndex + 1}/${pipelinesToRun.length}]: ${pipeline.name}');
    if (pipeline.description != null) {
      print('  šŸ“ ${pipeline.description}');
    }
    print('  šŸ“Š ${pipeline.steps.length} steps');
    print(
        '═══════════════════════════════════════════════════════════════════════');
    print('');

    final pipelineStopwatch = Stopwatch()..start();
    final results = <_PipelineStepResult>[];
    bool pipelineFailed = false;

    for (int i = 0; i < pipeline.steps.length; i++) {
      final stage = pipeline.steps[i];
      final stepNum = i + 1;
      final totalSteps = pipeline.steps.length;

      print(
          '───────────────────────────────────────────────────────────────────');
      print('  [$stepNum/$totalSteps] ${stage.name}');
      if (stage.description != null) {
        print('  šŸ“ ${stage.description}');
      }
      print(
          '───────────────────────────────────────────────────────────────────');

      final result = await _executeStep(stage);
      results.add(result);

      if (result.status == _StepStatus.failed) {
        pipelineFailed = true;

        // Check if we should continue despite failure
        final shouldContinue = stage.continueOnError || stage.allowFailure;
        if (!shouldContinue) {
          print('');
          print(
              'āŒ Pipeline halted at step "${stage.name}". Use continue_on_error: true to skip failures.');
          break;
        }
        print(
            '   āš ļø Step failed but continue_on_error is enabled — continuing pipeline');
      }

      // Upload artifact if configured
      if ((result.status == _StepStatus.passed ||
              result.status == _StepStatus.warning) &&
          stage.uploadOutput &&
          stage.outputPath != null) {
        // Verify output file exists before upload
        final outputFile = File(stage.outputPath!);
        final outputDir = Directory(stage.outputPath!);
        if (outputFile.existsSync() || outputDir.existsSync()) {
          await flutterReleaseXpromptUploadOption(stage.outputPath!);
          await generateQrCodeAndLink();
        } else {
          print(
              '   āš ļø output_path "${stage.outputPath}" not found after step completed. Skipping upload.');
        }
      }

      // Notify Slack if configured
      if (stage.notifySlack &&
          (result.status == _StepStatus.passed ||
              result.status == _StepStatus.warning)) {
        await notifySlack();
      }

      // Notify Teams if configured
      if (stage.notifyTeams &&
          (result.status == _StepStatus.passed ||
              result.status == _StepStatus.warning)) {
        await notifyTeams();
      }

      print('');
    }

    pipelineStopwatch.stop();

    // Print pipeline summary table
    _printPipelineSummary(
      pipelineName: pipeline.name,
      results: results,
      totalDuration: pipelineStopwatch.elapsed,
      pipelineFailed: pipelineFailed,
    );

    if (pipelineFailed) {
      globalFailed = true;
      print(
          '\nāŒ Stopping remaining pipelines due to failure in "${pipeline.name}".');
      break;
    }
  }

  globalStopwatch.stop();

  if (pipelinesToRun.length > 1) {
    print('');
    print(
        '═══════════════════════════════════════════════════════════════════════');
    if (globalFailed) {
      print('  āŒ Global Execution FAILED');
    } else {
      print('  šŸŽ‰ All Pipelines Completed Successfully!');
    }
    print('  ā±ļø Total Time: ${_formatDuration(globalStopwatch.elapsed)}');
    print(
        '═══════════════════════════════════════════════════════════════════════');
    print('');
  }
}