discover method

WorkspaceDiscoveryResult discover(
  1. String? rootPath
)

Discovers the workspace rooted at rootPath.

Implementation

WorkspaceDiscoveryResult discover(String? rootPath) {
  T measureGlob<T>(T Function() action) {
    return action();
  }

  T measureParse<T>(T Function() action) {
    return action();
  }

  final requestedRoot = rootPath ?? Directory.current.path;
  late final String root;
  try {
    root = Directory(requestedRoot).resolveSymbolicLinksSync();
  } catch (_) {
    root = Directory(requestedRoot).absolute.path;
  }
  final workspacePrefix = root.replaceAll('\\', '/').toLowerCase();

  final configPath = '$root/zuke.yaml';
  final inputContents = <String, String>{};
  final configFile = File(configPath);
  late final ZukeConfig config;
  if (!configFile.existsSync()) {
    throw InvalidWorkspaceConfigError(
      'zuke.yaml not found at ${configFile.path}',
    );
  }
  final configContent = configFile.readAsStringSync();
  inputContents[configFile.path] = configContent;
  config = ZukeConfig.fromYaml(configContent, root: root);

  final errors = <String>[];
  final seenPhysicalPaths = <String, List<String>>{};

  // Parse features
  final parser = GherkinParser();
  final features = <ParsedFeature>[];
  for (final pattern in config.featurePatterns) {
    _checkPatternSafety(pattern, errors);
    final files = measureGlob(
      () => _matchGlob(pattern, root, workspacePrefix: workspacePrefix),
    );
    if (files.isEmpty) {
      errors.add('Feature pattern "$pattern" matched no files');
    }
    for (final f in files) {
      _trackOverlap(f, pattern, seenPhysicalPaths, errors);
      try {
        final (content, result) = measureParse(() {
          final fileContent = File(f).readAsStringSync();
          final parseRes = parser.parseFile(fileContent, f);
          return (fileContent, parseRes);
        });
        inputContents[File(f).path] = content;
        features.addAll(result.features);
        for (final e in result.errors) {
          errors.add('${e.source.file}:${e.source.line}: ${e.message}');
        }
      } catch (e) {
        errors.add('$f: parse failed: $e');
      }
    }
  }

  // Load YAML data
  final epics = <String, Map<String, dynamic>>{};
  for (final pattern in config.epicPatterns) {
    _checkPatternSafety(pattern, errors);
    _loadYamlMap(
      pattern,
      root,
      epics,
      errors,
      seenPhysicalPaths,
      measureGlob: measureGlob,
      measureParse: measureParse,
      workspacePrefix: workspacePrefix,
      inputContents: inputContents,
    );
  }
  final controls = <String, Map<String, dynamic>>{};
  for (final pattern in config.controlPatterns) {
    _checkPatternSafety(pattern, errors);
    _loadYamlListItems(
      pattern,
      root,
      'controls',
      controls,
      errors,
      seenPhysicalPaths,
      measureGlob: measureGlob,
      measureParse: measureParse,
      workspacePrefix: workspacePrefix,
      inputContents: inputContents,
    );
  }
  final registries = <String, Map<String, dynamic>>{};
  for (final pattern in config.registryPatterns) {
    _checkPatternSafety(pattern, errors);
    _loadRegistries(
      pattern,
      root,
      registries,
      errors,
      seenPhysicalPaths,
      measureGlob: measureGlob,
      measureParse: measureParse,
      workspacePrefix: workspacePrefix,
      inputContents: inputContents,
    );
  }
  final policies = <String, Map<String, dynamic>>{};
  if (config.projectPolicy != null) {
    _checkPatternSafety(config.projectPolicy!, errors);
    final files = measureGlob(
      () => _matchGlob(
        config.projectPolicy!,
        root,
        workspacePrefix: workspacePrefix,
      ),
    );
    for (final f in files) {
      _trackOverlap(f, config.projectPolicy!, seenPhysicalPaths, errors);
      measureParse(() {
        _loadPolicyFile(f, policies, errors, inputContents: inputContents);
      });
    }
    if (files.isEmpty) {
      errors.add('Project policy "${config.projectPolicy}" matched no files');
    }
  }
  for (final pattern in config.profilePatterns) {
    _checkPatternSafety(pattern, errors);
    final files = measureGlob(
      () => _matchGlob(pattern, root, workspacePrefix: workspacePrefix),
    );
    for (final f in files) {
      _trackOverlap(f, pattern, seenPhysicalPaths, errors);
      measureParse(() {
        _loadPolicyFile(f, policies, errors, inputContents: inputContents);
      });
    }
  }

  _validateEvidenceRequirements(config, features, errors);

  final inputPatterns = <String>{
    ...config.featurePatterns,
    ...config.epicPatterns,
    ...config.controlPatterns,
    ...config.registryPatterns,
    if (config.projectPolicy != null) config.projectPolicy!,
    ...config.profilePatterns,
  }.toList()..sort();
  final patternInputPaths = inputContents.keys
      .where((path) => path != configFile.path)
      .toSet();

  return WorkspaceDiscoveryResult(
    config: config,
    data: MetadataExtractorResult(
      features: features,
      epics: epics,
      controls: controls,
      registries: registries,
      policies: policies,
      errors: errors,
    ),
    inputContents: Map.unmodifiable(inputContents),
    inputPatterns: List.unmodifiable(inputPatterns),
    patternInputPaths: Set.unmodifiable(patternInputPaths),
  );
}