Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
8b490eb
chore: add .NET 10 solution with core, GUI host, executable and test …
ravenbix Oct 10, 2026
84750f4
feat(core): add SQL executor seam, fake executor and fake clock for t…
ravenbix Oct 10, 2026
4f28b32
feat(gui): host on 127.0.0.1 with a random port and token-to-cookie auth
ravenbix Oct 10, 2026
d46dfed
feat(gui): stream the batch list over SSE and add New batch as a JSON…
ravenbix Oct 10, 2026
58f0c92
feat(gui): serve the prototype page with SSE state and Ctrl+N, with P…
ravenbix Oct 10, 2026
ff9b63d
feat(cli): add sqlrunboard executable with --app-data and --no-browser
ravenbix Oct 10, 2026
4024c55
chore(ci): run all tests on Chromium, Firefox and WebKit on Windows, …
ravenbix Oct 10, 2026
d00f3c6
feat(gui): allow only the hosts 127.0.0.1 and localhost
ravenbix Oct 10, 2026
e11bf5f
feat(gui): reject requests with an Origin header for a different origin
ravenbix Oct 10, 2026
82dd086
feat(gui): write a user-only lock file with the url and token at start
ravenbix Oct 10, 2026
d67d725
feat(core): scan the batch folder with ; filters in natural order
ravenbix Oct 10, 2026
743fcc7
feat(gui): accept POST requests only with Content-Type application/json
ravenbix Oct 10, 2026
4009e2f
feat(gui): find a running server from the lock file and ignore a stal…
ravenbix Oct 10, 2026
b462e07
feat(cli): open the running server and stop when one already runs
ravenbix Oct 10, 2026
a418a44
feat: merge ticket #19 single-instance lock
ravenbix Oct 10, 2026
d46eb21
test(cli): check that the browser executable has no native UI dependency
ravenbix Oct 10, 2026
4c5f5fa
feat(desktop): add sqlrunboard-desktop that shows the page in an Aval…
ravenbix Oct 10, 2026
8cbef0b
feat(core): keep the scripts of a batch and rescan when its settings …
ravenbix Oct 10, 2026
36feebc
feat(gui): save settings and check boxes on the server, add the folde…
ravenbix Oct 10, 2026
5fc8e48
Merge branch 'spec-1-initial-release' into ticket-4
ravenbix Oct 10, 2026
6cde457
feat: merge ticket #4 settings tab and file scan
ravenbix Oct 10, 2026
c27238f
chore(ci): add release workflow that publishes single-file archives o…
ravenbix Oct 10, 2026
ed925b7
Merge branch 'spec-1-initial-release' into ticket-23
ravenbix Oct 10, 2026
898b192
feat(core): save a batch to a version 1 batch file with a relative fo…
ravenbix Oct 10, 2026
5f27c97
feat(core): open, load again and remove batch files in the side list
ravenbix Oct 10, 2026
345d715
feat(core): run a batch: rescan, queue the included scripts and obey …
ravenbix Oct 10, 2026
008123d
feat(gui): run a batch from the header and show its scripts on the board
ravenbix Oct 10, 2026
dfcbb18
Merge branch 'spec-1-initial-release' into ticket-5
ravenbix Oct 10, 2026
75a1bf9
feat(gui): save, save as, open and remove batch files with in-page di…
ravenbix Oct 10, 2026
e1fae33
feat(gui): keep app settings in settings.json in the app data folder
ravenbix Oct 10, 2026
9348567
feat(core): split script text on GO and GO n with the SSMS rules and …
ravenbix Oct 10, 2026
6bf0004
feat(core): send each script as GO parts and read its file with BOM d…
ravenbix Oct 10, 2026
11b2ebb
feat(core): cancel one or all scripts, fail on the command timeout an…
ravenbix Oct 10, 2026
abb67ae
test(gui): wait for the SSE snapshot before the batch file tests pres…
ravenbix Oct 10, 2026
e839d2c
feat(gui): cancel a script from its card menu or drawer and all scrip…
ravenbix Oct 10, 2026
7453817
Merge branch 'spec-1-initial-release' into ticket-8
ravenbix Oct 10, 2026
085386f
Merge branch 'spec-1-initial-release' into ticket-10
ravenbix Oct 10, 2026
dce6e5a
fix(core): stop the queue of a batch that you remove from the list
ravenbix Oct 10, 2026
da5144b
feat(gui): keep the Run tab view and other page settings in the app s…
ravenbix Oct 10, 2026
f609a86
Merge branch 'spec-1-initial-release' into ticket-12
ravenbix Oct 10, 2026
d52c4a6
Merge branch 'spec-1-initial-release' into ticket-10
ravenbix Oct 10, 2026
63f9af1
fix(core): cancel the run of a batch that you remove from the list
ravenbix Oct 10, 2026
8f1e2d4
feat(core): add reruns and end the run when the target or file scan c…
ravenbix Oct 10, 2026
b2a2994
feat(gui): add Rerun, Rerun all failed and the settings-changed note …
ravenbix Oct 10, 2026
e5d170d
feat(core): add the SqlClient executor with per-script connections an…
ravenbix Oct 10, 2026
44e1273
feat(core): keep the batch password in memory and forget it on a logo…
ravenbix Oct 10, 2026
46bd364
test: add the SQL Server integration suite and compose.yaml
ravenbix Oct 10, 2026
1f217ff
feat(core): move a script in the start order, or in the queue during …
ravenbix Oct 10, 2026
d8d21de
Merge branch 'spec-1-initial-release' into ticket-9
ravenbix Oct 10, 2026
17ba6d1
feat(core): end the run when Load again changes the target or the fil…
ravenbix Oct 10, 2026
aa77836
feat(core): sort by file name, keep New and Missing marks until the n…
ravenbix Oct 10, 2026
fb18ed8
feat(core): move a batch in the side list
ravenbix Oct 10, 2026
fa01821
feat(gui): send the move and sort commands to the server, keep the si…
ravenbix Oct 10, 2026
21c3b78
feat(gui): ask for the password, check the logon before a run and sho…
ravenbix Oct 10, 2026
407e36f
feat: run scripts with the SqlClient executor in both executables
ravenbix Oct 10, 2026
ca02c77
feat(core): write run.log and summary.csv in a log folder for each run
ravenbix Oct 10, 2026
156b7cd
feat(gui): log unsaved batches in the app data folder and show log wa…
ravenbix Oct 10, 2026
90a9e60
Merge branch 'spec-1-initial-release' into ticket-11
ravenbix Oct 10, 2026
cee9d62
chore(ci): run the integration suite on Linux with a SQL Server servi…
ravenbix Oct 10, 2026
7401063
test(core): reruns write to the log folder of the current run
ravenbix Oct 10, 2026
f6c6cd2
Merge branch 'spec-1-initial-release' into ticket-13
ravenbix Oct 10, 2026
fb0ff48
fix(gui): hold page renders while a pointer button is down so live ru…
ravenbix Oct 10, 2026
67e40a3
test(gui): open the Run tab by hand in the run view tests
ravenbix Oct 10, 2026
307451f
Merge branch 'spec-1-initial-release' into ticket-7
ravenbix Oct 10, 2026
0968914
feat(gui): check the logon before a rerun after the run ended
ravenbix Oct 10, 2026
8de2848
Merge branch 'spec-1-initial-release' into ticket-12
ravenbix Oct 10, 2026
3d2b0d3
Merge branch 'spec-1-initial-release' into ticket-7
ravenbix Oct 10, 2026
3394a3b
feat: merge ticket #13 script order and move commands
ravenbix Oct 10, 2026
6ad6d25
Merge branch 'spec-1-initial-release' into ticket-7
ravenbix Oct 10, 2026
193cfd7
feat(core): stop all batches and wait until the logs are written
ravenbix Oct 10, 2026
f9ce171
feat(gui): add quit route; stopping cancels all scripts, writes logs …
ravenbix Oct 11, 2026
788690c
feat: merge ticket #12 table view and Run tab view toggle
ravenbix Oct 11, 2026
f291b44
feat: merge ticket #7 real SQL executor
ravenbix Oct 11, 2026
311a790
feat(gui): add Quit button that asks to save unsaved batches and conf…
ravenbix Oct 11, 2026
f41ca96
test(cli): Ctrl+C stops the server and removes the lock file
ravenbix Oct 11, 2026
eaf785d
feat(desktop): close the window when the server stops
ravenbix Oct 11, 2026
3aa7e25
Merge branch 'spec-1-initial-release' into ticket-20
ravenbix Oct 11, 2026
d382a5f
feat(core): keep the messages of each attempt and publish them as mes…
ravenbix Oct 11, 2026
b8eb568
feat(gui): confirm the target before Run, Rerun and Rerun all failed
ravenbix Oct 11, 2026
8559e26
Merge branch 'spec-1-initial-release' into ticket-16
ravenbix Oct 11, 2026
2ff61a3
feat(core): restore the side list and the last run from summary.csv
ravenbix Oct 11, 2026
c4a63d6
test(core): cover the 10,000 message limit and engine errors as messages
ravenbix Oct 11, 2026
54ba8fa
feat(gui): show live attempt messages, the run.log note and log warni…
ravenbix Oct 11, 2026
ec3e0e7
Merge branch 'spec-1-initial-release' into ticket-14
ravenbix Oct 11, 2026
ef349a1
feat(gui): keep the side list in the app settings and restore it at t…
ravenbix Oct 11, 2026
c663c3b
feat(gui): use the native folder picker of the desktop shell for Browse
ravenbix Oct 11, 2026
159b1a2
Merge branch 'spec-1-initial-release' into ticket-15
ravenbix Oct 11, 2026
1437b5d
fix(core): remove a nullability warning in the newest log folder lookup
ravenbix Oct 11, 2026
907e936
feat(gui): use the native Open and Save as dialogs and close the shel…
ravenbix Oct 11, 2026
d7263a6
feat(desktop): answer the page's native dialog requests and quit thro…
ravenbix Oct 11, 2026
740215e
Merge branch 'spec-1-initial-release' into ticket-22
ravenbix Oct 11, 2026
c7a712a
fix(gui): open the card menu on the chart of a Queued timeline row be…
ravenbix Oct 11, 2026
8de54b3
fix(gui): draw the timeline concurrency strip from exact attempt star…
ravenbix Oct 11, 2026
21b060d
fix(gui): keep the minimum timeline bar width at 2 px
ravenbix Oct 11, 2026
b05fe4d
test(gui): cover the timeline bar click, the drawer scroll and the sh…
ravenbix Oct 11, 2026
79bd4ac
Merge branch 'spec-1-initial-release' into ticket-17
ravenbix Oct 11, 2026
0756e11
fix(gui): end an unfinished attempt of an ended run in the timeline c…
ravenbix Oct 11, 2026
da7ce87
test(gui): cover the timeline Columns and Collapse idle gaps toggles …
ravenbix Oct 11, 2026
5ab2921
test(core): stop the runs before a test deletes its temp folder
ravenbix Oct 11, 2026
0bc7771
test(gui): read the timeline header and row boxes in one call during …
ravenbix Oct 11, 2026
57e6dd0
fix(core): start the executors before the run state is published, and…
ravenbix Oct 11, 2026
01dc12d
fix(core): fail an attempt whose executor throws at once, and always …
ravenbix Oct 11, 2026
7fbcddd
fix(gui): write settings.json with a rename and keep a batch command …
ravenbix Oct 11, 2026
11bac60
fix(gui): end the event streams when the app stops, and stop the runs…
ravenbix Oct 11, 2026
8c5e546
test(gui): bound each process wait of the executable tests and kill t…
ravenbix Oct 11, 2026
6159b64
test(gui): delete the temp folder of the Quit page tests
ravenbix Oct 11, 2026
4736b20
test(core): cover the log end of a batch that you remove during its run
ravenbix Oct 11, 2026
bba2251
fix: give each batch state a version so the page drops a late older s…
ravenbix Oct 11, 2026
060aa66
fix: refuse Load again while the run has Queued or Running scripts, a…
ravenbix Oct 11, 2026
1b745be
fix(core): publish the messages of a rerun of a run that came back af…
ravenbix Oct 11, 2026
760dabf
fix(gui): show the error of an attempt that has no messages, as after…
ravenbix Oct 11, 2026
0e7ca7c
fix(gui): show 0 attempts for a script that did not start
ravenbix Oct 11, 2026
1d6637a
fix(gui): check the logon before a rerun during the run for a SQL Ser…
ravenbix Oct 11, 2026
2c9e8e2
fix(core): ignore the letter case of batch file paths on Windows and …
ravenbix Oct 11, 2026
2b0d9e4
fix: queue a missing file on Run, so it fails with File not found whe…
ravenbix Oct 11, 2026
32643fa
fix(core): keep a script Queued for a rerun in summary.csv, so a rest…
ravenbix Oct 11, 2026
5ca6de0
fix(core): compare folders as full paths, so an end separator does no…
ravenbix Oct 11, 2026
90a4fa0
refactor(gui): remove the script source viewer and the dead prototype…
ravenbix Oct 11, 2026
44ecb25
refactor(gui): answer 200 or 404 for a batch command through one helper
ravenbix Oct 11, 2026
bbe9700
test(gui): start the executable in its tests through one helper
ravenbix Oct 11, 2026
7e99e26
refactor(core): move the run of a batch into RunEngine, which shares …
ravenbix Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
name: test (${{ matrix.os }}, ${{ matrix.browser }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- run: dotnet build
# Playwright advises against caching the browsers.
- run: ./tests/SqlRunboard.Gui.Tests/bin/Debug/net10.0/playwright.ps1 install ${{ matrix.browser }} ${{ runner.os == 'Linux' && '--with-deps' || '' }}
- run: dotnet test --no-build
env:
BROWSER: ${{ matrix.browser }}

# The integration suite against a real SQL Server, on Linux only (docs/SPEC.md "Tests").
# The SA password is a throwaway for this run's container, which only this job can reach.
integration:
runs-on: ubuntu-latest
services:
sql:
image: mcr.microsoft.com/mssql/server:2022-latest
env:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: Rb-${{ github.run_id }}-${{ github.run_attempt }}-Sql9
ports:
- 1433:1433
options: >-
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -C -b -S localhost -U sa -P $MSSQL_SA_PASSWORD -Q 'SELECT 1'"
--health-interval 10s --health-timeout 5s --health-retries 20
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- run: dotnet test --project tests/SqlRunboard.Integration.Tests
env:
SQLRUNBOARD_TEST_SQL: Server=localhost,1433;User ID=sa;Password=Rb-${{ github.run_id }}-${{ github.run_attempt }}-Sql9;TrustServerCertificate=true
77 changes: 77 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Release

# Push a tag such as v0.1.0 to publish a release.
on:
push:
tags: ['v*']

permissions:
contents: write

jobs:
publish:
name: publish (${{ matrix.rid }})
strategy:
fail-fast: false
matrix:
include:
- { rid: win-x64, os: windows-latest }
# macOS runner: the SDK gives the apphost an ad-hoc signature, which Apple silicon needs.
- { rid: osx-arm64, os: macos-latest }
- { rid: linux-x64, os: ubuntu-latest }
- { rid: linux-arm64, os: ubuntu-latest }
runs-on: ${{ matrix.os }}
defaults:
run:
shell: pwsh
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v5
with:
global-json-file: global.json
- name: Publish and pack
run: |
$rid = '${{ matrix.rid }}'
$out = "publish/$rid"
foreach ($project in 'sqlrunboard', 'sqlrunboard-desktop') {
dotnet publish "src/$project" -c Release -r $rid --self-contained `
-p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:DebugType=None -o $out
if ($LASTEXITCODE) { exit $LASTEXITCODE }
}
# Pack only the two executables (the win-x64 output also has native .pdb files).
$ext = $rid.StartsWith('win') ? '.exe' : ''
$files = "sqlrunboard$ext", "sqlrunboard-desktop$ext"
$name = "sqlrunboard-${{ github.ref_name }}-$rid"
# tar keeps the Unix execute bit. Windows users expect a zip.
if ($ext) { Compress-Archive -Path ($files | ForEach-Object { "$out/$_" }) -DestinationPath "$name.zip" }
else { tar -czf "$name.tar.gz" -C $out @files }
if ($LASTEXITCODE) { exit $LASTEXITCODE }
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.rid }}
path: sqlrunboard-*
if-no-files-found: error

release:
needs: publish
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v5
with:
path: dist
merge-multiple: true
- name: Create release
shell: pwsh
env:
GH_TOKEN: ${{ github.token }}
run: |
$notes = @'
Each archive contains `sqlrunboard` (opens the GUI in your browser) and `sqlrunboard-desktop` (opens the GUI in its own window). The executables are self-contained: you do not need to install .NET.

The executables are not code signed.

- macOS: the system quarantines downloaded files. After you extract the archive, run `xattr -d com.apple.quarantine sqlrunboard sqlrunboard-desktop` in the folder.
- Windows: SmartScreen can show "Windows protected your PC". Click "More info", then "Run anyway".
- Linux: `sqlrunboard-desktop` needs WebKitGTK (for example `libwebkit2gtk-4.1-0`) on the computer.
'@
gh release create '${{ github.ref_name }}' dist/* --repo '${{ github.repository }}' --title '${{ github.ref_name }}' --notes $notes --generate-notes
7 changes: 7 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
12 changes: 12 additions & 0 deletions SqlRunboard.slnx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/sqlrunboard-desktop/sqlrunboard-desktop.csproj" />
<Project Path="src/SqlRunboard.Core/SqlRunboard.Core.csproj" />
<Project Path="src/SqlRunboard.Gui/SqlRunboard.Gui.csproj" />
<Project Path="src/sqlrunboard/sqlrunboard.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/SqlRunboard.Core.Tests/SqlRunboard.Core.Tests.csproj" />
<Project Path="tests/SqlRunboard.Gui.Tests/SqlRunboard.Gui.Tests.csproj" />
</Folder>
</Solution>
14 changes: 14 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Local SQL Server for the integration tests (docs/SPEC.md "Tests"). The image is amd64 only:
# on Apple Silicon, turn on Rosetta in Docker Desktop.
# MSSQL_SA_PASSWORD=<strong password> docker compose up -d
# SQLRUNBOARD_TEST_SQL="Server=localhost,1433;User ID=sa;Password=<same>;TrustServerCertificate=true"
# dotnet test --project tests/SqlRunboard.Integration.Tests
services:
sql:
image: mcr.microsoft.com/mssql/server:2022-latest
platform: linux/amd64
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: ${MSSQL_SA_PASSWORD:?set MSSQL_SA_PASSWORD}
ports:
- "${SQL_PORT:-1433}:1433"
9 changes: 9 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature"
},
"test": {
"runner": "Microsoft.Testing.Platform"
}
}
55 changes: 55 additions & 0 deletions src/SqlRunboard.Core/Batch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Text.Json.Serialization;

namespace SqlRunboard.Core;

public enum Auth { Integrated, SqlServer, EntraInteractive, EntraDefault }

public enum Encrypt { Mandatory, Optional, Strict }

/// <summary>The target block of the batch file. See docs/SPEC.md "Batch".</summary>
public sealed record BatchTarget
{
public string? Server { get; init; }
public string? Database { get; init; }
public Auth Auth { get; init; } = Auth.Integrated;
public string? User { get; init; }
public string? PasswordVariable { get; init; }
public Encrypt Encrypt { get; init; } = Encrypt.Mandatory;
public bool TrustServerCertificate { get; init; }
public string ExtraConnectionString { get; init; } = "";
}

/// <summary>The batch settings, with the defaults from docs/SPEC.md "Batch".</summary>
public sealed record BatchSettings
{
public string? Folder { get; init; }
public string Filters { get; init; } = "*.sql";
public bool Recurse { get; init; }
public int ParallelLimit { get; init; } = 4;
public bool StopOnFirstFailure { get; init; }
public bool ConfirmBeforeRun { get; init; }
public int? TimeoutSeconds { get; init; }
public BatchTarget Target { get; init; } = new();

/// <summary>The ranges of docs/SPEC.md "Batch". JSON input can also give null for the non-null properties.</summary>
public bool IsValid => this is { ParallelLimit: >= 1 and <= 64, TimeoutSeconds: null or >= 1, Filters: not null, Target: not null };
}

/// <summary>
/// One file of the batch. Path is relative to the folder, with /. Include false: a cleared check box.
/// State: New or Missing until the next save (docs/SPEC.md "Script order"). The batch file never has it.
/// </summary>
public sealed record ScriptEntry(string Path, bool Include, [property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] ScriptState? State = null);

public enum ScriptState { New, Missing }

/// <summary>
/// One entry in the side list. File is null for an unsaved batch. Scripts is the start order. Run is the current run, or null.
/// FileError: the app could not read the batch file at the start (for example "File not found"). The entry stays until you remove it.
/// Version: each change of the batch gets a higher number, so the page can drop a state that is older than the one it has.
/// </summary>
public sealed record Batch(int Id, string Name, string? File, bool Dirty, BatchSettings Settings, IReadOnlyList<ScriptEntry> Scripts, RunState? Run = null,
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] string? FileError = null, long Version = 0);

/// <summary>The move commands of docs/SPEC.md "Script order". One item at a time.</summary>
public enum MoveTo { Up, Down, Top, Bottom }
43 changes: 43 additions & 0 deletions src/SqlRunboard.Core/BatchFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SqlRunboard.Core;

/// <summary>The file on disk changed after the app loaded or saved it. The user must choose: overwrite it, or load it again.</summary>
public sealed class BatchFileChangedException(string file) : IOException($"The file changed on disk: {file}");

/// <summary>The batch file, schema version 1. See docs/SPEC.md "Batch". It holds no secrets: the password is never in BatchSettings.</summary>
public static class BatchFile
{
public const string Extension = ".sqlrunboard.json";

// A key that is not in the file gets the spec default.
sealed record Json(int Version, string? Folder = null, string Filters = "*.sql", bool Recurse = false, int ParallelLimit = 4, bool StopOnFirstFailure = false,
bool ConfirmBeforeRun = false, int? TimeoutSeconds = null, BatchTarget? Target = null, IReadOnlyList<ScriptEntry>? Scripts = null);

static readonly JsonSerializerOptions Options = new(JsonSerializerDefaults.Web) { WriteIndented = true, Converters = { new JsonStringEnumConverter() } };

public static bool HasExtension(string path) => path.EndsWith(Extension, StringComparison.OrdinalIgnoreCase);

/// <summary>The batch name: the file name without .sqlrunboard.json.</summary>
public static string NameOf(string file) => Path.GetFileName(file)[..^Extension.Length];

public static string Format(BatchSettings s, IReadOnlyList<ScriptEntry> scripts) =>
JsonSerializer.Serialize(new Json(1, s.Folder, s.Filters, s.Recurse, s.ParallelLimit, s.StopOnFirstFailure, s.ConfirmBeforeRun, s.TimeoutSeconds, s.Target, scripts), Options);

/// <summary>Throws InvalidDataException for an incorrect file.</summary>
public static (BatchSettings Settings, IReadOnlyList<ScriptEntry> Scripts) Parse(string text)
{
Json? j;
try { j = JsonSerializer.Deserialize<Json>(text, Options); }
catch (JsonException e) { throw new InvalidDataException($"Not a batch file: {e.Message}"); }
if (j is not { Version: 1 }) throw new InvalidDataException("Not a batch file of version 1.");
var settings = new BatchSettings
{
Folder = j.Folder, Filters = j.Filters, Recurse = j.Recurse, ParallelLimit = j.ParallelLimit, StopOnFirstFailure = j.StopOnFirstFailure,
ConfirmBeforeRun = j.ConfirmBeforeRun, TimeoutSeconds = j.TimeoutSeconds, Target = j.Target ?? new(),
};
if (!settings.IsValid || j.Scripts?.Any(s => s?.Path is null) == true) throw new InvalidDataException("The batch file has an incorrect setting.");
return (settings, [.. (j.Scripts ?? []).Select(s => new ScriptEntry(s.Path, s.Include))]); // a State in the file is not ours
}
}
Loading
Loading