Add unit tests for PackRunAttestation and SealedInstallEnforcer
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
release-manifest-verify / verify (push) Has been cancelled
- Implement comprehensive tests for PackRunAttestationService, covering attestation generation, verification, and event emission. - Add tests for SealedInstallEnforcer to validate sealed install requirements and enforcement logic. - Introduce a MonacoLoaderService stub for testing purposes to prevent Monaco workers/styles from loading during Karma runs.
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.BuildMetadata;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Discovery;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Gradle;
|
||||
using StellaOps.Scanner.Analyzers.Lang.Java.Internal.Maven;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal;
|
||||
|
||||
internal static class JavaLockFileCollector
|
||||
{
|
||||
private static readonly string[] GradleLockPatterns = { "gradle.lockfile" };
|
||||
private static readonly string[] GradleLockPatterns = ["gradle.lockfile"];
|
||||
|
||||
public static async Task<JavaLockData> LoadAsync(LanguageAnalyzerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -15,6 +20,10 @@ internal static class JavaLockFileCollector
|
||||
var entries = new Dictionary<string, JavaLockEntry>(StringComparer.OrdinalIgnoreCase);
|
||||
var root = context.RootPath;
|
||||
|
||||
// Discover all build files
|
||||
var buildFiles = JavaBuildFileDiscovery.Discover(root);
|
||||
|
||||
// Priority 1: Gradle lockfiles (most reliable)
|
||||
foreach (var pattern in GradleLockPatterns)
|
||||
{
|
||||
var lockPath = Path.Combine(root, pattern);
|
||||
@@ -33,15 +42,35 @@ internal static class JavaLockFileCollector
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: If no lockfiles, parse Gradle build files with version catalog
|
||||
if (entries.Count == 0 && buildFiles.UsesGradle && !buildFiles.HasGradleLockFiles)
|
||||
{
|
||||
await ParseGradleBuildFilesAsync(context, buildFiles, entries, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Priority 3: Parse Maven POMs with property resolution
|
||||
foreach (var pomFile in buildFiles.MavenPoms)
|
||||
{
|
||||
await ParsePomWithResolutionAsync(context, pomFile.AbsolutePath, entries, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Fallback: original pom.xml scanning for any POMs not caught by discovery
|
||||
foreach (var pomPath in Directory.EnumerateFiles(root, "pom.xml", SearchOption.AllDirectories))
|
||||
{
|
||||
await ParsePomAsync(context, pomPath, entries, cancellationToken).ConfigureAwait(false);
|
||||
if (!buildFiles.MavenPoms.Any(p => p.AbsolutePath.Equals(pomPath, StringComparison.OrdinalIgnoreCase)))
|
||||
{
|
||||
await ParsePomWithResolutionAsync(context, pomPath, entries, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return entries.Count == 0 ? JavaLockData.Empty : new JavaLockData(entries);
|
||||
}
|
||||
|
||||
private static async Task ParseGradleLockFileAsync(LanguageAnalyzerContext context, string path, IDictionary<string, JavaLockEntry> entries, CancellationToken cancellationToken)
|
||||
private static async Task ParseGradleLockFileAsync(
|
||||
LanguageAnalyzerContext context,
|
||||
string path,
|
||||
IDictionary<string, JavaLockEntry> entries,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var reader = new StreamReader(stream);
|
||||
@@ -52,7 +81,7 @@ internal static class JavaLockFileCollector
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
line = line.Trim();
|
||||
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#", StringComparison.Ordinal))
|
||||
if (string.IsNullOrWhiteSpace(line) || line.StartsWith('#'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -76,6 +105,9 @@ internal static class JavaLockFileCollector
|
||||
continue;
|
||||
}
|
||||
|
||||
var scope = MapGradleConfigurationToScope(configuration);
|
||||
var riskLevel = JavaScopeClassifier.GetRiskLevel(scope);
|
||||
|
||||
var entry = new JavaLockEntry(
|
||||
groupId.Trim(),
|
||||
artifactId.Trim(),
|
||||
@@ -84,13 +116,193 @@ internal static class JavaLockFileCollector
|
||||
NormalizeLocator(context, path),
|
||||
configuration,
|
||||
null,
|
||||
null,
|
||||
scope,
|
||||
riskLevel,
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
entries[entry.Key] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ParsePomAsync(LanguageAnalyzerContext context, string path, IDictionary<string, JavaLockEntry> entries, CancellationToken cancellationToken)
|
||||
private static async Task ParseGradleBuildFilesAsync(
|
||||
LanguageAnalyzerContext context,
|
||||
JavaBuildFiles buildFiles,
|
||||
IDictionary<string, JavaLockEntry> entries,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// Load version catalog if present
|
||||
GradleVersionCatalog? versionCatalog = null;
|
||||
if (buildFiles.HasVersionCatalog)
|
||||
{
|
||||
var catalogFile = buildFiles.VersionCatalogFiles.FirstOrDefault();
|
||||
if (catalogFile is not null)
|
||||
{
|
||||
versionCatalog = await GradleVersionCatalogParser.ParseAsync(
|
||||
catalogFile.AbsolutePath,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Load gradle.properties
|
||||
GradleProperties? gradleProperties = null;
|
||||
var propsFile = buildFiles.GradlePropertiesFiles.FirstOrDefault();
|
||||
if (propsFile is not null)
|
||||
{
|
||||
gradleProperties = await GradlePropertiesParser.ParseAsync(
|
||||
propsFile.AbsolutePath,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Parse Kotlin DSL files
|
||||
foreach (var ktsFile in buildFiles.GradleKotlinFiles)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var buildFile = await GradleKotlinParser.ParseAsync(
|
||||
ktsFile.AbsolutePath,
|
||||
gradleProperties,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
AddGradleDependencies(context, buildFile, versionCatalog, entries);
|
||||
}
|
||||
|
||||
// Parse Groovy DSL files
|
||||
foreach (var groovyFile in buildFiles.GradleGroovyFiles)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var buildFile = await GradleGroovyParser.ParseAsync(
|
||||
groovyFile.AbsolutePath,
|
||||
gradleProperties,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
AddGradleDependencies(context, buildFile, versionCatalog, entries);
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddGradleDependencies(
|
||||
LanguageAnalyzerContext context,
|
||||
GradleBuildFile buildFile,
|
||||
GradleVersionCatalog? versionCatalog,
|
||||
IDictionary<string, JavaLockEntry> entries)
|
||||
{
|
||||
foreach (var dep in buildFile.Dependencies)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(dep.GroupId) || string.IsNullOrWhiteSpace(dep.ArtifactId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var version = dep.Version;
|
||||
|
||||
// Try to resolve from version catalog if version is missing
|
||||
if (string.IsNullOrWhiteSpace(version) && versionCatalog is not null)
|
||||
{
|
||||
// Check if this dependency matches a catalog library
|
||||
var catalogLib = versionCatalog.Libraries.Values
|
||||
.FirstOrDefault(l =>
|
||||
l.GroupId.Equals(dep.GroupId, StringComparison.OrdinalIgnoreCase) &&
|
||||
l.ArtifactId.Equals(dep.ArtifactId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
version = catalogLib?.Version;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var scope = dep.Scope ?? "compile";
|
||||
var riskLevel = JavaScopeClassifier.GetRiskLevel(scope);
|
||||
|
||||
var entry = new JavaLockEntry(
|
||||
dep.GroupId,
|
||||
dep.ArtifactId,
|
||||
version,
|
||||
Path.GetFileName(buildFile.SourcePath),
|
||||
NormalizeLocator(context, buildFile.SourcePath),
|
||||
scope,
|
||||
null,
|
||||
null,
|
||||
scope,
|
||||
riskLevel,
|
||||
dep.VersionSource.ToString().ToLowerInvariant(),
|
||||
dep.VersionProperty,
|
||||
null);
|
||||
|
||||
entries.TryAdd(entry.Key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ParsePomWithResolutionAsync(
|
||||
LanguageAnalyzerContext context,
|
||||
string path,
|
||||
IDictionary<string, JavaLockEntry> entries,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pom = await MavenPomParser.ParseAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
if (pom == MavenPom.Empty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Build effective POM with property resolution
|
||||
var effectivePomBuilder = new MavenEffectivePomBuilder(context.RootPath);
|
||||
var effectivePom = await effectivePomBuilder.BuildAsync(pom, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var dep in effectivePom.ResolvedDependencies)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(dep.GroupId) ||
|
||||
string.IsNullOrWhiteSpace(dep.ArtifactId) ||
|
||||
string.IsNullOrWhiteSpace(dep.Version) ||
|
||||
!dep.IsVersionResolved)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var scope = dep.Scope ?? "compile";
|
||||
var riskLevel = JavaScopeClassifier.GetRiskLevel(scope);
|
||||
|
||||
// Get license info if available
|
||||
var license = effectivePom.Licenses.FirstOrDefault();
|
||||
|
||||
var entry = new JavaLockEntry(
|
||||
dep.GroupId,
|
||||
dep.ArtifactId,
|
||||
dep.Version,
|
||||
"pom.xml",
|
||||
NormalizeLocator(context, path),
|
||||
scope,
|
||||
null,
|
||||
null,
|
||||
scope,
|
||||
riskLevel,
|
||||
dep.VersionSource.ToString().ToLowerInvariant(),
|
||||
dep.VersionProperty,
|
||||
license?.SpdxId);
|
||||
|
||||
entries.TryAdd(entry.Key, entry);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fall back to simple parsing if resolution fails
|
||||
await ParsePomSimpleAsync(context, path, entries, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ParsePomSimpleAsync(
|
||||
LanguageAnalyzerContext context,
|
||||
string path,
|
||||
IDictionary<string, JavaLockEntry> entries,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
var document = await XDocument.LoadAsync(stream, LoadOptions.None, cancellationToken).ConfigureAwait(false);
|
||||
@@ -117,6 +329,9 @@ internal static class JavaLockFileCollector
|
||||
continue;
|
||||
}
|
||||
|
||||
scope ??= "compile";
|
||||
var riskLevel = JavaScopeClassifier.GetRiskLevel(scope);
|
||||
|
||||
var entry = new JavaLockEntry(
|
||||
groupId,
|
||||
artifactId,
|
||||
@@ -125,12 +340,49 @@ internal static class JavaLockFileCollector
|
||||
NormalizeLocator(context, path),
|
||||
scope,
|
||||
repository,
|
||||
null,
|
||||
scope,
|
||||
riskLevel,
|
||||
"direct",
|
||||
null,
|
||||
null);
|
||||
|
||||
entries[entry.Key] = entry;
|
||||
entries.TryAdd(entry.Key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? MapGradleConfigurationToScope(string? configuration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configuration))
|
||||
{
|
||||
return "compile";
|
||||
}
|
||||
|
||||
// Parse configuration like "compileClasspath,runtimeClasspath"
|
||||
var configs = configuration.Split(',', StringSplitOptions.TrimEntries);
|
||||
|
||||
foreach (var config in configs)
|
||||
{
|
||||
var scope = config.ToLowerInvariant() switch
|
||||
{
|
||||
"compileclasspath" or "implementation" or "api" => "compile",
|
||||
"runtimeclasspath" or "runtimeonly" => "runtime",
|
||||
"testcompileclasspath" or "testimplementation" => "test",
|
||||
"testruntimeclasspath" or "testruntimeonly" => "test",
|
||||
"compileonly" => "provided",
|
||||
"annotationprocessor" => "compile",
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (scope is not null)
|
||||
{
|
||||
return scope;
|
||||
}
|
||||
}
|
||||
|
||||
return "compile";
|
||||
}
|
||||
|
||||
private static string NormalizeLocator(LanguageAnalyzerContext context, string path)
|
||||
=> context.GetRelativePath(path).Replace('\\', '/');
|
||||
}
|
||||
@@ -143,7 +395,12 @@ internal sealed record JavaLockEntry(
|
||||
string Locator,
|
||||
string? Configuration,
|
||||
string? Repository,
|
||||
string? ResolvedUrl)
|
||||
string? ResolvedUrl,
|
||||
string? Scope,
|
||||
string? RiskLevel,
|
||||
string? VersionSource,
|
||||
string? VersionProperty,
|
||||
string? License)
|
||||
{
|
||||
public string Key => BuildKey(GroupId, ArtifactId, Version);
|
||||
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java.Internal.Maven;
|
||||
|
||||
/// <summary>
|
||||
/// Discovers and accesses the local Maven repository (~/.m2/repository).
|
||||
/// </summary>
|
||||
internal sealed class MavenLocalRepository
|
||||
{
|
||||
private readonly string? _repositoryPath;
|
||||
|
||||
public MavenLocalRepository()
|
||||
{
|
||||
_repositoryPath = DiscoverRepositoryPath();
|
||||
}
|
||||
|
||||
public MavenLocalRepository(string repositoryPath)
|
||||
{
|
||||
_repositoryPath = repositoryPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the repository path, or null if not found.
|
||||
/// </summary>
|
||||
public string? RepositoryPath => _repositoryPath;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the local repository exists.
|
||||
/// </summary>
|
||||
public bool Exists => _repositoryPath is not null && Directory.Exists(_repositoryPath);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to a POM file in the local repository.
|
||||
/// </summary>
|
||||
public string? GetPomPath(string groupId, string artifactId, string version)
|
||||
{
|
||||
if (_repositoryPath is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var relativePath = GetRelativePath(groupId, artifactId, version, $"{artifactId}-{version}.pom");
|
||||
return Path.Combine(_repositoryPath, relativePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the path to a JAR file in the local repository.
|
||||
/// </summary>
|
||||
public string? GetJarPath(string groupId, string artifactId, string version, string? classifier = null)
|
||||
{
|
||||
if (_repositoryPath is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var fileName = classifier is null
|
||||
? $"{artifactId}-{version}.jar"
|
||||
: $"{artifactId}-{version}-{classifier}.jar";
|
||||
|
||||
var relativePath = GetRelativePath(groupId, artifactId, version, fileName);
|
||||
return Path.Combine(_repositoryPath, relativePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the directory path for an artifact version in the local repository.
|
||||
/// </summary>
|
||||
public string? GetArtifactDirectory(string groupId, string artifactId, string version)
|
||||
{
|
||||
if (_repositoryPath is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var groupPath = groupId.Replace('.', Path.DirectorySeparatorChar);
|
||||
return Path.Combine(_repositoryPath, groupPath, artifactId, version);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a POM exists in the local repository.
|
||||
/// </summary>
|
||||
public bool HasPom(string groupId, string artifactId, string version)
|
||||
{
|
||||
var path = GetPomPath(groupId, artifactId, version);
|
||||
return path is not null && File.Exists(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a JAR exists in the local repository.
|
||||
/// </summary>
|
||||
public bool HasJar(string groupId, string artifactId, string version, string? classifier = null)
|
||||
{
|
||||
var path = GetJarPath(groupId, artifactId, version, classifier);
|
||||
return path is not null && File.Exists(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists all versions of an artifact in the local repository.
|
||||
/// </summary>
|
||||
public IEnumerable<string> GetAvailableVersions(string groupId, string artifactId)
|
||||
{
|
||||
if (_repositoryPath is null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var groupPath = groupId.Replace('.', Path.DirectorySeparatorChar);
|
||||
var artifactDir = Path.Combine(_repositoryPath, groupPath, artifactId);
|
||||
|
||||
if (!Directory.Exists(artifactDir))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
foreach (var versionDir in Directory.EnumerateDirectories(artifactDir))
|
||||
{
|
||||
var version = Path.GetFileName(versionDir);
|
||||
var pomPath = Path.Combine(versionDir, $"{artifactId}-{version}.pom");
|
||||
|
||||
if (File.Exists(pomPath))
|
||||
{
|
||||
yield return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a POM from the local repository.
|
||||
/// </summary>
|
||||
public async Task<MavenPom?> ReadPomAsync(
|
||||
string groupId,
|
||||
string artifactId,
|
||||
string version,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var path = GetPomPath(groupId, artifactId, version);
|
||||
if (path is null || !File.Exists(path))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await MavenPomParser.ParseAsync(path, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string GetRelativePath(string groupId, string artifactId, string version, string fileName)
|
||||
{
|
||||
var groupPath = groupId.Replace('.', Path.DirectorySeparatorChar);
|
||||
return Path.Combine(groupPath, artifactId, version, fileName);
|
||||
}
|
||||
|
||||
private static string? DiscoverRepositoryPath()
|
||||
{
|
||||
// Check M2_REPO environment variable
|
||||
var m2Repo = Environment.GetEnvironmentVariable("M2_REPO");
|
||||
if (!string.IsNullOrEmpty(m2Repo) && Directory.Exists(m2Repo))
|
||||
{
|
||||
return m2Repo;
|
||||
}
|
||||
|
||||
// Check MAVEN_REPOSITORY environment variable
|
||||
var mavenRepo = Environment.GetEnvironmentVariable("MAVEN_REPOSITORY");
|
||||
if (!string.IsNullOrEmpty(mavenRepo) && Directory.Exists(mavenRepo))
|
||||
{
|
||||
return mavenRepo;
|
||||
}
|
||||
|
||||
// Check for custom settings in ~/.m2/settings.xml
|
||||
var settingsPath = GetSettingsPath();
|
||||
if (settingsPath is not null)
|
||||
{
|
||||
var customPath = TryParseLocalRepositoryFromSettings(settingsPath);
|
||||
if (!string.IsNullOrEmpty(customPath) && Directory.Exists(customPath))
|
||||
{
|
||||
return customPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: ~/.m2/repository
|
||||
var userHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var defaultPath = Path.Combine(userHome, ".m2", "repository");
|
||||
|
||||
return Directory.Exists(defaultPath) ? defaultPath : null;
|
||||
}
|
||||
|
||||
private static string? GetSettingsPath()
|
||||
{
|
||||
var userHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var settingsPath = Path.Combine(userHome, ".m2", "settings.xml");
|
||||
|
||||
return File.Exists(settingsPath) ? settingsPath : null;
|
||||
}
|
||||
|
||||
private static string? TryParseLocalRepositoryFromSettings(string settingsPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = File.ReadAllText(settingsPath);
|
||||
var startTag = "<localRepository>";
|
||||
var endTag = "</localRepository>";
|
||||
|
||||
var startIndex = content.IndexOf(startTag, StringComparison.OrdinalIgnoreCase);
|
||||
if (startIndex < 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
startIndex += startTag.Length;
|
||||
var endIndex = content.IndexOf(endTag, startIndex, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (endIndex > startIndex)
|
||||
{
|
||||
var path = content[startIndex..endIndex].Trim();
|
||||
|
||||
// Expand environment variables
|
||||
path = Environment.ExpandEnvironmentVariables(path);
|
||||
|
||||
// Handle ${user.home}
|
||||
var userHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
path = path.Replace("${user.home}", userHome, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore parsing errors
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang.Java;
|
||||
|
||||
/// <summary>
|
||||
/// Minimal stub for shaded JAR analysis results pending full Postgres migration cleanup.
|
||||
/// </summary>
|
||||
internal sealed record ShadedJarAnalysisResult(
|
||||
bool IsShaded,
|
||||
double Confidence,
|
||||
IReadOnlyList<string> Markers,
|
||||
IReadOnlyList<string> EmbeddedArtifacts,
|
||||
IReadOnlyList<string> RelocatedPrefixes)
|
||||
{
|
||||
public static ShadedJarAnalysisResult None { get; } =
|
||||
new(false, 0, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user