diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 39539bd..756b5e4 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -5,6 +5,13 @@ "dotnet-reportgenerator-globaltool": { "version": "5.5.10", "commands": ["reportgenerator"] + }, + "dotnet-stryker": { + "version": "5.0.0", + "commands": [ + "dotnet-stryker" + ], + "rollForward": false } } } diff --git a/.githooks/pre-push b/.githooks/pre-push index 9978f48..d85021e 100755 --- a/.githooks/pre-push +++ b/.githooks/pre-push @@ -90,6 +90,17 @@ try { Invoke-CheckedCommand npm @('run', 'lint') $sitePath Invoke-CheckedCommand npm @('run', 'typecheck') $sitePath Invoke-CheckedCommand npm @('run', 'test:coverage') $sitePath + + # Match the measured scopes and thresholds required by the Quality workflow. + $mutationRunner = Join-Path $repoRoot 'scripts/Run-MutationTests.ps1' + $mutationRoot = Join-Path $coverageRoot 'mutation' + & $mutationRunner -Project Core -BreakAt 90 -ArtifactsPath $mutationRoot + & $mutationRunner -Project Cli -BreakAt 55 -ArtifactsPath $mutationRoot -Mutate @( + '**/Commands/SyncCommand.cs', + '**/Commands/CatalogCommand.cs', + '**/Sync/ExtractionProgressDisplay.cs' + ) + Invoke-CheckedCommand npm @('run', 'test:mutation', '--', 'stryker.ci.config.mjs') $sitePath } finally { Write-Host "Coverage reports: $coverageRoot" diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml new file mode 100644 index 0000000..c6793a1 --- /dev/null +++ b/.github/workflows/mutation.yml @@ -0,0 +1,78 @@ +name: Mutation testing + +on: + workflow_dispatch: + schedule: + - cron: "41 7 * * 1" + +permissions: + contents: read + +concurrency: + group: mutation-${{ github.ref }} + cancel-in-progress: true + +jobs: + cli: + name: CLI mutation (${{ matrix.project }}) + runs-on: ubuntu-latest + timeout-minutes: 120 + strategy: + fail-fast: false + max-parallel: 2 + matrix: + project: + [ + Core, + Catalog, + Cli, + Extraction.MsSql, + Extraction.Oracle, + Lineage.MsSql, + Lineage.Oracle, + ] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + - name: Run mutation tests + shell: pwsh + env: + MUTATION_PROJECT: ${{ matrix.project }} + run: ./scripts/Run-MutationTests.ps1 -Project $env:MUTATION_PROJECT + - name: Upload mutation reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: mutation-cli-${{ matrix.project }} + path: TestResults/mutation/ + if-no-files-found: error + retention-days: 14 + + site: + name: Site mutation + runs-on: ubuntu-latest + timeout-minutes: 120 + defaults: + run: + working-directory: site + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + cache-dependency-path: site/package-lock.json + - run: npm ci + - run: npm run test:mutation + - name: Upload mutation reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: mutation-site + path: site/reports/mutation/ + if-no-files-found: error + retention-days: 14 diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a184d0a..396d341 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -160,6 +160,74 @@ jobs: if-no-files-found: error retention-days: 14 + cli-mutation: + name: CLI mutation (${{ matrix.project }}) + runs-on: ubuntu-latest + timeout-minutes: 45 + strategy: + fail-fast: false + matrix: + include: + - project: Core + minimum: 90 + mutate: [] + - project: Cli + minimum: 55 + mutate: + - '**/Commands/SyncCommand.cs' + - '**/Commands/CatalogCommand.cs' + - '**/Sync/ExtractionProgressDisplay.cs' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + - name: Enforce measured mutation baseline + shell: pwsh + env: + MUTATION_PROJECT: ${{ matrix.project }} + MUTATION_MINIMUM: ${{ matrix.minimum }} + MUTATION_PATTERNS: ${{ toJSON(matrix.mutate) }} + run: | + $patterns = @(ConvertFrom-Json $env:MUTATION_PATTERNS) + ./scripts/Run-MutationTests.ps1 -Project $env:MUTATION_PROJECT -BreakAt ([int]$env:MUTATION_MINIMUM) -Mutate $patterns -ArtifactsPath TestResults/quality-mutation + - name: Upload mutation reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: quality-mutation-cli-${{ matrix.project }} + path: TestResults/quality-mutation/ + if-no-files-found: error + retention-days: 14 + + site-mutation: + name: Site reachability mutation + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: site + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: npm + cache-dependency-path: site/package-lock.json + - run: npm ci + - name: Enforce reachability mutation baseline + run: npm run test:mutation -- stryker.ci.config.mjs + - name: Upload mutation reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: quality-mutation-site + path: site/reports/mutation/ + if-no-files-found: error + retention-days: 14 + cli-performance: name: CLI performance budgets runs-on: ubuntu-latest @@ -308,7 +376,7 @@ jobs: quality-gate: name: Quality gate if: always() - needs: [vulnerabilities, codeql, cli-style, cli-coverage, cli-performance, site-style, site-coverage, workflow-lint, coverage-badges] + needs: [vulnerabilities, codeql, cli-style, cli-coverage, cli-mutation, cli-performance, site-style, site-coverage, site-mutation, workflow-lint, coverage-badges] runs-on: ubuntu-latest timeout-minutes: 5 steps: @@ -343,7 +411,7 @@ jobs: quality-comment: name: Comment quality results - needs: [vulnerabilities, codeql, cli-style, cli-coverage, cli-performance, site-style, site-coverage, workflow-lint, coverage-badges, quality-gate] + needs: [vulnerabilities, codeql, cli-style, cli-coverage, cli-mutation, cli-performance, site-style, site-coverage, site-mutation, workflow-lint, coverage-badges, quality-gate] # Fork PR tokens are read-only; keep their checks running without attempting # to elevate permissions or making a missing comment fail their gate. if: ${{ !cancelled() && github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository }} @@ -368,9 +436,11 @@ jobs: | CodeQL | ${{ needs.codeql.result }} | | CLI lint and format | ${{ needs.cli-style.result }} | | CLI tests and coverage | ${{ needs.cli-coverage.result }} | + | CLI mutation (Core ≥90%, selected command files ≥55%) | ${{ needs.cli-mutation.result }} | | CLI performance budgets | ${{ needs.cli-performance.result }} | | Site lint and format | ${{ needs.site-style.result }} | | Site tests and coverage | ${{ needs.site-coverage.result }} | + | Site reachability mutation (100%) | ${{ needs.site-mutation.result }} | | Workflow lint | ${{ needs.workflow-lint.result }} | | Coverage badge data | ${{ needs.coverage-badges.result }} | diff --git a/.gitignore b/.gitignore index 79ffa8f..bd1cb5c 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,9 @@ site/node_modules/ site/dist/ site/*.tsbuildinfo coverage/ +StrykerOutput/ +.stryker-tmp/ +site/reports/mutation/ # .NET CLI build output cli/**/bin/ diff --git a/README.md b/README.md index 70362c8..a52901e 100644 --- a/README.md +++ b/README.md @@ -490,7 +490,8 @@ GitHub workflows are grouped into independent CLI and Site families: | Workflow | What it runs | |----------|--------------| -| [Quality](.github/workflows/quality.yml) | CodeQL, dependency review and full npm/NuGet audits, CLI/Site lint and format checks, coverage thresholds, workflow lint, and a single aggregate quality gate. | +| [Quality](.github/workflows/quality.yml) | CodeQL, dependency review and full npm/NuGet audits, CLI/Site lint and format checks, coverage and mutation thresholds, workflow lint, and a single aggregate quality gate. | +| [Mutation testing](.github/workflows/mutation.yml) | Weekly/manual Stryker runs for CLI projects and the site, with HTML/JSON reports. See [local commands and score policy](docs/mutation-testing.md). | | [CLI - CI](.github/workflows/cli.yml) | .NET format, build, tests and coverage on Linux and Windows; gateway publishing guards and CLI workflow validation; publishing-script compatibility checks, including the actual Windows PowerShell 5.1 parser. | | [CLI - benchmark](.github/workflows/cli-benchmark.yml) | Provisions the sample databases, extracts objects and verifies lineage on relevant pull requests or manual runs. Manual runs can also enable the prepared Oracle gateway runner. | | [Docker - publish Oracle gateway to GHCR](.github/workflows/cli-publish-oracle-gateway.yml) | Manually validates and publishes the private gateway image from the default branch. | diff --git a/cli/src/SyncSql.Cli/Commands/SyncCommand.cs b/cli/src/SyncSql.Cli/Commands/SyncCommand.cs index 0feb493..a53c139 100644 --- a/cli/src/SyncSql.Cli/Commands/SyncCommand.cs +++ b/cli/src/SyncSql.Cli/Commands/SyncCommand.cs @@ -211,10 +211,10 @@ public static Command Build(IServiceProvider services) } attemptedServers++; - DatabaseCredentials credentials; try { - credentials = ResolveCredentials(server, credentialProvider, OperatingSystem.IsWindows()); + DatabaseCredentials credentials = ResolveCredentials(server, credentialProvider, OperatingSystem.IsWindows()); + work.Add((server, depth, filters, credentials)); } catch (InvalidOperationException ex) { @@ -224,8 +224,6 @@ public static Command Build(IServiceProvider services) (depth == 0 ? failedServers : failedDiscoveredServers).Add(server.Name); continue; } - - work.Add((server, depth, filters, credentials)); } // Each worker owns its connections and output. Fold the small summaries in input diff --git a/cli/src/SyncSql.Cli/Sync/ExtractionProgressDisplay.cs b/cli/src/SyncSql.Cli/Sync/ExtractionProgressDisplay.cs index d8f701f..e314ba8 100644 --- a/cli/src/SyncSql.Cli/Sync/ExtractionProgressDisplay.cs +++ b/cli/src/SyncSql.Cli/Sync/ExtractionProgressDisplay.cs @@ -41,7 +41,7 @@ public CatalogProgressDisplay StartCatalog(Action? log = null) { lock (Gate) { - CatalogProgressDisplay display = new(this, log ?? (message => WriteLog(message + Environment.NewLine))); + CatalogProgressDisplay display = new(this, log ?? (message => WriteLog($"{message}{Environment.NewLine}"))); _display = display; if (animated) { diff --git a/cli/src/SyncSql.Core/Abstractions/ExtractionWorkScheduler.cs b/cli/src/SyncSql.Core/Abstractions/ExtractionWorkScheduler.cs index 592dba4..d469d74 100644 --- a/cli/src/SyncSql.Core/Abstractions/ExtractionWorkScheduler.cs +++ b/cli/src/SyncSql.Core/Abstractions/ExtractionWorkScheduler.cs @@ -10,11 +10,17 @@ public sealed class ExtractionWorkScheduler private readonly Dictionary _active = []; private int _running; private int _nextEngine; + private readonly TaskScheduler _taskScheduler; - public ExtractionWorkScheduler(int maxParallelism) + public ExtractionWorkScheduler(int maxParallelism) : this(maxParallelism, TaskScheduler.Default) + { + } + + internal ExtractionWorkScheduler(int maxParallelism, TaskScheduler taskScheduler) { ArgumentOutOfRangeException.ThrowIfLessThan(maxParallelism, 1); MaxParallelism = maxParallelism; + _taskScheduler = taskScheduler; } public int MaxParallelism { get; } @@ -88,7 +94,7 @@ private void Dispatch() _running++; _nextEngine = (selected + 1) % engines.Length; ExtractionWorkContext context = new(this, engine, item.Token); - _ = Task.Run(async () => + _ = Task.Factory.StartNew(async () => { try { await item.ExecuteAsync(context); } finally @@ -99,7 +105,7 @@ private void Dispatch() Dispatch(); } } - }, CancellationToken.None); + }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, _taskScheduler).Unwrap(); } } diff --git a/cli/src/SyncSql.Core/Configuration/SyncSqlConfigLoader.cs b/cli/src/SyncSql.Core/Configuration/SyncSqlConfigLoader.cs index 2834575..fdc9578 100644 --- a/cli/src/SyncSql.Core/Configuration/SyncSqlConfigLoader.cs +++ b/cli/src/SyncSql.Core/Configuration/SyncSqlConfigLoader.cs @@ -21,17 +21,7 @@ public static async Task LoadAsync(string path, CancellationToken } await using FileStream stream = File.OpenRead(path); - SyncSqlConfig config; - try - { - config = await JsonSerializer.DeserializeAsync(stream, SerializerOptions, cancellationToken) - ?? throw new ConfigValidationException($"Config file '{path}' is empty or 'null'."); - } - catch (JsonException ex) - { - throw new ConfigValidationException($"Config file '{path}' could not be parsed: {ex.Message}"); - } - + SyncSqlConfig config = await DeserializeAsync(stream, path, cancellationToken); Validate(config, path); string directory = Path.GetDirectoryName(Path.GetFullPath(path))!; return config with @@ -51,6 +41,19 @@ public static async Task LoadAsync(string path, CancellationToken string? ResolvePath(string? value) => value is null ? null : Path.GetFullPath(value, directory); } + private static async Task DeserializeAsync(Stream stream, string path, CancellationToken cancellationToken) + { + try + { + return await JsonSerializer.DeserializeAsync(stream, SerializerOptions, cancellationToken) + ?? throw new ConfigValidationException($"Config file '{path}' is empty or 'null'."); + } + catch (JsonException ex) + { + throw new ConfigValidationException($"Config file '{path}' could not be parsed: {ex.Message}"); + } + } + private static void Validate(SyncSqlConfig config, string path) { if (config.Servers.Count == 0) diff --git a/cli/src/SyncSql.Core/Credentials/CredentialsFileProvider.cs b/cli/src/SyncSql.Core/Credentials/CredentialsFileProvider.cs index 5b9af85..23d7399 100644 --- a/cli/src/SyncSql.Core/Credentials/CredentialsFileProvider.cs +++ b/cli/src/SyncSql.Core/Credentials/CredentialsFileProvider.cs @@ -45,30 +45,29 @@ public static async Task LoadAsync(string path, Cancell throw new CredentialParseException($"Credentials file not found: {path}"); } - Dictionary? entries; + Dictionary entries = await ReadEntriesAsync(path, cancellationToken); + Dictionary byPrefix = new(StringComparer.OrdinalIgnoreCase); + foreach ((string prefix, CredentialsFileEntry? entry) in entries) + { + byPrefix[prefix] = new PartialCredentials(entry?.User, entry?.Password); + } + + return new CredentialsFileProvider(byPrefix, path); + } + + private static async Task> ReadEntriesAsync(string path, CancellationToken cancellationToken) + { + await using FileStream stream = File.OpenRead(path); try { - await using FileStream stream = File.OpenRead(path); - entries = await JsonSerializer.DeserializeAsync>(stream, SerializerOptions, cancellationToken); + return await JsonSerializer.DeserializeAsync>(stream, SerializerOptions, cancellationToken) + ?? throw new CredentialParseException($"Credentials file '{path}' is empty or 'null'."); } catch (JsonException ex) { throw new CredentialParseException( $"Credentials file '{path}' could not be parsed: {ex.Message} Expected {{ \"PREFIX\": {{ \"user\": \"...\", \"password\": \"...\" }} }}."); } - - if (entries is null) - { - throw new CredentialParseException($"Credentials file '{path}' is empty or 'null'."); - } - - Dictionary byPrefix = new(StringComparer.OrdinalIgnoreCase); - foreach ((string prefix, CredentialsFileEntry? entry) in entries) - { - byPrefix[prefix] = new PartialCredentials(entry?.User, entry?.Password); - } - - return new CredentialsFileProvider(byPrefix, path); } public PartialCredentials Read(string credentialsVariablePrefix) => diff --git a/cli/stryker-config.json b/cli/stryker-config.json new file mode 100644 index 0000000..21693d5 --- /dev/null +++ b/cli/stryker-config.json @@ -0,0 +1,10 @@ +{ + "stryker-config": { + "mutation-level": "Standard", + "reporters": ["progress", "html", "json"], + "concurrency": 2, + "thresholds": { "high": 80, "low": 60, "break": 0 }, + "break-on-initial-test-failure": true, + "mutate": ["**/*.cs", "!**/obj/**", "!**/bin/**", "!**/Generated/**"] + } +} diff --git a/cli/tests/SyncSql.Cli.Tests/CatalogCommandProgressTests.cs b/cli/tests/SyncSql.Cli.Tests/CatalogCommandProgressTests.cs index a7022ee..4b69046 100644 --- a/cli/tests/SyncSql.Cli.Tests/CatalogCommandProgressTests.cs +++ b/cli/tests/SyncSql.Cli.Tests/CatalogCommandProgressTests.cs @@ -11,6 +11,30 @@ namespace SyncSql.Cli.Tests; public sealed class CatalogCommandProgressTests { + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task HandledBuildErrorsReturnFailureAndRestoreTerminal(bool missingDirectory) + { + using StringWriter output = new(CultureInfo.InvariantCulture); + var builder = Substitute.For(); + Exception failure = missingDirectory ? new DirectoryNotFoundException("Missing input") : new InvalidDataException("Invalid catalog"); + builder.BuildAsync(Arg.Any(), Arg.Any()) + .Returns(Task.FromException(failure)); + ServiceCollection registrations = new(); + registrations.AddLogging(); + registrations.AddSingleton(builder); + registrations.AddSingleton(new SyncSqlTerminal(output, animated: true)); + using ServiceProvider services = registrations.BuildServiceProvider(); + + int exit = await new RootCommand { CatalogCommand.Build(services) } + .Parse(["catalog", "build", "--objects-root", Path.GetTempPath()]).InvokeAsync(); + Assert.Equal(1, exit); + Assert.Contains("Catalog Failed", output.ToString()); + Assert.DoesNotContain("Catalog Done", output.ToString()); + Assert.EndsWith("\e[?25h", output.ToString()); + } + [Theory] [InlineData(true)] [InlineData(false)] diff --git a/cli/tests/SyncSql.Cli.Tests/CommandOptionContractTests.cs b/cli/tests/SyncSql.Cli.Tests/CommandOptionContractTests.cs new file mode 100644 index 0000000..3f764da --- /dev/null +++ b/cli/tests/SyncSql.Cli.Tests/CommandOptionContractTests.cs @@ -0,0 +1,84 @@ +using System.CommandLine; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using SyncSql.Cli.Commands; +using SyncSql.Core.Abstractions; +using CatalogModel = SyncSql.Core.Domain.Catalog; + +namespace SyncSql.Cli.Tests; + +public sealed class CommandOptionContractTests : IDisposable +{ + private readonly string _root = Directory.CreateTempSubdirectory("syncsql-options-").FullName; + + [Fact] + public async Task CatalogForwardsExplicitOptionsAndSupportsMultipleInputRoots() + { + string first = Directory.CreateDirectory(Path.Combine(_root, "first")).FullName; + string second = Directory.CreateDirectory(Path.Combine(_root, "second")).FullName; + string firstMetrics = Directory.CreateDirectory(Path.Combine(first, "metrics")).FullName; + string outputRoot = Path.Combine(_root, "published"); + string repo = Path.Combine(_root, "repo"); + string metrics = Path.Combine(_root, "metrics-override"); + ICatalogBuilder builder = Substitute.For(); + ICatalogPublisher publisher = Substitute.For(); + CatalogModel catalog = new() { GeneratedAt = DateTimeOffset.UnixEpoch, Servers = [], TypeCounts = new Dictionary(), Nodes = [], Edges = [] }; + builder.BuildAsync(Arg.Any(), Arg.Any()).Returns(catalog); + ServiceCollection registrations = new(); + registrations.AddLogging(); + registrations.AddSingleton(builder); + registrations.AddSingleton(publisher); + using ServiceProvider services = registrations.BuildServiceProvider(); + + List arguments = ["catalog", "build", "--objects-root", first, second, + "--output-root", outputRoot, "--repo-root", repo, "--metrics-root", metrics, + "--path-prefix", "extracted", "--prune"]; + var parsed = new RootCommand { CatalogCommand.Build(services) }.Parse([.. arguments]); + Assert.Empty(parsed.Errors); + Assert.Equal(0, await parsed.InvokeAsync()); + await builder.Received(1).BuildAsync(Arg.Is(request => + request.ObjectsRoot == _root && request.Inputs.Count == 2 && + request.Inputs[0].ObjectsRoot == first && request.Inputs[0].MetricsRoot == firstMetrics && + request.Inputs[1].ObjectsRoot == second && request.Inputs[1].MetricsRoot == null && + request.RepoRoot == repo && request.MetricsRoot == metrics && + request.PathPrefix == "extracted"), Arg.Any()); + await publisher.Received(1).PublishAsync(catalog, Path.Combine(outputRoot, "catalog.json"), true, + Arg.Any(), Arg.Any>()); + } + + [Fact] + public async Task CatalogDefaultsPublicationToItsSingleInputRoot() + { + ICatalogBuilder builder = Substitute.For(); + ICatalogPublisher publisher = Substitute.For(); + CatalogModel catalog = new() { GeneratedAt = DateTimeOffset.UnixEpoch, Servers = [], TypeCounts = new Dictionary(), Nodes = [], Edges = [] }; + builder.BuildAsync(Arg.Any(), Arg.Any()).Returns(catalog); + ServiceCollection registrations = new(); + registrations.AddLogging(); + registrations.AddSingleton(builder); + registrations.AddSingleton(publisher); + using ServiceProvider services = registrations.BuildServiceProvider(); + + Assert.Equal(0, await new RootCommand { CatalogCommand.Build(services) } + .Parse(["catalog", "build", "--objects-root", _root]).InvokeAsync()); + await publisher.Received(1).PublishAsync(catalog, Path.Combine(_root, "catalog.json"), false, + Arg.Any(), Arg.Any>()); + } + + [Theory] + [InlineData("--path")] + [InlineData("--output-root")] + public async Task LintFindsSyntaxErrorsInNestedSqlFiles(string option) + { + string nested = Directory.CreateDirectory(Path.Combine(_root, "nested")).FullName; + await File.WriteAllTextAsync(Path.Combine(nested, "invalid.sql"), "SELECT FROM WHERE;"); + ServiceCollection registrations = new(); + registrations.AddLogging(); + using ServiceProvider services = registrations.BuildServiceProvider(); + + Assert.Equal(1, await new RootCommand { LintCommand.Build(services) } + .Parse(["lint", option, _root]).InvokeAsync()); + } + + public void Dispose() => Directory.Delete(_root, recursive: true); +} diff --git a/cli/tests/SyncSql.Core.Tests/Abstractions/ExtractionProgressAggregatorTests.cs b/cli/tests/SyncSql.Core.Tests/Abstractions/ExtractionProgressAggregatorTests.cs new file mode 100644 index 0000000..69d261a --- /dev/null +++ b/cli/tests/SyncSql.Core.Tests/Abstractions/ExtractionProgressAggregatorTests.cs @@ -0,0 +1,30 @@ +using SyncSql.Core.Abstractions; + +namespace SyncSql.Core.Tests.Abstractions; + +public sealed class ExtractionProgressAggregatorTests +{ + [Fact] + public void CombinesChildDeltasWithoutDoubleCountingRepeatedProgress() + { + RecordingProgress target = new(); + ExtractionProgressAggregator aggregator = new(target, initialCount: 10); + IProgress first = aggregator.CreateChild(); + IProgress second = aggregator.CreateChild(); + + first.Report(new("first", 2)); + second.Report(new("second", 3)); + first.Report(new("first", 2)); + first.Report(new("first", 5)); + aggregator.Report(new("finished", 999, Completed: 2, Total: 2)); + + Assert.Equal([12, 15, 15, 18, 18], target.Values.Select(value => value.ObjectsExtracted)); + Assert.Equal(new ExtractionProgress("finished", 18, 2, 2), target.Values[^1]); + } + + private sealed class RecordingProgress : IProgress + { + public List Values { get; } = []; + public void Report(ExtractionProgress value) => Values.Add(value); + } +} diff --git a/cli/tests/SyncSql.Core.Tests/Abstractions/SystemProcessRunnerTests.cs b/cli/tests/SyncSql.Core.Tests/Abstractions/SystemProcessRunnerTests.cs index 8415d92..f98fc64 100644 --- a/cli/tests/SyncSql.Core.Tests/Abstractions/SystemProcessRunnerTests.cs +++ b/cli/tests/SyncSql.Core.Tests/Abstractions/SystemProcessRunnerTests.cs @@ -71,6 +71,7 @@ public async Task RunAsync_ChildFails_ReportsExitCodeWithoutThrowing() Assert.False(result.Succeeded); Assert.NotEqual(0, result.ExitCode); + Assert.False(string.IsNullOrWhiteSpace(result.StandardError)); } [Fact] diff --git a/cli/tests/SyncSql.Core.Tests/Configuration/ConfigurationValidationRegressionTests.cs b/cli/tests/SyncSql.Core.Tests/Configuration/ConfigurationValidationRegressionTests.cs new file mode 100644 index 0000000..f53e7c5 --- /dev/null +++ b/cli/tests/SyncSql.Core.Tests/Configuration/ConfigurationValidationRegressionTests.cs @@ -0,0 +1,63 @@ +using System.Text.Json.Nodes; +using SyncSql.Core.Configuration; + +namespace SyncSql.Core.Tests.Configuration; + +public sealed class ConfigurationValidationRegressionTests +{ + private const string ValidJson = """{"servers":[{"name":"SQL","host":"sql","type":"mssql","credentialsVariablePrefix":"SQL"}]}"""; + + [Theory] + [InlineData("server", "databases", "server 'SQL' databases")] + [InlineData("server", "schemas", "server 'SQL' schemas")] + [InlineData("server", "objectNames", "server 'SQL' objectNames")] + [InlineData("defaults", "databases", "defaults.databases")] + [InlineData("defaults", "schemas", "defaults.schemas")] + [InlineData("defaults", "objectNames", "defaults.objectNames")] + [InlineData("root", "serverSelection", "serverSelection")] + public async Task InvalidRegexInEveryFilterLocationReportsItsLocation(string scope, string key, string location) + { + foreach (string list in new[] { "include", "exclude" }) + { + JsonNode root = JsonNode.Parse(ValidJson)!; + root["defaults"] = new JsonObject(); + JsonNode container = scope switch + { + "server" => root["servers"]![0]!, + "defaults" => root["defaults"]!, + _ => root, + }; + container[key] = new JsonObject { [list] = new JsonArray("[") }; + ConfigValidationException error = await Assert.ThrowsAsync(() => Load(root.ToJsonString())); + Assert.Contains("invalid regex", error.Message, StringComparison.Ordinal); + Assert.Contains(location, error.Message, StringComparison.Ordinal); + } + } + + [Fact] + public async Task MatchingIncludeAndExcludeStillValidateInvalidRegex() + { + JsonNode root = JsonNode.Parse(ValidJson)!; + root["serverSelection"] = new JsonObject { ["include"] = new JsonArray("["), ["exclude"] = new JsonArray("[") }; + await Assert.ThrowsAsync(() => Load(root.ToJsonString())); + } + + [Fact] + public async Task PropertiesAreCaseInsensitiveAndZeroDepthDisablesDiscovery() + { + SyncSqlConfig loaded = await Load("""{"SERVERS":[{"NAME":"SQL","HOST":"sql","TYPE":"mssql","CREDENTIALSVARIABLEPREFIX":"SQL"}],"discovery":{"linkedServers":{"enabled":true,"maxDepth":0}}}"""); + Assert.Equal("SQL", Assert.Single(loaded.Servers).Name); + Assert.Equal(0, loaded.Discovery.LinkedServers.MaxDepth); + } + + private static async Task Load(string json) + { + string path = Path.GetTempFileName(); + try + { + await File.WriteAllTextAsync(path, json); + return await SyncSqlConfigLoader.LoadAsync(path); + } + finally { File.Delete(path); } + } +} diff --git a/cli/tests/SyncSql.Core.Tests/Configuration/LinkedServerDiscoveryContractTests.cs b/cli/tests/SyncSql.Core.Tests/Configuration/LinkedServerDiscoveryContractTests.cs new file mode 100644 index 0000000..8a80cc3 --- /dev/null +++ b/cli/tests/SyncSql.Core.Tests/Configuration/LinkedServerDiscoveryContractTests.cs @@ -0,0 +1,61 @@ +using SyncSql.Core.Configuration; +using SyncSql.Core.Domain; + +namespace SyncSql.Core.Tests.Configuration; + +public sealed class LinkedServerDiscoveryContractTests +{ + private static readonly ServerConfig Parent = new() + { + Name = "Source", + Host = "source", + Type = DatabaseEngine.MsSql, + CredentialsVariablePrefix = "SQL", + ObjectTypes = ["Tables"], + }; + private static DiscoveredLinkedServer Link(string name, string endpoint) => new() + { + Name = name, + DataSource = endpoint, + Product = "SQL Server", + UsesLocalLogin = true, + }; + + [Fact] + public void ZeroDepthPreventsDiscoveryEvenWhenEnabled() + { + var plan = LinkedServerFollowUpPlanner.Plan(Parent, "user", [Link("remote", "remote")], new() { Enabled = true, MaxDepth = 0 }, [Parent]); + Assert.Empty(plan.FollowUps); + Assert.Empty(plan.Skipped); + } + + [Fact] + public void AnyMatchingRemoteLoginPermitsFollowingTheLink() + { + var link = Link("remote", "remote") with { UsesLocalLogin = false, RemoteLoginNames = ["other", "USER"] }; + var plan = LinkedServerFollowUpPlanner.Plan(Parent, "user", [link], new() { Enabled = true }, [Parent]); + Assert.Single(plan.FollowUps); + Assert.Empty(plan.Skipped); + } + + [Fact] + public void RegisteredEndpointKeepsItsPortAndSuffixWhenDiscoveredThroughAnAlias() + { + ServerConfig registered = Parent with { Name = "Registered", Host = "physical.example", Port = 1444, HostNameSuffix = "target.example", Aliases = ["alias,1433"] }; + var plan = LinkedServerFollowUpPlanner.Plan(Parent, "user", [Link("remote", "alias,1433")], + new() { Enabled = true }, [Parent, registered], coveredServers: [Parent]); + ServerConfig target = Assert.Single(plan.FollowUps).Server; + Assert.Equal("physical.example", target.Host); + Assert.Equal(1444, target.Port); + Assert.Equal("target.example", target.HostNameSuffix); + } + + [Fact] + public void NewTargetsWithCollidingLinkNamesReceiveDistinctServerNames() + { + var plan = LinkedServerFollowUpPlanner.Plan(Parent, "user", [Link("remote", "one"), Link("REMOTE", "two")], + new() { Enabled = true }, [Parent]); + Assert.Equal(2, plan.FollowUps.Count); + Assert.Equal(2, plan.FollowUps.Select(followUp => followUp.Server.Name).Distinct(StringComparer.OrdinalIgnoreCase).Count()); + } +} diff --git a/cli/tests/SyncSql.Core.Tests/Configuration/ServerConfigurationContractTests.cs b/cli/tests/SyncSql.Core.Tests/Configuration/ServerConfigurationContractTests.cs new file mode 100644 index 0000000..a776e64 --- /dev/null +++ b/cli/tests/SyncSql.Core.Tests/Configuration/ServerConfigurationContractTests.cs @@ -0,0 +1,72 @@ +using SyncSql.Core.Configuration; +using SyncSql.Core.Domain; + +namespace SyncSql.Core.Tests.Configuration; + +public sealed class ServerConfigurationContractTests +{ + private static readonly ServerConfig Server = new() + { + Name = "Reporting", + Host = "db", + Type = DatabaseEngine.MsSql, + CredentialsVariablePrefix = "DB", + }; + + [Fact] + public void OracleIdentityPreservesServicePortAndNormalizesAliases() + { + ServerIdentity identity = ServerIdentity.FromConfig(Server with + { + Type = DatabaseEngine.Oracle, + Host = " oracle.example ", + Port = 1522, + ServiceName = "Sales", + Aliases = [" alias ", "ALIAS"], + Tags = [" prod ", "PROD", "", " ", null!], + }); + Assert.Equal("ORACLE.EXAMPLE:1522/Sales", identity.Endpoint); + Assert.Equal(["ALIAS", "ORACLE.EXAMPLE:1522/Sales"], identity.Addresses); + Assert.Equal(["prod"], identity.Tags); + Assert.True(identity.Matches(" alias ")); + Assert.False(identity.Matches("alias,1433")); + } + + [Theory] + [InlineData(1)] + [InlineData(65535)] + public void ValidBoundaryPortsStillApplyTheDnsSuffix(int port) => + Assert.Equal($"SQL.EXAMPLE.COM,{port}", ServerIdentity.NormalizeSqlAddress($"sql,{port}", suffix: "example.com")); + + [Fact] + public void DefaultObjectNameFilterIsInheritedWhenServerHasNoOverride() + { + EffectiveFilters filters = EffectiveFilters.Resolve(new() { ObjectNames = new() { Exclude = ["^private_"] } }, Server); + Assert.False(filters.ObjectNames.IsAllowed("private_orders")); + Assert.True(filters.ObjectNames.IsAllowed("orders")); + } + + [Fact] + public void OwnerlessLinkOverrideAppliesToAnOwnedLinkAndRecordsExactEvidence() + { + ServerConfig config = Server with + { + LinkTargets = [ + new() { Name = "Other", DataSource = "wrong" }, + new() { Name = "DL", Owner = "OTHER", DataSource = "wrong-owner" }, + new() { Name = "DL", DataSource = "sql,1444", Database = "Orders", DefaultSchema = "sales", TargetEngine = DatabaseEngine.MsSql }, + ], + }; + LinkMetadata result = LinkTargetEnrichment.Apply(config, "dl", "APP", new()); + Assert.Equal("sql,1444", result.DataSource); + Assert.Equal("Orders", result.Database); + Assert.Equal("sales", result.DefaultSchema); + Assert.Equal(DatabaseEngine.MsSql, result.TargetEngine); + Assert.Equal([ + new LinkEvidence("dataSource", "sql,1444", "config:linkTargets/APP/dl"), + new LinkEvidence("database", "Orders", "config:linkTargets/APP/dl"), + new LinkEvidence("defaultSchema", "sales", "config:linkTargets/APP/dl"), + new LinkEvidence("targetEngine", "mssql", "config:linkTargets/APP/dl"), + ], result.Evidence); + } +} diff --git a/cli/tests/SyncSql.Core.Tests/Credentials/CredentialPrecedenceTests.cs b/cli/tests/SyncSql.Core.Tests/Credentials/CredentialPrecedenceTests.cs new file mode 100644 index 0000000..f4f4a54 --- /dev/null +++ b/cli/tests/SyncSql.Core.Tests/Credentials/CredentialPrecedenceTests.cs @@ -0,0 +1,51 @@ +using NSubstitute; +using SyncSql.Core.Abstractions; +using SyncSql.Core.Credentials; + +namespace SyncSql.Core.Tests.Credentials; + +public sealed class CredentialPrecedenceTests +{ + [Theory] + [InlineData(true)] + [InlineData(false)] + public void LowerPriorityValuesOnlyFillTheMissingHalf(bool usernameFirst) + { + ICredentialProvider first = Substitute.For(); + ICredentialProvider second = Substitute.For(); + ICredentialProvider unused = Substitute.For(); + first.Read("SQL").Returns(usernameFirst ? new PartialCredentials("preferred-user", null) : new PartialCredentials(null, "preferred-password")); + second.Read("SQL").Returns(new PartialCredentials("fallback-user", "fallback-password")); + + LayeredCredentialProvider provider = new(first, second, unused); + Assert.Equal(usernameFirst + ? new PartialCredentials("preferred-user", "fallback-password") + : new PartialCredentials("fallback-user", "preferred-password"), provider.Read("SQL")); + unused.DidNotReceive().Read(Arg.Any()); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void EmptyValuesDoNotPreventFallback(string? empty) + { + ICredentialProvider first = Substitute.For(); + ICredentialProvider second = Substitute.For(); + first.Read("SQL").Returns(new PartialCredentials(empty, empty)); + second.Read("SQL").Returns(new PartialCredentials("user", "password")); + Assert.Equal(new PartialCredentials("user", "password"), new LayeredCredentialProvider(first, second).Read("SQL")); + } + + [Fact] + public async Task FilePropertiesAreCaseInsensitiveAndAllowTrailingCommas() + { + string path = Path.GetTempFileName(); + try + { + await File.WriteAllTextAsync(path, """{"SQL":{"USER":"user","PASSWORD":"password",},}"""); + CredentialsFileProvider provider = await CredentialsFileProvider.LoadAsync(path); + Assert.Equal(new PartialCredentials("user", "password"), provider.Read("sql")); + } + finally { File.Delete(path); } + } +} diff --git a/cli/tests/SyncSql.Core.Tests/ExtractionSchedulerDispatchTests.cs b/cli/tests/SyncSql.Core.Tests/ExtractionSchedulerDispatchTests.cs new file mode 100644 index 0000000..462aeb0 --- /dev/null +++ b/cli/tests/SyncSql.Core.Tests/ExtractionSchedulerDispatchTests.cs @@ -0,0 +1,71 @@ +using SyncSql.Core.Abstractions; +using SyncSql.Core.Domain; + +namespace SyncSql.Core.Tests; + +public sealed class ExtractionSchedulerDispatchTests +{ + [Fact] + public async Task CancellationAfterDispatchButBeforeExecutionNeverInvokesWork() + { + ManualTaskScheduler dispatcher = new(); + ExtractionWorkScheduler scheduler = new(1, dispatcher); + using CancellationTokenSource cancellation = new(); + bool invoked = false; + Task run = scheduler.RunAsync([(DatabaseEngine.MsSql, _ => + { + invoked = true; + return Task.FromResult(42); + })], cancellation.Token); + Assert.False(run.IsCompleted); + Assert.Equal(1, dispatcher.PendingCount); + + cancellation.Cancel(); + dispatcher.Drain(); + + await Assert.ThrowsAnyAsync(() => run.WaitAsync(TimeSpan.FromSeconds(5))); + Assert.False(invoked); + Task retry = scheduler.RunAsync([(DatabaseEngine.MsSql, _ => Task.FromResult(7))], CancellationToken.None); + dispatcher.Drain(); + int[] retried = await retry.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.Equal([7], retried); + } + + [Fact] + public async Task EqualLoadEnginesRotateInsteadOfStarvingTheSecondEngine() + { + ManualTaskScheduler dispatcher = new(); + ExtractionWorkScheduler scheduler = new(1, dispatcher); + List order = []; + Task run = scheduler.RunAsync(new[] { DatabaseEngine.MsSql, DatabaseEngine.Oracle }.SelectMany(engine => + Enumerable.Range(0, 3).Select(index => (engine, (Func>)(_ => + { + order.Add($"{engine}:{index}"); + return Task.FromResult(index); + })))), CancellationToken.None); + dispatcher.Drain(); + + int[] results = await run.WaitAsync(TimeSpan.FromSeconds(5)); + Assert.Equal([0, 1, 2, 0, 1, 2], results); + Assert.Equal(["MsSql:0", "Oracle:0", "MsSql:1", "Oracle:1", "MsSql:2", "Oracle:2"], order); + } + + [Theory] + [InlineData(0)] + [InlineData(-1)] + public void InvalidBudgetsAreRejected(int budget) => + Assert.Throws(() => new ExtractionWorkScheduler(budget)); + + private sealed class ManualTaskScheduler : TaskScheduler + { + private readonly Queue _pending = new(); + public int PendingCount => _pending.Count; + protected override IEnumerable GetScheduledTasks() => _pending.ToArray(); + protected override void QueueTask(Task task) => _pending.Enqueue(task); + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => false; + public void Drain() + { + while (_pending.TryDequeue(out Task? task)) { TryExecuteTask(task); } + } + } +} diff --git a/cli/tests/SyncSql.Core.Tests/Serialization/ExtractedFileContractTests.cs b/cli/tests/SyncSql.Core.Tests/Serialization/ExtractedFileContractTests.cs new file mode 100644 index 0000000..2193771 --- /dev/null +++ b/cli/tests/SyncSql.Core.Tests/Serialization/ExtractedFileContractTests.cs @@ -0,0 +1,111 @@ +using SyncSql.Core.Domain; +using SyncSql.Core.Serialization; + +namespace SyncSql.Core.Tests.Serialization; + +public sealed class ExtractedFileContractTests +{ + private static readonly ExtractedObject Object = new() + { + Server = "SQL", + Database = "App", + Schema = "dbo", + Type = "Tables", + Name = "Orders", + Engine = DatabaseEngine.MsSql, + Ddl = "CREATE TABLE dbo.Orders (Id int);", + }; + + [Theory] + [InlineData(null, "Orders")] + [InlineData("", "Orders")] + [InlineData("dbo", "dbo.Orders")] + public void WrittenHeaderPreservesHumanReadableIdentityAndSeparatesDdl(string? schema, string qualified) + { + string text = ExtractedObjectFile.Write(Object with { Schema = schema }); + Assert.StartsWith("-- Auto-generated by SyncSQL. Do not edit manually; changes will be overwritten.\n-- Path layout: schema/type\n-- Identity: ", text); + Assert.Contains($"\n-- Server: SQL\n-- Database: App\n-- Type: Tables\n-- Object: {qualified}\n-- Engine: mssql\n\n{Object.Ddl}\n", text, StringComparison.Ordinal); + Assert.DoesNotContain("-- ===", text, StringComparison.Ordinal); + } + + [Fact] + public void DenyAndColumnGrantMetadataAreWrittenAndRecovered() + { + GrantEntry grant = new("SELECT", GrantState.Deny, "reader", "DATABASE_ROLE", "Secret"); + string text = ExtractedObjectFile.Write(Object with { Grants = [grant] }); + Assert.Contains("\n\n-- === Grants ===\n-- [grant] SELECT|DENY|reader|DATABASE_ROLE|Secret\n", text, StringComparison.Ordinal); + Assert.Equal(grant, Assert.Single(ExtractedObjectFile.Parse(text.Split('\n')).Grants)); + } + + [Fact] + public void ColumnOnlyDescriptionSurvivesAlongsideUndocumentedColumns() + { + string text = ExtractedObjectFile.Write(Object with + { + Columns = [new("Id", "int", null), new("Secret", "text", "Sensitive")], + }); + Assert.Contains("-- === Extended Properties ===\n-- [column: Secret] MS_Description = Sensitive\n", text, StringComparison.Ordinal); + Assert.Equal("Sensitive", ExtractedObjectFile.Parse(text.Split('\n')).Columns.Single(column => column.Name == "Secret").Description); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" \t")] + public void BlankDescriptionsDoNotCreateEmptyPropertySections(string? description) + { + string text = ExtractedObjectFile.Write(Object with { Columns = [new("Id", "int", description)] }); + Assert.DoesNotContain("-- === Extended Properties ===", text, StringComparison.Ordinal); + } + + [Fact] + public void ParserRecognizesCaseInsensitiveDescriptionPropertiesAndIgnoresUnrelatedProperties() + { + ParsedObjectFile parsed = ExtractedObjectFile.Parse([ + "-- Engine: mssql", "", "SELECT 1;", "", + "-- === Extended Properties ===", + "-- [object] Owner = wrong", "-- [object] ms_description = Object description", + "-- [object] MS_Description = Later description", + "-- [column: Id] Owner = wrong", "-- [column: Name] ms_description = Column description", + "-- unrelated comment", + ]); + Assert.Equal("Object description", parsed.Description); + Assert.Equal(new ExtractedColumn("Name", null, "Column description"), Assert.Single(parsed.Columns)); + } + + [Fact] + public void ParserKeepsAdjacentSectionBodiesSeparate() + { + ParsedObjectFile parsed = ExtractedObjectFile.Parse([ + "-- Engine: mssql", "", "SELECT 1;", "", + "-- === Indexes ===", "CREATE INDEX first;", "", + "-- === Triggers ===", "CREATE TRIGGER second;", + ]); + Assert.Equal("SELECT 1;", parsed.Ddl); + Assert.Equal([new ExtractedSection("Indexes", "CREATE INDEX first;"), new ExtractedSection("Triggers", "CREATE TRIGGER second;")], parsed.Sections); + } + + [Theory] + [InlineData("")] + [InlineData("-- Engine: mssql")] + [InlineData("-- Engine: mssql\n")] + public void EmptyAndHeaderOnlyFilesHaveNoDdl(string text) => + Assert.Empty(ExtractedObjectFile.Parse(text.Split('\n')).Ddl); + + [Fact] + public void EmptyLineSequenceHasNoDdl() => Assert.Empty(ExtractedObjectFile.Parse([]).Ddl); + + [Theory] + [InlineData(".", "_")] + [InlineData("..", "__")] + public void RelativeDirectoryNamesCannotEscapeTheExportTree(string name, string expected) => + Assert.Equal(expected, ExtractedObjectFile.SafeFileName(name)); + + [Fact] + public void HeaderWithoutSeparatorDoesNotLoseTheFirstDdlLine() => + Assert.Equal("SELECT 1;\nSELECT 2;", ExtractedObjectFile.Parse(["-- Engine: mssql", "SELECT 1;", "SELECT 2;"]).Ddl); + + [Fact] + public void EmptyExportAncestryFallsBackToTheServerName() => + Assert.Equal("SQL/App/dbo/Tables/Orders.sql", ExtractedObjectFile.RelativePath("SQL", "App", "dbo", "Tables", "Orders", serverPath: [])); +} diff --git a/docs/mutation-investigation.md b/docs/mutation-investigation.md new file mode 100644 index 0000000..abf81ac --- /dev/null +++ b/docs/mutation-investigation.md @@ -0,0 +1,140 @@ +# CLI mutation investigation — 2026-09-26 + +The original 100% CLI smoke result covered only `NameFilter.cs`. Full runs of +`SyncSql.Core` and `SyncSql.Cli` found substantially more gaps. The regression tests +added during this investigation protect existing behavior. The follow-up also +refactors scheduling and deserialization to make those behaviors testable without +changing their public contracts. + +## Initial investigation results + +| Scope | Before | After targeted regression tests | +| --- | ---: | ---: | +| Entire Core project | 80.56% | 86.67% | +| Core configuration and credentials | 82.60% | 91.40% | +| Core process runner and progress aggregator | 60.71% | 85.71% | +| Catalog and lint commands | 56.59% | 63.57% | + +The initial complete Core run scored 80.56%: 636 killed, 23 timed out, 150 survived, +and nine had no coverage. The initial complete command-project run scored 69.23%: +424 killed, eight timed out, and 192 survived. These are separate projects, not an +aggregate score for the entire CLI solution. Catalog, both extraction projects, +and both lineage projects have not been mutation-tested in this investigation. + +That complete Core verification recorded 687 killed, 22 timed out, 103 survived, and +six uncovered mutations, with the same 119 compile-error exclusions. The final +command verification was scoped to `CatalogCommand.cs` and `LintCommand.cs`: +82 killed and 47 survived, versus 73 killed and 56 survived before. The whole +command-project score was not rerun after adding its five regression cases. + +## Behavioral gaps addressed + +- Credential resolution must preserve the higher-priority half while filling the + missing half from a lower-priority provider, and stop reading providers once both + halves are available. Empty values must permit fallback. Credential files accept + case-insensitive property names and trailing commas. +- Every configuration filter location must reject invalid include and exclude + expressions with a useful location in the error. An expression present in both + lists must still be validated. Zero discovery depth remains valid. +- Oracle identities retain the service and port, normalize aliases, and clean tags. + Valid boundary SQL Server ports still receive the configured DNS suffix. +- Ownerless link overrides apply to owned links when no owner-specific match exists, + retaining exact evidence fields and their configuration source. Default object + name exclusions must be inherited. +- Process failures retain meaningful stderr. Independent extraction jobs contribute + count deltas without double-counting repeated progress; parent updates retain the + accumulated count. +- Catalog commands forward multiple input roots, metrics/repository paths, + `--path-prefix` and publication options. A single input root + supplies the default publication directory. Lint must discover invalid SQL in + nested directories through either `--path` or `--output-root`. + +There are 20 additional Core cases and five additional command-project cases, plus +a stronger assertion in the existing failed-process test. All 302 Core tests and +100 command-project tests pass. + +## Findings from the initial investigation + +Stryker 5.0.0 excludes mutations that cannot compile. The full runs excluded 119 Core +mutations and 390 command-project mutations. In particular, definite-assignment +errors caused Stryker's recovery to remove mutations from the configuration and +credential `LoadAsync` methods and `SyncCommand.Build`. An invalid string arithmetic +mutation also affected `ExtractionProgressDisplay.StartCatalog`. These exclusions +are not killed mutations and must not be interpreted as verified behavior. + +Remaining survivors warrant case-by-case review. Priorities include extracted-file +serialization, linked-server discovery, scheduler fairness/cancellation, and command +failure/progress handling. Other survivors change help/log text, platform window +behavior, or equivalent expressions such as `First` versus `FirstOrDefault` on a +collection guaranteed to be nonempty. Do not suppress them wholesale or claim 100% +protection based on a score that excludes uncompilable mutations. + +The scheduler mutation removing `Token.ThrowIfCancellationRequested()` was killed +in the initial run but survived the final run. That exposed timing-sensitive +test detection; see the follow-up below. + +## Follow-up improvements + +The scheduler now accepts an internal `TaskScheduler` while its public constructor +retains the default thread-pool behavior and `DenyChildAttach` semantics. Tests can +hold a dispatched job before execution, cancel its batch, then release it. They +assert that work is never invoked, cancellation is returned, and the scheduler can +be reused. A controlled dispatcher also verifies rotation between equally loaded +engines without relying on sleeps or thread timing. + +Configuration and credential deserialization now return from small helpers instead +of assigning locals across a `try`/`catch`. Sync queues each work item inside the +successful credential-resolution branch. Terminal log construction uses string +interpolation. These preserve normal behavior while allowing Stryker to instrument +methods it previously dropped after invalid mutations. + +Additional contracts cover human-readable export headers, schema-less names, +column-level DENY grants, column-only descriptions, irrelevant extended properties, +adjacent section boundaries, empty inputs, safe relative filenames, discovery depth, +mixed remote-login mappings, registered ports/suffixes, unique discovered names, +and handled catalog failures restoring the terminal with a failed status. + +### Follow-up measurements + +The complete Core rerun improved from **86.67% to 91.60%**: 741 killed, 22 timed +out, 67 survived, and three uncovered mutations. Compile-error exclusions fell +from 119 to 91. The cancellation-guard removal is killed specifically by +`CancellationAfterDispatchButBeforeExecutionNeverInvokesWork`, which controls +dispatch instead of depending on thread timing. + +The targeted CLI rerun covers `SyncCommand.cs`, `CatalogCommand.cs`, and +`ExtractionProgressDisplay.cs`: **56.32%**, with 242 killed, three timed out, and +190 survivors. This is not comparable to the earlier whole-project score, both +because the scope differs and because previously excluded code now participates. + +| File | Previous compile errors | Follow-up compile errors | Previous survivors | Follow-up survivors | +| --- | ---: | ---: | ---: | ---: | +| Core configuration loader | 26 | 12 | 5 | 7 | +| Credentials file provider | 17 | 3 | 0 | 2 | +| Sync command | 300 | 99 | 8 | 119 | +| Extraction progress display | 23 | 15 | 45 | 48 | +| Catalog command | 10 | 10 | 36 | 23 | +| Linked-server follow-up planner | 20 | 20 | 13 | 8 | + +Neither follow-up run emitted the prior whole-method compiler recovery warnings. +Ordinary uncompilable mutations remain excluded. Newly testable sync paths expose +remaining gaps around server-selection overrides, discovery, and command failure +handling; help/log text also contributes survivors. These require individual +review rather than blanket exclusions. + +The full solution Release verification passed **1,018 tests**, with **21 skipped** +and no failures, including 328 Core and 115 command-project tests. The complete +Core mutation run captured 325 tests before three final serialization edge cases +were added; those cases are included in the Release verification and the focused +serialization rerun. + +The focused serialization rerun scored **97.83%** (179 killed, one timed out, +four survived), up from 82.07% in the previous full Core verification. It retains +28 compile-error exclusions. Remaining survivors affect grant output and parser +control flow and are recorded for individual review. + +Local HTML/JSON reports are under `TestResults/mutation-investigation/` (initial +full runs and focused reruns) and `TestResults/mutation-verification/` (full Core +verification), with follow-up reports under `TestResults/mutation-improvements/`. +They are ignored build artifacts. See [mutation testing](mutation-testing.md) +for pinned tools, commands, CI scheduling, and the scoped merge-gate thresholds. diff --git a/docs/mutation-testing.md b/docs/mutation-testing.md new file mode 100644 index 0000000..6eb2ca4 --- /dev/null +++ b/docs/mutation-testing.md @@ -0,0 +1,99 @@ +# Mutation testing + +See the [CLI mutation investigation](mutation-investigation.md) for measured gaps, +regression tests, and limitations found in the first full Core/command runs. + +Mutation testing checks whether tests detect small changes to production code. +Stryker.NET 5.0.0 runs against the seven CLI production projects and their matching +xUnit projects; StrykerJS 10.0.0 runs against the site application sources +using the existing Vitest configuration. Generated grammar, test code, and build +outputs are excluded. Database integration and sample benchmark suites are not +used: these runs require no database credentials or Docker services. Site packaging +scripts remain covered by ordinary Vitest tests; their subprocess execution and +fixtures outside the site directory need a separate mutation harness. + +## Run locally + +Use the .NET SDK selected by `global.json`, PowerShell 7, and Node.js 22 or 24 LTS. +CI uses Node.js 22. Node.js 25's built-in web storage can conflict with jsdom's +`localStorage` in the existing UI tests. +From the repository root: + +```powershell +# Restore the pinned tool and mutate all seven CLI projects sequentially. +./scripts/Run-MutationTests.ps1 + +# Select one or more projects. +./scripts/Run-MutationTests.ps1 -Project Core,Catalog + +# Fast targeted run; patterns are relative to the production project. +./scripts/Run-MutationTests.ps1 -Project Core -Mutate '**/NameFilter.cs' + +# Enforce a chosen score for a run (0-100). +./scripts/Run-MutationTests.ps1 -Project Core -BreakAt 80 +``` + +From `site/`: + +```sh +npm ci +npm run test:mutation +npm run test:mutation -- --mutate src/lib/reachability.ts +# Run the required CI scope and its committed threshold. +npm run test:mutation -- stryker.ci.config.mjs +``` + +CLI reports are written under `TestResults/mutation//reports/`. +Use `-ArtifactsPath` to choose another output root. Site reports are written to +`site/reports/mutation/mutation.html` and `mutation.json`. Open the HTML report to +inspect individual mutations and the tests that killed them. A surviving mutation +means the selected tests did not detect it; add an assertion or a missing case after +checking whether the change can actually affect behavior. Mutations with no coverage +also reduce the score. Do not exclude survivors simply to raise the score. + +## CI and score policy + +The repository's `pre-push` hook runs the same three required mutation scopes and +thresholds listed below after ordinary tests, coverage, lint, and type checks. +Any mutation failure blocks the push. Core and CLI runs execute sequentially; +allow roughly 15–25 additional minutes, depending on the machine. CLI mutation +reports are saved in `TestResults/pre-push//mutation//reports/`; +site reports remain in `site/reports/mutation/`. Enable the repository hook with +`git config core.hooksPath .githooks` and use the prerequisites listed above. + +The [Mutation testing workflow](../.github/workflows/mutation.yml) runs every Monday +and can be started manually with GitHub Actions. CLI projects run as separate jobs +(at most two concurrently); each Stryker run uses two workers. Jobs have a two-hour +limit and upload HTML/JSON reports with 14-day retention, including available output +after failures. Full runs can take substantially longer than ordinary unit tests. + +The [Quality workflow](../.github/workflows/quality.yml) also runs mutation checks +on every pull request and merge-queue commit, as well as its other triggers. Both +mutation jobs are required by the aggregate **Quality gate**; failure, cancellation, +or a skipped job prevents the gate from passing. Configure **Quality gate** as a +required status check in branch protection to enforce it before merging. + +| Required scope | Minimum score | Measured baseline | +| --- | ---: | ---: | +| Entire Core project | 90% | 91.60% | +| CLI `SyncCommand`, `CatalogCommand`, and `ExtractionProgressDisplay` | 55% | 56.32% | +| Site `src/lib/reachability.ts` | 100% | 100% | + +These checks use full mutation runs of their specified scope, without change-only +filtering. Threshold failures and initial build/test failures fail CI. Reports are +uploaded even after failures, with 14-day retention. The CLI jobs have a 45-minute +limit and site reachability has a 20-minute limit. Results appear in the existing +quality-results PR comment. A targeted score is not a baseline for the whole project. + +The broader weekly/manual workflow and local defaults retain a **0** threshold +while baselines are established for the remaining scopes. Surviving mutations in +those exploratory runs do not fail CI; build and initial test failures still do. + +Configuration lives in `cli/stryker-config.json` and `site/stryker.config.json`. +The Quality workflow passes explicit `-BreakAt` overrides for .NET and uses +`site/stryker.ci.config.mjs` for the site, leaving exploratory defaults unchanged. +`-Mutate` accepts one or more patterns. +No results are uploaded to Stryker Dashboard. + +Reference: [Stryker.NET configuration](https://stryker-mutator.io/docs/stryker-net/configuration/) +and [StrykerJS Vitest runner](https://stryker-mutator.io/docs/stryker-js/vitest-runner/). diff --git a/scripts/Run-MutationTests.ps1 b/scripts/Run-MutationTests.ps1 new file mode 100644 index 0000000..bee9527 --- /dev/null +++ b/scripts/Run-MutationTests.ps1 @@ -0,0 +1,37 @@ +[CmdletBinding()] +param( + [ValidateSet('Core', 'Catalog', 'Cli', 'Extraction.MsSql', 'Extraction.Oracle', 'Lineage.MsSql', 'Lineage.Oracle')] + [string[]] $Project = @('Core', 'Catalog', 'Cli', 'Extraction.MsSql', 'Extraction.Oracle', 'Lineage.MsSql', 'Lineage.Oracle'), + [string[]] $Mutate, + [ValidateRange(0, 100)] + [int] $BreakAt = 0, + [string] $ArtifactsPath = "$PSScriptRoot/../TestResults/mutation" +) + +$ErrorActionPreference = 'Stop' +$repoRoot = [System.IO.Path]::GetFullPath("$PSScriptRoot/..") +$artifacts = [System.IO.Path]::GetFullPath($ArtifactsPath) +Push-Location $repoRoot +try { + & dotnet tool restore + if ($LASTEXITCODE -ne 0) { throw "Tool restore failed with exit code $LASTEXITCODE." } + + foreach ($name in $Project) { + Push-Location "$repoRoot/cli/tests/SyncSql.$name.Tests" + try { + $strykerArgs = @( + 'stryker', '--config-file', "$repoRoot/cli/stryker-config.json", + '--project', "SyncSql.$name.csproj", + '--output', "$artifacts/$name", + '--threshold-high', "$([Math]::Max(80, $BreakAt))", + '--threshold-low', "$([Math]::Max(60, $BreakAt))", + '--break-at', "$BreakAt", '--skip-version-check' + ) + foreach ($pattern in $Mutate) { $strykerArgs += @('--mutate', $pattern) } + & dotnet @strykerArgs + if ($LASTEXITCODE -ne 0) { throw "Mutation testing failed for $name with exit code $LASTEXITCODE." } + } + finally { Pop-Location } + } +} +finally { Pop-Location } diff --git a/site/.prettierignore b/site/.prettierignore index 4d6d5c9..47876d4 100644 --- a/site/.prettierignore +++ b/site/.prettierignore @@ -1,6 +1,8 @@ node_modules/ dist/ coverage/ +reports/ +.stryker-tmp/ public/ vendor/ package-lock.json diff --git a/site/eslint.config.mjs b/site/eslint.config.mjs index de1ddd4..d24a2fb 100644 --- a/site/eslint.config.mjs +++ b/site/eslint.config.mjs @@ -5,7 +5,7 @@ import reactHooks from 'eslint-plugin-react-hooks' import tseslint from 'typescript-eslint' export default defineConfig([ - { ignores: ['node_modules/**', 'dist/**', 'coverage/**', 'public/**'] }, + { ignores: ['node_modules/**', 'dist/**', 'coverage/**', 'public/**', 'reports/**', '.stryker-tmp/**'] }, { files: ['**/*.{js,mjs,ts,tsx}'], extends: [js.configs.recommended, tseslint.configs.recommended], diff --git a/site/package-lock.json b/site/package-lock.json index 5331257..48a3a57 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -20,6 +20,8 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@stryker-mutator/core": "10.0.0", + "@stryker-mutator/vitest-runner": "10.0.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", "@testing-library/user-event": "^14.6.7", @@ -127,6 +129,53 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz", + "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/helper-string-parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure/node_modules/@babel/types": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, "node_modules/@babel/helper-compilation-targets": { "version": "7.29.7", "dev": true, @@ -150,6 +199,164 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.5.tgz", + "integrity": "sha512-GLe05QD98BkNFTkjaqeqF9QSRoHLKrrB4tpoplyQuuPrQJ72rtmkM4wvqJLX/sjZPqkbK5MUYSsuUTqdJoOVjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.5", + "@babel/types": "^8.0.5" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/code-frame": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.6.tgz", + "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.6", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/generator": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.6.tgz", + "integrity": "sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.6", + "@babel/types": "^8.0.6", + "@jridgewell/gen-mapping": "0.4.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/helper-globals": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.6.tgz", + "integrity": "sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/helper-string-parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.6.tgz", + "integrity": "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/traverse": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.6.tgz", + "integrity": "sha512-ilYV+qLStGzSKLipJlETdvdtpK3rAuwfCmCoGKv15MU4EoeIuLkespSNoNLFGOPSciEHx9TLWtN5mv0fz5w5mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.6", + "@babel/generator": "^8.0.6", + "@babel/helper-globals": "^8.0.6", + "@babel/parser": "^8.0.6", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.6", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@babel/types": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/@jridgewell/gen-mapping": { + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz", + "integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@babel/helper-member-expression-to-functions/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-module-imports": { "version": "7.29.7", "dev": true, @@ -178,6 +385,53 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz", + "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression/node_modules/@babel/helper-string-parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression/node_modules/@babel/types": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, "node_modules/@babel/helper-plugin-utils": { "version": "7.29.7", "dev": true, @@ -186,6 +440,164 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz", + "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/code-frame": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.6.tgz", + "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.6", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/generator": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.6.tgz", + "integrity": "sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.6", + "@babel/types": "^8.0.6", + "@jridgewell/gen-mapping": "0.4.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/helper-globals": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.6.tgz", + "integrity": "sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/helper-string-parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.6.tgz", + "integrity": "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/traverse": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.6.tgz", + "integrity": "sha512-ilYV+qLStGzSKLipJlETdvdtpK3rAuwfCmCoGKv15MU4EoeIuLkespSNoNLFGOPSciEHx9TLWtN5mv0fz5w5mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.6", + "@babel/generator": "^8.0.6", + "@babel/helper-globals": "^8.0.6", + "@babel/parser": "^8.0.6", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.6", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@babel/types": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/@jridgewell/gen-mapping": { + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz", + "integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { "version": "7.29.7", "dev": true, @@ -1740,46 +2152,409 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "license": "ISC", - "dependencies": { - "minipass": "^7.0.4" - }, + "node_modules/@inquirer/ansi": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.8.tgz", + "integrity": "sha512-WpQM+Ti6Z40EFwwt+uL2p4UabT+W179zHp6HhLVOzfbwnVn05IPO/eXIZXGNqcT1jbQ15SujNLzQ39k4QPPxBQ==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", + "node_modules/@inquirer/checkbox": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.5.tgz", + "integrity": "sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.3", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", + "node_modules/@inquirer/confirm": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.2.tgz", + "integrity": "sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", + "node_modules/@inquirer/core": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-12.0.3.tgz", + "integrity": "sha512-wsSy0sznmXwkty+2PzZwx00Cazc/E0r0B7mAzdGROz2Ct+DFZXaK7WDjGZvgjRldxH5ZhFVfF2lgkYrqgOw2KA==", "dev": true, "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, "engines": { - "node": ">=6.0.0" - } - }, + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.3.3.tgz", + "integrity": "sha512-YsKkS2q63IiLtaDK/9nqzdComN97SDQrmKiyNggN+ceP4ty+Z6VwyTz3FpjeUWeW1Efss2xHFKCC9sx7hnrsxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/external-editor": "^3.0.5", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.5.tgz", + "integrity": "sha512-uHuXLmXW+TtIfT/9vSBotypAkqn1n34Ul+CLGPos/xANyO4Ff5xZzkYhbKR4NEcfVK4a9mHQOpwVZzluSHFRGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.5.tgz", + "integrity": "sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.9.tgz", + "integrity": "sha512-EAWgUTGQ/Umgga51dE3B2PUHbufuXarDfg86uVgoSgNHNNQnyFKcOrQLWVqYMghuSyHh8+2HUH0Js9cTC1WAdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.6.tgz", + "integrity": "sha512-HtcJhB2QFVXbLuJ5S3syhNbTUVxYvwqV4VRBDkQceBloC9bmTViUoRFP5PbSaDZb3HzfPmpuU/gG4ybVBz4FHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.2.3.tgz", + "integrity": "sha512-6Yuwh1NGSbu1Lo4N1EWjXs1jKRntLg/ZCwhmeorEHde90v1XxAozdbd4Iu30eOQLW+6h1hp2O9ujNfLSbTPJnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.2.2.tgz", + "integrity": "sha512-W9zYdyzogK+6110mqwaSJWCBu2yA5Q/OfnGSjjZB1bNpHlmUozXxTl0+QOZBNeVd6Qo81/qT75gW05gLAtITxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.7.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.7.2.tgz", + "integrity": "sha512-QoRB4wFIjgH5iOhSjoIKMkTvSHDuV+O3OITlIqAYO0oK5x364GJILXiMBvlPiE+klg7Xx9tq5XVqQHcGUDYYPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.5", + "@inquirer/confirm": "^6.3.2", + "@inquirer/editor": "^5.3.3", + "@inquirer/expand": "^5.1.5", + "@inquirer/input": "^5.1.6", + "@inquirer/number": "^4.2.3", + "@inquirer/password": "^5.2.2", + "@inquirer/rawlist": "^5.3.5", + "@inquirer/search": "^4.3.3", + "@inquirer/select": "^5.2.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.5.tgz", + "integrity": "sha512-1oHky1ONfCOwNrnkQGDE1oaSij/3fI6HFMSf2H/WsGO2lEyDX9My82iggITSy9ddSZ8yk8j9v41OI0fVoSIoaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.3.3.tgz", + "integrity": "sha512-fyuIU1Nbpvwlikjg3gXwJFDI11+EFjqQ7P+iByfmivIKQ1vmaykNrD/vy5unHuUqUpsOsnvJ25//tPF7E/RBRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.5.tgz", + "integrity": "sha512-9kc15hr8r/kI+3DO/xLog5nOzTz1jqsHXa6JBFzmQKhkoJ8Slda1I1L/uD8ZSZ9tF1yp79wwXe7mclvX1rqR2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.3", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.1.1.tgz", + "integrity": "sha512-yJoHYrMnxIsJZCY+0Vb66Dy3he3kL3e2wOBKhoSwWWAzZAY82emlxwgprCtp6yRixvNRNq9ztfRWQYPNr3Go7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "dev": true, "license": "MIT" }, @@ -2241,6 +3016,26 @@ "win32" ] }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -2248,31 +3043,810 @@ "dev": true, "license": "MIT" }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", + "node_modules/@stryker-mutator/api": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/api/-/api-10.0.0.tgz", + "integrity": "sha512-ZtAJ0ZT3MVRCWJTBE2h90XB/6E+4lifHYtcTyNG6nU2nLekPgTo4gD5esjX6Okxo1b/JB4jJzyxYB54fwKAoJw==", "dev": true, - "license": "MIT", - "peer": true, + "license": "Apache-2.0", "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" + "mutation-testing-metrics": "3.8.4", + "mutation-testing-report-schema": "3.8.4", + "tslib": "~2.8.0", + "typed-inject": "~5.0.0" }, "engines": { - "node": ">=18" + "node": ">=22.0.0" } }, - "node_modules/@testing-library/jest-dom": { - "version": "7.0.1", + "node_modules/@stryker-mutator/core": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/core/-/core-10.0.0.tgz", + "integrity": "sha512-ZvMsRyaXQQ5e6Thcid9pkuODv6Fn9E3nrBQJUap+hcJuGJ4unm26afo3m6YKSjn8kinyxJ/3TXf0cTWRDaTxVw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@adobe/css-tools": "^4.4.0", + "@inquirer/prompts": "^8.0.0", + "@stryker-mutator/api": "10.0.0", + "@stryker-mutator/instrumenter": "10.0.0", + "@stryker-mutator/util": "10.0.0", + "ajv": "~8.20.0", + "chalk": "~5.6.0", + "commander": "~14.0.0", + "diff-match-patch": "1.0.5", + "emoji-regex": "~10.6.0", + "execa": "~9.6.0", + "json-rpc-2.0": "^1.7.0", + "lodash.groupby": "~4.6.0", + "minimatch": "~10.2.4", + "mutation-server-protocol": "~0.4.0", + "mutation-testing-elements": "3.8.4", + "mutation-testing-metrics": "3.8.4", + "mutation-testing-report-schema": "3.8.4", + "npm-run-path": "~6.0.0", + "progress": "~2.0.3", + "rxjs": "~7.8.1", + "semver": "^7.6.3", + "source-map": "~0.7.4", + "tree-kill": "~1.2.2", + "tslib": "2.8.1", + "typed-inject": "~5.0.0", + "typed-rest-client": "~2.3.0" + }, + "bin": { + "stryker": "bin/stryker.js" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@stryker-mutator/core/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@stryker-mutator/core/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@stryker-mutator/core/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@stryker-mutator/core/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/@stryker-mutator/core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stryker-mutator/core/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@stryker-mutator/core/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@stryker-mutator/instrumenter": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/instrumenter/-/instrumenter-10.0.0.tgz", + "integrity": "sha512-B7Wmn1KlEWyFeOz6D6oGvQGRfi5Xw3VemG6dEKvFQp4qLvxD9Mf4kcZghfxffgnYwXd3bFgqXsJ+ZGlhdfIOrQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/core": "~8.0.0", + "@babel/generator": "~8.0.0", + "@babel/parser": "~8.0.0", + "@babel/plugin-proposal-decorators": "~8.0.0", + "@babel/plugin-transform-explicit-resource-management": "^8.0.0", + "@babel/preset-react": "~8.0.0", + "@babel/preset-typescript": "~8.0.0", + "@babel/traverse": "~8.0.4", + "@stryker-mutator/api": "10.0.0", + "@stryker-mutator/util": "10.0.0", + "angular-html-parser": "~10.11.0", + "semver": "~7.8.0", + "tslib": "2.8.1", + "weapon-regex": "~2.0.0" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/code-frame": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.6.tgz", + "integrity": "sha512-uMHhvmDmwFiLuiozaoRYTZHqMFuu3fem/X9JY78lohAuLoh8LSGGC8JqUzo2cVuBsBxkjMZNk56CE7ock8Xxkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^8.0.6", + "js-tokens": "^10.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/compat-data": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.5.tgz", + "integrity": "sha512-YLsYoQMvL8l8WrGpN3Zj7O1wK5LEBN+cQtux7BcuHyxIXve724XG+zuJ1n3U1cUweRtTzQOA4IHbuQw3N34SZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/core": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.6.tgz", + "integrity": "sha512-5zwYt1V4ji3mXKKRIAkohSJ29WepxNwxQi+7y7P5AcVPh1dcxnZBU2cfxHnPwS+u9+lZnXPsv30TQaF2xg+Tfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.6", + "@babel/generator": "^8.0.6", + "@babel/helper-compilation-targets": "^8.0.6", + "@babel/helpers": "^8.0.5", + "@babel/parser": "^8.0.6", + "@babel/template": "^8.0.0", + "@babel/traverse": "^8.0.6", + "@babel/types": "^8.0.6", + "@types/gensync": "^1.0.5", + "convert-source-map": "^2.0.0", + "empathic": "^2.0.1", + "gensync": "^1.0.0-beta.2", + "import-meta-resolve": "^4.2.0", + "json5": "^2.2.3", + "obug": "^2.1.1", + "verkit": "^0.3.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/generator": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.6.tgz", + "integrity": "sha512-/pcanjdRsonCexCuxgGD8fKnBLLzk2i9uM4kmcmGyyev2R5YDs+rafMYZ89RvaGY1mW8T7C5au0TyYaEeHBGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^8.0.6", + "@babel/types": "^8.0.6", + "@jridgewell/gen-mapping": "0.4.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@types/jsesc": "^2.5.0", + "jsesc": "^3.0.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-compilation-targets": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.6.tgz", + "integrity": "sha512-HTrtZ4+iA0+kzdbCgvdSk1U4pEl3BvpGnlUbualCGNbrVhhQvhgBzurTUmqpWyvyHbWYWaIfdx2mOlL9LJjsew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^8.0.5", + "@babel/helper-validator-option": "^8.0.0", + "browserslist": "^4.24.0", + "flru": "1.0.2", + "verkit": "^0.3.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-create-class-features-plugin": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.6.tgz", + "integrity": "sha512-Hy7wG2Pk1LnAkFMq0CbBk789XMTYVzYGxR7tKgk+TOpZckQRME3Tdou4mMT/8DFjJFxFZ8R6UsS4DR/duBKlVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-member-expression-to-functions": "^8.0.5", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/helper-replace-supers": "^8.0.1", + "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", + "@babel/traverse": "^8.0.6", + "verkit": "^0.3.2" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-globals": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.6.tgz", + "integrity": "sha512-OdnA/N52LhzFpeUuVz5YGuSWjc7TPHiFmVYZbPlSgExocO2GwbxVpSni9MVs5wwW+wZ4kPuwBEBN9pIPDYz1mA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-module-imports": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-8.0.0.tgz", + "integrity": "sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-module-transforms": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-8.0.6.tgz", + "integrity": "sha512-YUi+3X0cb0v97xKhuqkLdrwQtP6XXv1eVbhLRCFyMwG481UskbMQk4mjH7K/Jg3oJ1KLskvFwwq4kF/QnABkWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^8.0.0", + "@babel/helper-validator-identifier": "^8.0.6", + "@babel/traverse": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-plugin-utils": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz", + "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-replace-supers": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz", + "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^8.0.0", + "@babel/helper-optimise-call-expression": "^8.0.0", + "@babel/traverse": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-string-parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.6.tgz", + "integrity": "sha512-+6HYEBnsDRZHJtUTAEfMQd0zMDqdFHS7C+5vAsEZqBUr3vEMWOWlHJ0Jz1gS8jJIlCvLsuA6ivmeoqzH936YcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-validator-identifier": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.6.tgz", + "integrity": "sha512-HYx0YmuchRxRSpnjvcNffnPS8DCauKNmnumIuHG3jnQAVewRA7kfJGwF13ayEpSZs7s4R/oBH4eppuXcAQd5JQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helper-validator-option": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz", + "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/helpers": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.5.tgz", + "integrity": "sha512-fQtPOXjYOYv85PIdwotp2TJGVYOycX0PQq+l844fFAxOULtBy8BVF35GyeueX0r4KvDthqPH5xAI1clQPk/2uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.5" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/parser": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.6.tgz", + "integrity": "sha512-LpGDIYJAzc3Y3PT9x+FxBrGYu2PlWxLmyN7SwPTzaX0LSwEmFJBsRnPBqmaXF2r1v+Zhz6lNiEJ+7yKtM5+Ugg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^8.0.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-proposal-decorators": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz", + "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^8.0.1", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-syntax-decorators": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-syntax-decorators": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz", + "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-syntax-jsx": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-8.0.1.tgz", + "integrity": "sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-syntax-typescript": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-8.0.3.tgz", + "integrity": "sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-destructuring": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-8.0.5.tgz", + "integrity": "sha512-evjxc5fGvpXG2WMGSalpX8IJZVumLIDgk5r5eRhvXRDqFX/GPWq1BeRfJalPCcwJfrR5A/wvegUhx9W5Nr7/mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-8.0.5.tgz", + "integrity": "sha512-Vr20jk/ZxRGu30o0jYvcKl2Pf0tiyr8H9q2ZGxYD6yjOpv9DdP6//I85q0cSOdADZhsu/ZZoApjGVCfl4xnVJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-transform-destructuring": "^8.0.5" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-8.0.1.tgz", + "integrity": "sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^8.0.1", + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-display-name": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-8.0.1.tgz", + "integrity": "sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-jsx": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-8.0.1.tgz", + "integrity": "sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-module-imports": "^8.0.0", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/plugin-syntax-jsx": "^8.0.1", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-8.0.1.tgz", + "integrity": "sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-8.0.1.tgz", + "integrity": "sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-plugin-utils": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/plugin-transform-typescript": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-8.0.6.tgz", + "integrity": "sha512-WF3yRlQtClzScvQbWct00ix63+w3DPiyU0WmCBU4YqK539n3VvJFElElpevoD8wfu6953CbiNoUZBi2B7/5jXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^8.0.0", + "@babel/helper-create-class-features-plugin": "^8.0.6", + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0", + "@babel/plugin-syntax-typescript": "^8.0.3" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/preset-react": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-8.0.1.tgz", + "integrity": "sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/helper-validator-option": "^8.0.0", + "@babel/plugin-transform-react-display-name": "^8.0.1", + "@babel/plugin-transform-react-jsx": "^8.0.1", + "@babel/plugin-transform-react-jsx-development": "^8.0.1", + "@babel/plugin-transform-react-pure-annotations": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/preset-typescript": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-8.0.1.tgz", + "integrity": "sha512-qrPhQIN1NLrPmzgazF9XKQqXrOcp/WJly+K+6ReFonn24FZqRJO7clxOJo6Ni75L+2vAqI3cHVU2OJLBxoPp5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^8.0.1", + "@babel/helper-validator-option": "^8.0.0", + "@babel/plugin-transform-modules-commonjs": "^8.0.1", + "@babel/plugin-transform-typescript": "^8.0.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "peerDependencies": { + "@babel/core": "^8.0.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/template": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz", + "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.0", + "@babel/parser": "^8.0.0", + "@babel/types": "^8.0.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/traverse": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.6.tgz", + "integrity": "sha512-ilYV+qLStGzSKLipJlETdvdtpK3rAuwfCmCoGKv15MU4EoeIuLkespSNoNLFGOPSciEHx9TLWtN5mv0fz5w5mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^8.0.6", + "@babel/generator": "^8.0.6", + "@babel/helper-globals": "^8.0.6", + "@babel/parser": "^8.0.6", + "@babel/template": "^8.0.0", + "@babel/types": "^8.0.6", + "obug": "^2.1.1" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@babel/types": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.6.tgz", + "integrity": "sha512-c+xgSWboV2pdwXP67BUWDVRHi2BO5Q3KwxyFVs7taVTY+fsK47/L51ZXQoV9rozHJDNHvJ4v4WEsI/IlxV2l2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^8.0.6", + "@babel/helper-validator-identifier": "^8.0.6" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/@jridgewell/gen-mapping": { + "version": "0.4.0-beta.0", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.4.0-beta.0.tgz", + "integrity": "sha512-JdGNkbE4GlNPYQhM0L95fBQr7ctLZJ276QXQLTad4t1oSdnnCI3fDq9DW3BqYAWv8Wc3+HS+4Gsii1oPMCfz1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0-beta.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@stryker-mutator/instrumenter/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@stryker-mutator/util": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/util/-/util-10.0.0.tgz", + "integrity": "sha512-LzOpHiJaCp2ABQgnPMlrQQcsK43bd5Vo/2FGL78aN62yDoeRQ+4j3tzeuXxK5OAHdC3fUz6TDoy4IsoAuLAd3w==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@stryker-mutator/vitest-runner": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@stryker-mutator/vitest-runner/-/vitest-runner-10.0.0.tgz", + "integrity": "sha512-SHK2/vfvRUpiz7jXPnQMBnr6zLdm69DK03Mo5mPhaZWcRSygrKUqYsPqWsXsK+5ySHzlMTfCyFK5NQ/X9sJFFw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@stryker-mutator/api": "10.0.0", + "@stryker-mutator/util": "10.0.0", + "semver": "^7.7.4", + "tslib": "~2.8.0" + }, + "engines": { + "node": ">=22.0.0" + }, + "peerDependencies": { + "@stryker-mutator/core": "10.0.0", + "vitest": ">=2.0.0" + } + }, + "node_modules/@stryker-mutator/vitest-runner/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", @@ -2452,6 +4026,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/gensync": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz", + "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3019,6 +4607,16 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/angular-html-parser": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/angular-html-parser/-/angular-html-parser-10.11.0.tgz", + "integrity": "sha512-3vERzJ65UFDr3C7uozLJwsNcQS3FS784dSh583oDgDTTZMgXe3/pdyXgKndxiP5R2lvYRfW6gSl145Hnyf2OFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "dev": true, @@ -3368,6 +4966,37 @@ "qified": "^0.10.1" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001809", "dev": true, @@ -3409,6 +5038,26 @@ "node": "*" } }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "dev": true, + "license": "MIT" + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -3422,6 +5071,16 @@ "version": "5.0.5", "license": "MIT" }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -3718,6 +5377,17 @@ "node": ">=6" } }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "license": "Apache-2.0", @@ -3731,6 +5401,13 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "license": "MIT" }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/discontinuous-range": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", @@ -3743,6 +5420,21 @@ "license": "MIT", "peer": true }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", @@ -3787,6 +5479,23 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, + "node_modules/empathic": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.1.0.tgz", + "integrity": "sha512-AnfC1ATldl49/cvZdLPDjBfrRNwbDO05aibiOtzQu3qtlbJtomNLhF30HEtn/7iBz50dlMECqATo3fG0LrdEgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -3832,6 +5541,19 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -4156,6 +5878,33 @@ "node": ">=10" } }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/expect-type": { "version": "1.4.0", "dev": true, @@ -4198,6 +5947,50 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fdir": { "version": "6.5.0", "dev": true, @@ -4214,6 +6007,22 @@ } } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/file-entry-cache": { "version": "11.1.5", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", @@ -4266,6 +6075,16 @@ "dev": true, "license": "ISC" }, + "node_modules/flru": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flru/-/flru-1.0.2.tgz", + "integrity": "sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -4309,6 +6128,16 @@ "node": ">=0.6" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -4317,6 +6146,62 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -4439,6 +6324,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hashery": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", @@ -4452,6 +6350,19 @@ "node": ">=20" } }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -4523,6 +6434,16 @@ "node": ">= 14" } }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "dev": true, @@ -4570,6 +6491,17 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "license": "MIT" }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -4628,11 +6560,50 @@ "node": ">=0.10.0" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "dev": true, "license": "MIT" }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -4679,6 +6650,13 @@ "node": ">=8" } }, + "node_modules/js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" @@ -4732,6 +6710,13 @@ "node": ">=6" } }, + "node_modules/json-rpc-2.0": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/json-rpc-2.0/-/json-rpc-2.0-1.8.1.tgz", + "integrity": "sha512-BmN42JKn6FI36pcMMzHCXQhpKNEfbeqgSIcUYHKnifq0/4Ey6PDinovwUOgajT7NsgUtpWdDXaCWlpI4LHXN3g==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5070,11 +7055,21 @@ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", "license": "MIT", - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" } }, "node_modules/min-indent": { @@ -5085,6 +7080,13 @@ "node": ">=4" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -5150,6 +7152,53 @@ "dev": true, "license": "MIT" }, + "node_modules/mutation-server-protocol": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mutation-server-protocol/-/mutation-server-protocol-0.4.1.tgz", + "integrity": "sha512-SBGK0j8hLDne7bktgThKI8kGvGTx3rY3LAeQTmOKZ5bVnL/7TorLMvcVF7dIPJCu5RNUWhkkuF53kurygYVt3g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "zod": "^4.1.12" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/mutation-testing-elements": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/mutation-testing-elements/-/mutation-testing-elements-3.8.4.tgz", + "integrity": "sha512-5CF1SNa7at5ZH33vEr+21wNebTSrtNIVvnzaUlxortHajOrIPaSLczIWvg6sI/fsExekQ7jookwf2cHftkckqQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mutation-testing-metrics": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/mutation-testing-metrics/-/mutation-testing-metrics-3.8.4.tgz", + "integrity": "sha512-DZcmndJBH6nrNs3tpiB3OcMVq9KkG2cHCpJSnDxSxPwi9qrafRmoec40xjhkbzOoX1n7/4UkDqg5tIj4A6nvCw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "mutation-testing-report-schema": "3.8.4" + } + }, + "node_modules/mutation-testing-report-schema": { + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/mutation-testing-report-schema/-/mutation-testing-report-schema-3.8.4.tgz", + "integrity": "sha512-s4G71R6Lt/PpZ0cqeglIcgyBdzLM8E+SeCHZAPg1wkSsPtRBa4XfPzAozYKdiJk/TLbNEEb7En9t0/bveuPuxA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/nanoid": { "version": "3.3.18", "dev": true, @@ -5213,11 +7262,54 @@ "node": ">=0.10.0" } }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nwsapi": { "version": "2.2.27", "dev": true, "license": "MIT" }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -5349,6 +7441,19 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "license": "(MIT AND Zlib)" }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.3.0", "dev": true, @@ -5485,12 +7590,38 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/pretty-ms": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.1.tgz", + "integrity": "sha512-HzMy3Geq23nVALD/M2LliU+F+M+gVNsvkQWWqeBZ8HDiCgzo6YPJ/Omrmtq24EFrIsk0a3EkQGEd7bDOo+IhGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/protobufjs": { "version": "7.6.6", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz", @@ -5542,6 +7673,23 @@ "dev": true, "license": "MIT" }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/railroad-diagrams": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", @@ -5690,6 +7838,16 @@ "node": ">=8" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -5799,6 +7957,16 @@ "dev": true, "license": "MIT" }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5965,11 +8133,110 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "dev": true, @@ -6044,6 +8311,19 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "dev": true, @@ -6208,6 +8488,16 @@ "node": "*" } }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -6225,8 +8515,18 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "optional": true + "devOptional": true, + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } }, "node_modules/type-check": { "version": "0.4.0", @@ -6253,6 +8553,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typed-inject": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/typed-inject/-/typed-inject-5.0.0.tgz", + "integrity": "sha512-0Ql2ORqBORLMdAW89TQKZsb1PQkFGImFfVmncXWe7a+AA3+7dh7Se9exxZowH4kbnlvKEFkMxUYdHUpjYWFJaA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/typed-rest-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-2.3.0.tgz", + "integrity": "sha512-FfBj5tjviexjIus3La4n4s9i+f81Zj7HU+lUlQWK219HMRfmzLsbIf4PZF2+X6EouJKyuANpvvef5VrUWM4AFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "des.js": "^1.1.0", + "js-md4": "^0.3.2", + "qs": "^6.14.1", + "tunnel": "0.0.6", + "underscore": "^1.13.8" + }, + "engines": { + "node": ">= 16.0.0" + } + }, "node_modules/typescript": { "version": "5.9.3", "dev": true, @@ -6289,10 +8616,30 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/underscore": { + "version": "1.13.8", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", + "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "8.3.0", "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unzipper": { "version": "0.10.14", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", @@ -6406,6 +8753,19 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/verkit": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.3.2.tgz", + "integrity": "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/vite": { "version": "6.4.3", "dev": true, @@ -6580,6 +8940,13 @@ "node": ">=18" } }, + "node_modules/weapon-regex": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/weapon-regex/-/weapon-regex-2.0.5.tgz", + "integrity": "sha512-BJZkSdtcae9Wb8+hKNtAqR+Q61EMOAuQnpCCHKwT50K7fIWg7/2/RnbKR27YB+sMcYxYLUVj+Asm5Y2XhD8mZg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "dev": true, @@ -6716,6 +9083,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yoctocolors": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.2.0.tgz", + "integrity": "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zip-stream": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", diff --git a/site/package.json b/site/package.json index 7009a94..c54d84c 100644 --- a/site/package.json +++ b/site/package.json @@ -15,6 +15,7 @@ "example:refresh": "node scripts/catalog-example.mjs", "example:check": "node scripts/catalog-example.mjs --check", "test": "vitest run --configLoader runner", + "test:mutation": "stryker run", "test:coverage": "vitest run --configLoader runner --coverage", "verify:ai-model": "node scripts/package-ai-model.mjs --strict --verify-only" }, @@ -39,6 +40,8 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@stryker-mutator/core": "10.0.0", + "@stryker-mutator/vitest-runner": "10.0.0", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.3", "@testing-library/user-event": "^14.6.7", diff --git a/site/src/lib/reachability.test.ts b/site/src/lib/reachability.test.ts index 8a802f8..9045c4a 100644 --- a/site/src/lib/reachability.test.ts +++ b/site/src/lib/reachability.test.ts @@ -31,6 +31,51 @@ describe('getReachable', () => { expect(getReachable('missing', 'outgoing', sameServerIndex).size).toBe(0) }) + it.each(['incoming', 'outgoing'] as const)('does not traverse %s edges from a missing seed', (direction) => { + const index = buildIndex( + makeCatalog({ + nodes: [makeNode({ id: 'known' })], + edges: [makeEdge('missing', 'known'), makeEdge('known', 'missing')], + }), + ) + expect([...getReachable('missing', direction, index)]).toEqual([]) + }) + + it.each(['incoming', 'outgoing'] as const)( + 'records a dangling %s neighbor without traversing through it', + (direction) => { + const edges = [makeEdge('a', 'missing'), makeEdge('missing', 'b')] + const index = buildIndex( + makeCatalog({ + nodes: [makeNode({ id: 'a' }), makeNode({ id: 'b' })], + edges, + }), + ) + const seed = direction === 'outgoing' ? 'a' : 'b' + expect([...getReachable(seed, direction, index)]).toEqual([['missing', 1]]) + }, + ) + + it('does not enter a disconnected component with arbitrary node IDs', () => { + // This is a valid catalog ID, even though Stryker also uses it when mutating + // an empty array. Traversal must never introduce nodes outside the frontier. + const disconnectedId = 'Stryker was here' + const index = buildIndex( + makeCatalog({ + nodes: ['a', 'b', disconnectedId, 'unreachable'].map((id) => makeNode({ id })), + edges: [makeEdge('a', 'b'), makeEdge(disconnectedId, 'unreachable')], + }), + ) + expect([...getReachable('a', 'outgoing', index)]).toEqual([['b', 1]]) + }) + + it('terminates when the frontier is exhausted with no hop limit', () => { + expect([...getReachable('a', 'outgoing', sameServerIndex, Infinity)]).toEqual([ + ['b', 1], + ['c', 2], + ]) + }) + it('stops at maxHops', () => { const reachable = getReachable('a', 'outgoing', sameServerIndex, 1) expect([...reachable.keys()]).toEqual(['b']) diff --git a/site/stryker.ci.config.mjs b/site/stryker.ci.config.mjs new file mode 100644 index 0000000..13ceeef --- /dev/null +++ b/site/stryker.ci.config.mjs @@ -0,0 +1,9 @@ +import { readFileSync } from 'node:fs' + +const config = JSON.parse(readFileSync(new URL('./stryker.config.json', import.meta.url), 'utf8')) + +export default { + ...config, + mutate: ['src/lib/reachability.ts'], + thresholds: { high: 100, low: 100, break: 100 }, +} diff --git a/site/stryker.config.json b/site/stryker.config.json new file mode 100644 index 0000000..1ef2ca1 --- /dev/null +++ b/site/stryker.config.json @@ -0,0 +1,13 @@ +{ + "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "testRunner": "vitest", + "vitest": { "configFile": "vitest.mutation.config.ts" }, + "coverageAnalysis": "perTest", + "mutate": ["src/**/*.{ts,tsx}", "!src/**/*.test.{ts,tsx}", "!src/test/**", "!src/**/*.d.ts"], + "ignorePatterns": ["vendor/**", "public/models/**", "dist/**", "coverage/**", "reports/**"], + "reporters": ["progress", "clear-text", "html", "json"], + "concurrency": 2, + "thresholds": { "high": 80, "low": 60, "break": 0 }, + "htmlReporter": { "fileName": "reports/mutation/mutation.html" }, + "jsonReporter": { "fileName": "reports/mutation/mutation.json" } +} diff --git a/site/vitest.config.ts b/site/vitest.config.ts index dd79fd8..c06717e 100644 --- a/site/vitest.config.ts +++ b/site/vitest.config.ts @@ -1,8 +1,9 @@ -import { defineConfig } from 'vitest/config' +import { configDefaults, defineConfig } from 'vitest/config' export default defineConfig({ esbuild: { jsx: 'automatic' }, test: { + exclude: [...configDefaults.exclude, '**/.stryker-tmp/**'], environment: 'jsdom', setupFiles: ['./src/test/setup.ts'], coverage: { diff --git a/site/vitest.mutation.config.ts b/site/vitest.mutation.config.ts new file mode 100644 index 0000000..59a852a --- /dev/null +++ b/site/vitest.mutation.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import config from './vitest.config' + +// Packaging tests launch subprocesses and change the working directory. Stryker's +// Vitest runner uses worker threads, so mutation runs use application tests only. +export default mergeConfig(config, defineConfig({ test: { include: ['src/**/*.test.{ts,tsx}'] } }))