up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-24 09:07:40 +02:00
parent 150b3730ef
commit e6119cbe91
59 changed files with 1827 additions and 204 deletions

View File

@@ -1,3 +1,4 @@
using System.IO.Compression;
using System.Text.Json;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
@@ -58,6 +59,7 @@ internal static class NodePackageCollector
}
TraverseTarballs(context, lockData, workspaceIndex, packages, visited, yarnPnpPresent, cancellationToken);
TraverseYarnPnpCache(context, packages, visited, yarnPnpPresent, cancellationToken);
AppendDeclaredPackages(packages, lockData);
@@ -349,6 +351,110 @@ internal static class NodePackageCollector
}
}
private static void TraverseYarnPnpCache(
LanguageAnalyzerContext context,
List<NodePackage> packages,
HashSet<string> visited,
bool yarnPnpPresent,
CancellationToken cancellationToken)
{
if (!yarnPnpPresent)
{
return;
}
var cacheDirectory = Path.Combine(context.RootPath, ".yarn", "cache");
if (!Directory.Exists(cacheDirectory))
{
return;
}
var enumerationOptions = new EnumerationOptions
{
RecurseSubdirectories = true,
IgnoreInaccessible = true,
AttributesToSkip = FileAttributes.ReparsePoint | FileAttributes.Device
};
foreach (var zipPath in Directory.EnumerateFiles(cacheDirectory, "*.zip", enumerationOptions))
{
cancellationToken.ThrowIfCancellationRequested();
TryProcessZipball(context, zipPath, packages, visited, yarnPnpPresent, cancellationToken);
}
}
private static void TryProcessZipball(
LanguageAnalyzerContext context,
string zipPath,
List<NodePackage> packages,
HashSet<string> visited,
bool yarnPnpPresent,
CancellationToken cancellationToken)
{
try
{
using var archive = ZipFile.OpenRead(zipPath);
var packageEntry = archive.Entries
.FirstOrDefault(entry => entry.FullName.EndsWith("package.json", StringComparison.OrdinalIgnoreCase));
if (packageEntry is null || packageEntry.Length == 0)
{
return;
}
using var entryStream = packageEntry.Open();
using var buffer = new MemoryStream();
entryStream.CopyTo(buffer);
buffer.Position = 0;
var sha256 = SHA256.HashData(buffer.ToArray());
var sha256Hex = Convert.ToHexString(sha256).ToLowerInvariant();
buffer.Position = 0;
using var document = JsonDocument.Parse(buffer);
var root = document.RootElement;
var relativeDirectory = NormalizeRelativeDirectoryZip(context, zipPath);
var locator = BuildZipLocator(context, zipPath, packageEntry.FullName);
var usedByEntrypoint = context.UsageHints.IsPathUsed(zipPath);
var package = TryCreatePackageFromJson(
context,
root,
relativeDirectory,
locator,
usedByEntrypoint,
cancellationToken,
lockData: null,
workspaceIndex: null,
packageJsonPath: null,
packageSha256: sha256Hex,
yarnPnpPresent: yarnPnpPresent);
if (package is null)
{
return;
}
if (visited.Add($"zip::{locator}"))
{
packages.Add(package);
}
}
catch (IOException)
{
// ignore unreadable zipballs
}
catch (InvalidDataException)
{
// ignore invalid zip payloads
}
catch (JsonException)
{
// ignore malformed package definitions in zips
}
}
private static void AppendDeclaredPackages(List<NodePackage> packages, NodeLockData lockData)
{
if (lockData.DeclaredPackages.Count == 0)
@@ -572,6 +678,17 @@ internal static class NodePackageCollector
return $"{normalizedArchive}!{normalizedEntry}";
}
private static string BuildZipLocator(LanguageAnalyzerContext context, string zipPath, string entryName)
{
var relative = context.GetRelativePath(zipPath);
var normalizedArchive = string.IsNullOrWhiteSpace(relative) || relative == "."
? Path.GetFileName(zipPath)
: relative.Replace(Path.DirectorySeparatorChar, '/');
var normalizedEntry = entryName.Replace('\\', '/');
return $"{normalizedArchive}!{normalizedEntry}";
}
private static string NormalizeRelativeDirectoryTar(LanguageAnalyzerContext context, string tgzPath)
{
var relative = context.GetRelativePath(Path.GetDirectoryName(tgzPath)!);
@@ -583,6 +700,17 @@ internal static class NodePackageCollector
return relative.Replace(Path.DirectorySeparatorChar, '/');
}
private static string NormalizeRelativeDirectoryZip(LanguageAnalyzerContext context, string zipPath)
{
var relative = context.GetRelativePath(Path.GetDirectoryName(zipPath)!);
if (string.IsNullOrEmpty(relative) || relative == ".")
{
return "zip";
}
return relative.Replace(Path.DirectorySeparatorChar, '/');
}
private static bool ShouldSkipDirectory(string name)
{
if (name.Length == 0)