using System.Collections.Generic; using System.Globalization; using System.Text; using StellaOps.Scanner.Analyzers.Lang.Deno.Internal; using StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Observations; using StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Runtime; using StellaOps.Scanner.Core.Contracts; namespace StellaOps.Scanner.Analyzers.Lang.Deno; public sealed class DenoLanguageAnalyzer : ILanguageAnalyzer { public string Id => "deno"; public string DisplayName => "Deno Analyzer"; public async ValueTask AnalyzeAsync(LanguageAnalyzerContext context, LanguageComponentWriter writer, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(writer); await TryWriteRuntimeShimAsync(context, cancellationToken).ConfigureAwait(false); // Optional runtime capture: executes only when STELLA_DENO_ENTRYPOINT is provided. await DenoRuntimeTraceRunner.TryExecuteAsync(context, logger: null, cancellationToken).ConfigureAwait(false); var workspace = await DenoWorkspaceNormalizer.NormalizeAsync(context, cancellationToken).ConfigureAwait(false); var moduleGraph = DenoModuleGraphResolver.Resolve(workspace, cancellationToken); var compatibility = DenoNpmCompatibilityAdapter.Analyze(workspace, moduleGraph, cancellationToken); var bundleScan = DenoBundleScanner.Scan(context.RootPath, cancellationToken); var bundleObservations = DenoBundleScanner.ToObservations(bundleScan); var containerInputs = DenoContainerAdapter.CollectInputs(workspace, bundleObservations); var containerRecords = DenoContainerEmitter.BuildRecords(Id, containerInputs); writer.AddRange(containerRecords); var observationDocument = DenoObservationBuilder.Build(moduleGraph, compatibility, bundleObservations); var observationJson = DenoObservationSerializer.Serialize(observationDocument); var observationHash = DenoObservationSerializer.ComputeSha256(observationJson); var observationBytes = Encoding.UTF8.GetBytes(observationJson); var observationMetadata = new[] { new KeyValuePair("deno.observation.hash", observationHash), new KeyValuePair("deno.observation.entrypoints", observationDocument.Entrypoints.Length.ToString(CultureInfo.InvariantCulture)), new KeyValuePair("deno.observation.capabilities", observationDocument.Capabilities.Length.ToString(CultureInfo.InvariantCulture)), new KeyValuePair("deno.observation.bundles", observationDocument.Bundles.Length.ToString(CultureInfo.InvariantCulture)) }; TryPersistObservation(context, observationBytes, observationMetadata); var observationEvidence = new[] { new LanguageComponentEvidence( LanguageEvidenceKind.Derived, "deno.observation", "document", observationJson, observationHash) }; writer.AddFromExplicitKey( analyzerId: Id, componentKey: "observation::deno", purl: null, name: "Deno Observation Summary", version: null, type: "deno-observation", metadata: observationMetadata, evidence: observationEvidence); TryIngestRuntimeTrace(context); // Task 5+ will convert moduleGraph + compatibility and bundle insights into SBOM components and evidence records. GC.KeepAlive(moduleGraph); GC.KeepAlive(compatibility); GC.KeepAlive(bundleObservations); GC.KeepAlive(containerInputs); GC.KeepAlive(observationDocument); } private static async ValueTask TryWriteRuntimeShimAsync(LanguageAnalyzerContext context, CancellationToken cancellationToken) { try { await DenoRuntimeShim.WriteAsync(context.RootPath, cancellationToken).ConfigureAwait(false); } catch { // Shim is best-effort; failure should not block static analysis. } } private void TryPersistObservation( LanguageAnalyzerContext context, byte[] observationBytes, IEnumerable> metadata) { ArgumentNullException.ThrowIfNull(context); ArgumentNullException.ThrowIfNull(observationBytes); if (context.AnalysisStore is not { } analysisStore) { return; } var metadataDictionary = CreateMetadata(metadata); var payload = new AnalyzerObservationPayload( analyzerId: Id, kind: "deno.observation", mediaType: "application/json", content: observationBytes, metadata: metadataDictionary, view: "observations"); analysisStore.Set(ScanAnalysisKeys.DenoObservationPayload, payload); } private static IReadOnlyDictionary? CreateMetadata(IEnumerable> metadata) { Dictionary? dictionary = null; foreach (var pair in metadata ?? Array.Empty>()) { if (string.IsNullOrWhiteSpace(pair.Key) || string.IsNullOrWhiteSpace(pair.Value)) { continue; } dictionary ??= new Dictionary(StringComparer.OrdinalIgnoreCase); dictionary[pair.Key] = pair.Value; } return dictionary; } private void TryIngestRuntimeTrace(LanguageAnalyzerContext context) { if (context.AnalysisStore is not { } analysisStore) { return; } var tracePath = Path.Combine(context.RootPath, "deno-runtime.ndjson"); if (!File.Exists(tracePath)) { return; } byte[] content; try { content = File.ReadAllBytes(tracePath); } catch (IOException) { return; } var (metadata, hash) = DenoRuntimeTraceProbe.Analyze(content); var runtimeMeta = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["deno.runtime.hash"] = hash, ["deno.runtime.event_count"] = metadata.EventCount.ToString(CultureInfo.InvariantCulture), ["deno.runtime.permission_uses"] = metadata.PermissionUses.ToString(CultureInfo.InvariantCulture), ["deno.runtime.module_loads"] = metadata.ModuleLoads.ToString(CultureInfo.InvariantCulture), ["deno.runtime.remote_origins"] = string.Join(',', metadata.RemoteOrigins), ["deno.runtime.permissions"] = string.Join(',', metadata.UniquePermissions), ["deno.runtime.npm_resolutions"] = metadata.NpmResolutions.ToString(CultureInfo.InvariantCulture), ["deno.runtime.wasm_loads"] = metadata.WasmLoads.ToString(CultureInfo.InvariantCulture), ["deno.runtime.dynamic_imports"] = metadata.DynamicImports.ToString(CultureInfo.InvariantCulture) }; var payload = new AnalyzerObservationPayload( analyzerId: Id, kind: "deno.runtime.v1", mediaType: "application/x-ndjson", content: content, metadata: runtimeMeta, view: "runtime"); analysisStore.Set(ScanAnalysisKeys.DenoRuntimePayload, payload); // Backward compatibility with early runtime experiments that used a string key. analysisStore.Set("deno.runtime", payload); // Also emit policy signals into AnalysisStore for downstream consumption. var signals = DenoPolicySignalEmitter.FromTrace(hash, metadata); foreach (var signal in signals) { analysisStore.Set(signal.Key, signal.Value); } } }