run method

  1. @override
Future<int> run()
override

Runs this command.

The return value is wrapped in a Future if necessary and returned by CommandRunner.runCommand.

Implementation

@override
Future<int> run() async {
  try {
    final result = historyStore.prune();
    if (result.warning case final warning?) {
      diagnostics('Warning: $warning');
    }
  } catch (error) {
    diagnostics('Warning: Failed to prune applied history: $error');
  }

  final projectName = argResults!.rest.singleOrNull;
  if (projectName == null) {
    return _error(
      ExitReporter.usageError,
      'MISSING_PROJECT_NAME',
      'Missing required project name.',
      missingDecisions: const ['projectName'],
    );
  }

  _interrupted = false;
  final interruptSubscription = _watchSigint
      ? ProcessSignal.sigint.watch().listen((_) => _interrupted = true)
      : null;
  final probeRoot = doctorProbe == null
      ? await Directory.systemTemp.createTemp('flutterstart-create-probe-')
      : null;
  try {
    final probe =
        doctorProbe ??
        DoctorProbe(
          processRunner: const ProcessRunner(),
          stagingRoot: probeRoot!.path,
        );
    final report = await probe.probe();
    final preflight = doctorBlockingConditions(report);
    if (preflight.isNotEmpty) {
      return _error(
        ExitReporter.environmentPreflightFailure,
        'ENVIRONMENT_PREFLIGHT_FAILURE',
        preflight.join('; '),
      );
    }

    final lock = recipeLock ?? await loadRecipeLock();
    final blueprint = await _collectBlueprint(projectName, lock, report);
    if (blueprint == null) return _collectionExitCode;

    final flutterVersion = report.flutterVersion.value;
    final dartVersion = report.dartVersion.value;
    if (flutterVersion == null || dartVersion == null) {
      return _error(
        ExitReporter.environmentPreflightFailure,
        'TOOLCHAIN_VERSION_UNOBSERVED',
        'Flutter and Dart versions must be observed before planning.',
      );
    }

    final resolvedCatalog = catalog ?? await loadCatalog();
    final baseCompatibility = validateBlueprint(
      blueprint: blueprint,
      flutterVersion: flutterVersion,
      dartVersion: dartVersion,
      lock: lock,
    );
    // Catalog-driven rules layer onto the same CompatibilityResult
    // contract the 0.1 engine already returns (FR-029-FR-033; a v1
    // Blueprint has no selections, so this contributes zero findings).
    final catalogCompatibility = validateBlueprintAgainstCatalog(
      blueprint: blueprint,
      catalog: resolvedCatalog,
      flutterVersion: flutterVersion,
      dartVersion: dartVersion,
      lock: lock,
    );
    final compatibility = CompatibilityResult([
      ...baseCompatibility.findings,
      ...catalogCompatibility.findings,
    ]);
    for (final warning in compatibility.warnings) {
      diagnostics('Warning [${warning.code}]: ${warning.message}');
    }
    if (compatibility.hasBlockers) {
      return _error(
        ExitReporter.blueprintInvalidOrCompatibilityBlocker,
        'COMPATIBILITY_BLOCKER',
        compatibility.blockers.map((finding) => finding.message).join('; '),
      );
    }

    final destination = Directory(
      blueprint.destination,
    ).absolute.uri.normalizePath().toFilePath();
    try {
      return await withSessionLock(
        destination,
        () => _plan(
          blueprint,
          lock,
          report,
          destination,
          compatibility,
          resolvedCatalog,
        ),
        baseDir: sessionLockBaseDir,
      );
    } on SessionLockHeldException catch (error) {
      return _error(error.exitCode, 'SESSION_LOCK_HELD', error.message);
    }
  } finally {
    await interruptSubscription?.cancel();
    if (probeRoot?.existsSync() ?? false) {
      probeRoot!.deleteSync(recursive: true);
    }
  }
}