Files
git.stella-ops.org/src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang.Node/Internal/NodeEnvironmentScanner.cs
master 75f6942769
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Add integration tests for migration categories and execution
- Implemented MigrationCategoryTests to validate migration categorization for startup, release, seed, and data migrations.
- Added tests for edge cases, including null, empty, and whitespace migration names.
- Created StartupMigrationHostTests to verify the behavior of the migration host with real PostgreSQL instances using Testcontainers.
- Included tests for migration execution, schema creation, and handling of pending release migrations.
- Added SQL migration files for testing: creating a test table, adding a column, a release migration, and seeding data.
2025-12-04 19:10:54 +02:00

127 lines
4.2 KiB
C#

using System.Globalization;
using System.Text.RegularExpressions;
namespace StellaOps.Scanner.Analyzers.Lang.Node.Internal;
internal static class NodeEnvironmentScanner
{
private static readonly Regex EnvAssign = new(@"^\s*(ENV|ARG)\s+NODE_OPTIONS\s*(=|\s)(?<value>.+)$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public static IReadOnlyList<LanguageComponentRecord> Scan(LanguageAnalyzerContext context, IReadOnlyList<string> sourceRoots, CancellationToken cancellationToken)
{
var warnings = new List<LanguageComponentRecord>();
foreach (var root in sourceRoots)
{
cancellationToken.ThrowIfCancellationRequested();
var dockerfile = Path.Combine(root, "Dockerfile");
if (File.Exists(dockerfile))
{
warnings.AddRange(ScanDockerfile(context, dockerfile));
}
var envFile = Path.Combine(root, ".env");
if (File.Exists(envFile))
{
warnings.AddRange(ScanEnvFile(context, envFile));
}
}
return warnings
.OrderBy(static r => r.ComponentKey, StringComparer.Ordinal)
.ToArray();
}
private static IEnumerable<LanguageComponentRecord> ScanDockerfile(LanguageAnalyzerContext context, string dockerfile)
{
var results = new List<LanguageComponentRecord>();
try
{
var lines = File.ReadAllLines(dockerfile);
for (var i = 0; i < lines.Length; i++)
{
var match = EnvAssign.Match(lines[i]);
if (!match.Success)
{
continue;
}
var value = match.Groups["value"].Value.Trim().Trim('"', '\'');
results.Add(BuildWarning(context, dockerfile, i + 1, value, source: "Dockerfile", reason: "NODE_OPTIONS"));
}
}
catch (IOException)
{
// Ignore IO errors
}
return results;
}
private static IEnumerable<LanguageComponentRecord> ScanEnvFile(LanguageAnalyzerContext context, string envFile)
{
var results = new List<LanguageComponentRecord>();
try
{
var lines = File.ReadAllLines(envFile);
for (var i = 0; i < lines.Length; i++)
{
var line = lines[i];
if (!line.Contains("NODE_OPTIONS", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var parts = line.Split('=', 2);
if (parts.Length != 2)
{
continue;
}
var value = parts[1].Trim().Trim('"', '\'');
results.Add(BuildWarning(context, envFile, i + 1, value, source: ".env", reason: "NODE_OPTIONS"));
}
}
catch (IOException)
{
// Ignore IO errors
}
return results;
}
private static LanguageComponentRecord BuildWarning(LanguageAnalyzerContext context, string filePath, int lineNumber, string value, string source, string reason)
{
var locator = context.GetRelativePath(filePath).Replace(Path.DirectorySeparatorChar, '/');
var metadata = new List<KeyValuePair<string, string?>>
{
new("source", source),
new("locator", string.Concat(locator, "#", lineNumber.ToString(CultureInfo.InvariantCulture))),
new("reason", reason),
new("value", value)
};
var evidence = new[]
{
new LanguageComponentEvidence(
LanguageEvidenceKind.Metadata,
"node.env",
string.Concat(locator, "#", lineNumber.ToString(CultureInfo.InvariantCulture)),
value,
null)
};
return LanguageComponentRecord.FromExplicitKey(
analyzerId: "node",
componentKey: string.Concat("warning:node-options:", locator, "#", lineNumber.ToString(CultureInfo.InvariantCulture)),
purl: null,
name: "NODE_OPTIONS warning",
version: null,
type: "node:warning",
metadata: metadata,
evidence: evidence,
usedByEntrypoint: false);
}
}