execute method

  1. @override
Future<ItemResult> execute(
  1. CommandContext context,
  2. CliArgs args
)

Execute command on a single folder.

Called by ToolRunner for each folder during traversal. Returns an ItemResult describing the outcome.

Implementation

@override
Future<ItemResult> execute(CommandContext context, CliArgs args) async {
  final projectPath = context.path;
  final verbose = args.verbose;
  final checkOnly = _flag(args, 'check');

  // Resolve the input targets for this project (step-3 precedence:
  // positional args > buildkit.yaml section > build.yaml fallback).
  final resolved = _resolveTargets(projectPath, args);

  // Skip-success when the project opts out (no targets from any source).
  if (resolved.targets.isEmpty) {
    if (verbose) {
      print('  ${context.relativePath}: no reflection targets, skipping');
    }
    return ItemResult.success(path: projectPath, name: context.name);
  }

  // Handle --list mode: report the project and stop.
  if (args.listOnly) {
    print('  ${context.relativePath}');
    return ItemResult.success(path: projectPath, name: context.name);
  }

  try {
    final result = await generateReflection(
      projectRoot: projectPath,
      targets: resolved.targets,
      options: ReflectionGenerationOptions(
        packageName: resolved.packageName,
        outputExtension: resolved.outputExtension,
        useAllCapabilities: resolved.useAllCapabilities,
        allMode: resolved.allMode,
        verbose: verbose,
        noCache: _flag(args, 'no-cache'),
        rebuildCache: _flag(args, 'rebuild-cache'),
        showCacheStatus: _flag(args, 'show-cache-status'),
        cacheOnlyPackages: _stringList(args.extraOptions['cache-only']),
        checkOnly: checkOnly,
      ),
    );

    if (result.cacheStatusShown) {
      return ItemResult.success(path: projectPath, name: context.name);
    }

    if (result.noFilesMatched) {
      if (verbose) {
        print('  ${context.relativePath}: no files matched targets');
      }
      return ItemResult.success(path: projectPath, name: context.name);
    }

    if (result.hasFailures) {
      // A resolution/generation crash is a hard failure — surface it so the
      // buildkit item is marked failed rather than silently "succeeded".
      return ItemResult.failure(
        path: projectPath,
        name: context.name,
        error: 'Reflection generation failed for ${result.failedCount} '
            'file(s) (generated ${result.processedCount}, '
            'skipped ${result.skippedCount})',
      );
    }

    if (result.hasStaleOutputs) {
      // The generated file no longer matches its source. Failing the item is
      // the whole purpose of check mode: this drift is invisible to
      // `dart analyze` and to the test suites, so the build is the only
      // place it can be caught.
      return ItemResult.failure(
        path: projectPath,
        name: context.name,
        error: '${result.staleCount} generated file(s) are stale — '
            '${result.staleFiles.join(', ')}. Regenerate and commit.',
      );
    }

    if (checkOnly) {
      return ItemResult.success(
        path: projectPath,
        name: context.name,
        message: 'Up to date ${result.upToDateCount}, '
            'skipped ${result.skippedCount}',
      );
    }

    return ItemResult.success(
      path: projectPath,
      name: context.name,
      message: 'Generated ${result.processedCount}, '
          'skipped ${result.skippedCount}',
    );
  } catch (e, stack) {
    stderr.writeln('Error processing $projectPath: $e');
    if (verbose) stderr.writeln(stack);
    return ItemResult.failure(
      path: projectPath,
      name: context.name,
      error: '$e',
    );
  }
}