feat: Implement Runtime Facts ingestion service and NDJSON reader
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled

- Added RuntimeFactsNdjsonReader for reading NDJSON formatted runtime facts.
- Introduced IRuntimeFactsIngestionService interface and its implementation.
- Enhanced Program.cs to register new services and endpoints for runtime facts.
- Updated CallgraphIngestionService to include CAS URI in stored artifacts.
- Created RuntimeFactsValidationException for validation errors during ingestion.
- Added tests for RuntimeFactsIngestionService and RuntimeFactsNdjsonReader.
- Implemented SignalsSealedModeMonitor for compliance checks in sealed mode.
- Updated project dependencies for testing utilities.
This commit is contained in:
master
2025-11-10 07:56:15 +02:00
parent 9df52d84aa
commit 69c59defdc
132 changed files with 19718 additions and 9334 deletions

View File

@@ -0,0 +1,76 @@
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
using StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.Bundles;
public sealed class BundleInspectorTests
{
[Fact]
public void EszipInspectorExtractsManifest()
{
var temp = TestPaths.CreateTemporaryDirectory();
try
{
var eszipPath = BundleFixtureBuilder.CreateSampleEszip(temp);
var result = DenoBundleInspector.TryInspect(eszipPath, CancellationToken.None);
Assert.NotNull(result);
Assert.Equal("eszip", result!.BundleType);
Assert.Equal("mod.ts", result.Entrypoint);
Assert.Equal(2, result.Modules.Length);
Assert.Contains(result.Modules, module => module.Specifier == "file:///mod.ts");
Assert.Single(result.Resources);
}
finally
{
TestPaths.SafeDelete(temp);
}
}
[Fact]
public void DenoCompileInspectorExtractsEmbeddedEszip()
{
var temp = TestPaths.CreateTemporaryDirectory();
try
{
var binaryPath = BundleFixtureBuilder.CreateSampleCompiledBinary(temp);
var result = DenoCompileInspector.TryInspect(binaryPath, CancellationToken.None);
Assert.NotNull(result);
Assert.Equal("deno-compile", result!.BundleType);
Assert.Equal("mod.ts", result.Entrypoint);
Assert.Equal(2, result.Modules.Length);
}
finally
{
TestPaths.SafeDelete(temp);
}
}
[Fact]
public void BundleScannerProducesObservations()
{
var temp = TestPaths.CreateTemporaryDirectory();
try
{
var eszip = BundleFixtureBuilder.CreateSampleEszip(temp);
var binary = BundleFixtureBuilder.CreateSampleCompiledBinary(temp);
Assert.True(File.Exists(eszip));
Assert.True(File.Exists(binary));
var scan = DenoBundleScanner.Scan(temp, CancellationToken.None);
var observations = DenoBundleScanner.ToObservations(scan);
Assert.Equal(1, scan.EszipBundles.Length);
Assert.Equal(1, scan.CompiledBundles.Length);
Assert.Equal(2, observations.Length);
Assert.Contains(observations, obs => obs.BundleType == "eszip");
Assert.Contains(observations, obs => obs.BundleType == "deno-compile");
}
finally
{
TestPaths.SafeDelete(temp);
}
}
}

View File

@@ -0,0 +1,34 @@
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
using StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.Containers;
public sealed class ContainerAdapterTests
{
[Fact]
public async Task CollectInputsIncludesCacheVendorAndBundlesAsync()
{
var (root, envDir) = DenoWorkspaceTestFixture.Create();
try
{
Environment.SetEnvironmentVariable("DENO_DIR", envDir);
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = await DenoWorkspaceNormalizer.NormalizeAsync(context, CancellationToken.None);
var bundleScan = DenoBundleScanner.Scan(root, CancellationToken.None);
var observations = DenoBundleScanner.ToObservations(bundleScan);
var inputs = DenoContainerAdapter.CollectInputs(workspace, observations);
Assert.NotEmpty(inputs);
Assert.Contains(inputs, input => input.Kind == DenoContainerSourceKind.Cache);
Assert.Contains(inputs, input => input.Kind == DenoContainerSourceKind.Vendor);
Assert.Contains(inputs, input => input.Kind == DenoContainerSourceKind.Bundle);
}
finally
{
Environment.SetEnvironmentVariable("DENO_DIR", null);
TestPaths.SafeDelete(root);
}
}
}

View File

@@ -0,0 +1,38 @@
using System.Collections.Immutable;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.Containers;
public sealed class ContainerEmitterTests
{
[Fact]
public void BuildRecordsProducesComponents()
{
var inputs = new[]
{
new DenoContainerInput(
DenoContainerSourceKind.Cache,
"cache-alias",
"sha256:abc",
new Dictionary<string, string?> { ["path"] = "/cache/path" },
Bundle: null),
new DenoContainerInput(
DenoContainerSourceKind.Bundle,
"/path/bundle.eszip",
null,
new Dictionary<string, string?>(),
new DenoBundleObservation(
"/path/bundle.eszip",
"eszip",
"mod.ts",
ImmutableArray<DenoBundleModule>.Empty,
ImmutableArray<DenoBundleResource>.Empty))
};
var records = DenoContainerEmitter.BuildRecords("deno", inputs);
Assert.Equal(2, records.Length);
Assert.Contains(records, record => record.Metadata.ContainsKey("deno.container.bundle.entrypoint"));
Assert.Contains(records, record => record.Metadata.ContainsKey("deno.container.layerDigest"));
}
}

View File

@@ -0,0 +1,121 @@
using System.Linq;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
using StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.Deno;
public sealed class DenoWorkspaceNormalizerTests
{
[Fact]
public async Task WorkspaceFixtureProducesDeterministicOutputAsync()
{
var (root, envDenoDir) = DenoWorkspaceTestFixture.Create();
var previousDenoDir = Environment.GetEnvironmentVariable("DENO_DIR");
try
{
Environment.SetEnvironmentVariable("DENO_DIR", envDenoDir);
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = await DenoWorkspaceNormalizer.NormalizeAsync(context, CancellationToken.None);
var config = Assert.Single(workspace.Configurations);
Assert.EndsWith("deno.jsonc", config.AbsolutePath, StringComparison.OrdinalIgnoreCase);
Assert.True(config.LockEnabled);
Assert.NotNull(config.InlineImportMap);
Assert.NotNull(config.ImportMapPath);
Assert.NotNull(config.VendorDirectoryPath);
Assert.Contains(workspace.ImportMaps, map => map.IsInline);
Assert.Contains(
workspace.ImportMaps,
map => map.AbsolutePath is not null && map.AbsolutePath.EndsWith("import_map.json", StringComparison.OrdinalIgnoreCase));
Assert.Contains(workspace.LockFiles, file => file.AbsolutePath.EndsWith("deno.lock", StringComparison.OrdinalIgnoreCase));
Assert.Contains(workspace.Vendors, vendor => vendor.AbsolutePath.EndsWith(Path.Combine("vendor"), StringComparison.OrdinalIgnoreCase));
Assert.Contains(workspace.CacheLocations, cache => cache.Kind == DenoCacheLocationKind.Env);
Assert.Contains(
workspace.CacheLocations,
cache => cache.Kind == DenoCacheLocationKind.Workspace && cache.AbsolutePath.EndsWith(".deno", StringComparison.OrdinalIgnoreCase));
Assert.Contains(workspace.FileSystem.Files, file => file.VirtualPath == "workspace://deno.jsonc");
Assert.Contains(workspace.FileSystem.Files, file => file.VirtualPath.StartsWith("vendor://", StringComparison.Ordinal));
Assert.Contains(
workspace.FileSystem.Files,
file => file.VirtualPath.StartsWith("deno-dir://", StringComparison.Ordinal) ||
file.VirtualPath.StartsWith("layer://", StringComparison.Ordinal));
}
finally
{
Environment.SetEnvironmentVariable("DENO_DIR", previousDenoDir);
DenoWorkspaceTestFixture.Cleanup(root);
}
}
[Fact]
public async Task GraphResolverCapturesImportAndCacheEdgesAsync()
{
var (root, envDenoDir) = DenoWorkspaceTestFixture.Create();
var previousDenoDir = Environment.GetEnvironmentVariable("DENO_DIR");
try
{
Environment.SetEnvironmentVariable("DENO_DIR", envDenoDir);
var context = new LanguageAnalyzerContext(root, TimeProvider.System);
var workspace = await DenoWorkspaceNormalizer.NormalizeAsync(context, CancellationToken.None);
var lockFile = workspace.LockFiles.Single(lf => string.Equals(lf.RelativePath, "deno.lock", StringComparison.OrdinalIgnoreCase));
Assert.Contains(lockFile.RemoteEntries, entry => entry.Key.Contains("server.ts", StringComparison.Ordinal));
var graph = DenoModuleGraphResolver.Resolve(workspace, CancellationToken.None);
var compatibility = DenoNpmCompatibilityAdapter.Analyze(workspace, graph, CancellationToken.None);
Assert.NotEmpty(graph.Nodes);
Assert.NotEmpty(graph.Edges);
var remoteNode = graph.Nodes.FirstOrDefault(
node => node.Kind == DenoModuleKind.RemoteModule &&
node.Id == "remote::https://deno.land/std@0.207.0/http/server.ts");
Assert.NotNull(remoteNode);
Assert.Equal("sha256-deadbeef", remoteNode!.Integrity);
Assert.Contains(
graph.Edges,
edge => edge.ImportKind == DenoImportKind.Cache &&
edge.Provenance.StartsWith("vendor-cache:", StringComparison.Ordinal) &&
edge.Specifier.Contains("https://deno.land/std@0.207.0/http/server.ts", StringComparison.Ordinal));
Assert.Contains(
graph.Edges,
edge => edge.ImportKind == DenoImportKind.NpmBridge &&
edge.Specifier == "npm:dayjs@1" &&
edge.Resolution == "dayjs@1.11.12");
Assert.Contains(
graph.Edges,
edge => edge.ImportKind == DenoImportKind.JsonAssertion &&
edge.Specifier == "data" &&
edge.Resolution?.EndsWith("data/data.json", StringComparison.OrdinalIgnoreCase) == true);
Assert.Contains(
graph.Edges,
edge => edge.ImportKind == DenoImportKind.Redirect &&
edge.Specifier == "https://deno.land/std/http/server.ts");
Assert.Contains(
compatibility.BuiltinUsages,
usage => usage.Specifier == "node:fs");
var npmResolution = compatibility.NpmResolutions.First(res => res.Specifier == "npm:dayjs@1");
Assert.True(npmResolution.ExistsOnDisk);
Assert.Equal("deno", npmResolution.Condition);
Assert.True(npmResolution.ResolvedPath?.EndsWith("deno.mod.ts", StringComparison.OrdinalIgnoreCase));
}
finally
{
Environment.SetEnvironmentVariable("DENO_DIR", previousDenoDir);
DenoWorkspaceTestFixture.Cleanup(root);
}
}
}

View File

@@ -0,0 +1,47 @@
using System.Threading;
using System.Threading.Tasks;
using StellaOps.Scanner.Analyzers.Lang;
using StellaOps.Scanner.Analyzers.Lang.Deno;
using StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
using StellaOps.Scanner.Core.Contracts;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.Observations;
public sealed class DenoLanguageAnalyzerObservationTests
{
[Fact]
public async Task AnalyzerStoresObservationPayloadInAnalysisStoreAsync()
{
var (root, envDenoDir) = DenoWorkspaceTestFixture.Create();
var previousDenoDir = Environment.GetEnvironmentVariable("DENO_DIR");
try
{
Environment.SetEnvironmentVariable("DENO_DIR", envDenoDir);
var store = new ScanAnalysisStore();
var context = new LanguageAnalyzerContext(
root,
TimeProvider.System,
usageHints: null,
services: null,
analysisStore: store);
var analyzer = new DenoLanguageAnalyzer();
var engine = new LanguageAnalyzerEngine(new[] { analyzer });
await engine.AnalyzeAsync(context, CancellationToken.None);
Assert.True(store.TryGet(ScanAnalysisKeys.DenoObservationPayload, out AnalyzerObservationPayload payload));
Assert.Equal("deno.observation", payload.Kind);
Assert.Equal("application/json", payload.MediaType);
Assert.True(payload.Content.Length > 0);
Assert.NotNull(payload.Metadata);
Assert.True(payload.Metadata!.ContainsKey("deno.observation.hash"));
}
finally
{
Environment.SetEnvironmentVariable("DENO_DIR", previousDenoDir);
DenoWorkspaceTestFixture.Cleanup(root);
}
}
}

View File

@@ -0,0 +1,28 @@
using System.Collections.Immutable;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal;
using StellaOps.Scanner.Analyzers.Lang.Deno.Internal.Observations;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.Observations;
public sealed class ObservationSerializerTests
{
[Fact]
public void SerializeProducesDeterministicJson()
{
var document = new DenoObservationDocument(
ImmutableArray.Create("mod.ts"),
ImmutableArray.Create("https://example.com/deps.ts"),
ImmutableArray<DenoCapabilityRecord>.Empty,
ImmutableArray<DenoDynamicImportObservation>.Empty,
ImmutableArray<DenoLiteralFetchObservation>.Empty,
ImmutableArray.Create(new DenoObservationBundleSummary("bundle.eszip", "eszip", "mod.ts", 2, 1)));
var json = DenoObservationSerializer.Serialize(document);
var hash = DenoObservationSerializer.ComputeSha256(json);
Assert.Equal("sha256:" + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(json))).ToLowerInvariant(), hash);
Assert.Contains("\"entrypoints\":[\"mod.ts\"]", json, StringComparison.Ordinal);
}
}

View File

@@ -0,0 +1,42 @@
<?xml version='1.0' encoding='utf-8'?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Remove="Microsoft.NET.Test.Sdk" />
<PackageReference Remove="xunit" />
<PackageReference Remove="xunit.runner.visualstudio" />
<PackageReference Remove="Microsoft.AspNetCore.Mvc.Testing" />
<PackageReference Remove="Mongo2Go" />
<PackageReference Remove="coverlet.collector" />
<PackageReference Remove="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Remove="..\\StellaOps.Concelier.Testing\\StellaOps.Concelier.Testing.csproj" />
<Compile Remove="$(MSBuildThisFileDirectory)..\\StellaOps.Concelier.Tests.Shared\\AssemblyInfo.cs" />
<Compile Remove="$(MSBuildThisFileDirectory)..\\StellaOps.Concelier.Tests.Shared\\MongoFixtureCollection.cs" />
<Using Remove="StellaOps.Concelier.Testing" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\\StellaOps.Scanner.Analyzers.Lang.Tests\\StellaOps.Scanner.Analyzers.Lang.Tests.csproj" />
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Scanner.Analyzers.Lang\\StellaOps.Scanner.Analyzers.Lang.csproj" />
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Scanner.Analyzers.Lang.Deno\\StellaOps.Scanner.Analyzers.Lang.Deno.csproj" />
<ProjectReference Include="..\\..\\__Libraries\\StellaOps.Scanner.Core\\StellaOps.Scanner.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit.v3" Version="3.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,74 @@
using System.IO.Compression;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
internal static class BundleFixtureBuilder
{
public static string CreateSampleEszip(string directory)
{
Directory.CreateDirectory(directory);
var bundlePath = Path.Combine(directory, "sample.eszip");
using (var archive = ZipFile.Open(bundlePath, ZipArchiveMode.Create))
{
var manifest = archive.CreateEntry("manifest.json", CompressionLevel.NoCompression);
using (var writer = new StreamWriter(manifest.Open(), Encoding.UTF8))
{
writer.Write("""
{
"entry": "mod.ts",
"modules": {
"mod.ts": {
"specifier": "file:///mod.ts",
"path": "modules/mod.ts",
"mediaType": "application/typescript"
},
"deps.ts": {
"specifier": "https://example.com/deps.ts",
"path": "modules/deps.ts",
"mediaType": "application/typescript"
}
},
"resources": [
{
"name": "assets/config.json",
"mediaType": "application/json",
"size": 42
}
]
}
""");
}
AddTextEntry(archive, "modules/mod.ts", "import \"./deps.ts\";\nconsole.log('hello');\n");
AddTextEntry(archive, "modules/deps.ts", "export const value = 42;\n");
AddTextEntry(archive, "assets/config.json", "{ \"ok\": true }\n");
}
return bundlePath;
}
public static string CreateSampleCompiledBinary(string directory)
{
Directory.CreateDirectory(directory);
var binaryPath = Path.Combine(directory, "sample.deno");
var eszipPath = CreateSampleEszip(directory);
var eszipBytes = File.ReadAllBytes(eszipPath);
using var stream = File.Create(binaryPath);
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
writer.Write(Encoding.UTF8.GetBytes("FAKE_DENO_COMPILE_HEADER"));
writer.Write(Encoding.UTF8.GetBytes(DenoCompileInspector.EszipMarker));
writer.Flush();
stream.Write(eszipBytes, 0, eszipBytes.Length);
return binaryPath;
}
private static void AddTextEntry(ZipArchive archive, string entryPath, string contents)
{
var entry = archive.CreateEntry(entryPath, CompressionLevel.NoCompression);
using var writer = new StreamWriter(entry.Open(), Encoding.UTF8);
writer.Write(contents);
}
}

View File

@@ -0,0 +1,186 @@
using StellaOps.Scanner.Analyzers.Lang.Tests.TestUtilities;
namespace StellaOps.Scanner.Analyzers.Lang.Deno.Tests.TestFixtures;
internal static class DenoWorkspaceTestFixture
{
public static (string RootPath, string EnvDenoDir) Create()
{
var root = TestPaths.CreateTemporaryDirectory();
CreateDenoFixture(root, out var envDir);
return (root, envDir);
}
public static void Cleanup(string root)
{
TestPaths.SafeDelete(root);
}
private static void CreateDenoFixture(string root, out string envDenoDir)
{
Directory.CreateDirectory(root);
File.WriteAllText(Path.Combine(root, "deno.jsonc"), """
// sample deno config
{
"importMap": "./import_map.json",
"lock": {
"enabled": true,
"path": "./deno.lock"
},
"vendor": {
"enabled": true,
"path": "./vendor"
},
"nodeModulesDir": false,
"imports": {
"$std/": "https://deno.land/std@0.207.0/",
"app/": "./src/app/",
"data": "./data/data.json",
"npmDynamic": "npm:dayjs@1",
"nodeFs": "node:fs"
},
"scopes": {
"https://deno.land/": {
"fmt/": "https://deno.land/std@0.207.0/fmt/"
}
}
}
""");
Directory.CreateDirectory(Path.Combine(root, "src", "app"));
Directory.CreateDirectory(Path.Combine(root, "data"));
File.WriteAllText(Path.Combine(root, "data", "data.json"), "{ \"ok\": true }");
File.WriteAllText(
Path.Combine(root, "import_map.json"),
"""
{
"imports": {
"app/": "./src/app/main.ts",
"vendor/": "https://deno.land/std@0.207.0/"
}
}
""");
File.WriteAllText(
Path.Combine(root, "deno.lock"),
"""
{
"version": "4",
"remote": {
"https://deno.land/std@0.207.0/http/server.ts": "sha256-deadbeef",
"https://example.com/mod.ts": "sha256-feedface",
"node:fs": "builtin"
},
"redirects": {
"https://deno.land/std/http/server.ts": "https://deno.land/std@0.207.0/http/server.ts"
},
"npm": {
"specifiers": {
"npm:dayjs@1": "dayjs@1.11.12"
},
"packages": {
"dayjs@1.11.12": {
"integrity": "sha512-sample",
"dependencies": {
"tslib": "tslib@2.6.3"
}
},
"tslib@2.6.3": {
"integrity": "sha512-tslib",
"dependencies": {}
}
}
}
}
""");
var vendorRoot = Path.Combine(root, "vendor", "https", "deno.land", "std@0.207.0", "http");
Directory.CreateDirectory(vendorRoot);
File.WriteAllText(Path.Combine(vendorRoot, "server.ts"), "export const vendor = true;");
var vendorBase = Path.Combine(root, "vendor");
File.WriteAllText(
Path.Combine(vendorBase, "import_map.json"),
"""
{
"imports": {
"std/http/server.ts": "https://deno.land/std@0.207.0/http/server.ts"
}
}
""");
File.WriteAllText(
Path.Combine(vendorBase, "deno.lock"),
"""
{
"version": "1",
"remote": {}
}
""");
CreateDenoDir(Path.Combine(root, ".deno"), "workspace.ts", includeRegistry: true);
envDenoDir = Path.Combine(root, "env-deno");
CreateDenoDir(envDenoDir, "env.ts");
var layerFs = Path.Combine(root, "layers", "sha256-deadbeef", "fs");
Directory.CreateDirectory(layerFs);
CreateDenoDir(Path.Combine(layerFs, ".deno"), "layer.ts");
var layerVendor = Path.Combine(layerFs, "vendor", "https", "layer.example");
Directory.CreateDirectory(layerVendor);
File.WriteAllText(Path.Combine(layerFs, "vendor", "import_map.json"), "{\"imports\":{\"layer/\": \"https://layer.example/\"}}");
}
private static void CreateDenoDir(string root, string fileName, bool includeRegistry = false)
{
var deps = Path.Combine(root, "deps", "https", "example.com");
var gen = Path.Combine(root, "gen");
var npm = Path.Combine(root, "npm");
Directory.CreateDirectory(deps);
Directory.CreateDirectory(gen);
Directory.CreateDirectory(npm);
File.WriteAllText(Path.Combine(deps, fileName), "export const cache = true;");
File.WriteAllText(Path.Combine(gen, $"{Path.GetFileNameWithoutExtension(fileName)}.js"), "console.log('gen');");
File.WriteAllText(Path.Combine(npm, "package.json"), "{}");
if (includeRegistry)
{
CreateNpmRegistryPackage(root);
}
}
private static void CreateNpmRegistryPackage(string denoDirRoot)
{
var registryRoot = Path.Combine(denoDirRoot, "npm", "registry.npmjs.org");
var dayjsRoot = Path.Combine(registryRoot, "dayjs", "1.11.12");
Directory.CreateDirectory(dayjsRoot);
File.WriteAllText(
Path.Combine(dayjsRoot, "package.json"),
"""
{
"name": "dayjs",
"version": "1.11.12",
"exports": {
".": {
"deno": "./deno.mod.ts",
"import": "./esm/index.js",
"default": "./lib/index.js"
},
"./plugin": "./plugin/index.js"
},
"main": "./lib/index.js"
}
""");
File.WriteAllText(Path.Combine(dayjsRoot, "deno.mod.ts"), "export const deno = true;");
Directory.CreateDirectory(Path.Combine(dayjsRoot, "esm"));
File.WriteAllText(Path.Combine(dayjsRoot, "esm", "index.js"), "export const esm = true;");
Directory.CreateDirectory(Path.Combine(dayjsRoot, "lib"));
File.WriteAllText(Path.Combine(dayjsRoot, "lib", "index.js"), "module.exports = true;");
Directory.CreateDirectory(Path.Combine(dayjsRoot, "plugin"));
File.WriteAllText(Path.Combine(dayjsRoot, "plugin", "index.js"), "export const plugin = true;");
}
}