Prepare release v26.7.3 (#871)
Some checks are pending
Build and Release / Determine run mode (push) Waiting to run
Build and Release / Read metadata (push) Blocked by required conditions
Build and Release / Sync Flatpak repo (push) Blocked by required conditions
Build and Release / Collect Flatpak artifacts (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg,app,updater, dmg) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis,updater, nsis) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage,updater, appimage) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions

This commit is contained in:
Thorsten Sommer 2026-07-19 16:15:09 +02:00 committed by GitHub
parent 58f87277ef
commit f666760415
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 301 additions and 17 deletions

View File

@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
using SharedTools;
@ -41,6 +42,39 @@ public sealed partial class UpdateMetadataCommands
// Prepare the metadata for the next release:
await this.PerformPrepare(action, true, version);
await this.BuildPreparedRelease(offline);
}
[Command("rebuild-release", Description = "Prepare & build a new build of the current release")]
public async Task RebuildRelease(
[Option("offline", Description = "Skip downloads and use locally available build dependencies")] bool offline = false)
{
if(!Environment.IsWorkingDirectoryValid())
return;
Console.WriteLine("==============================");
Console.WriteLine("- Prepare a new build of the current release ...");
RebuildReleaseState releaseState;
try
{
releaseState = await this.ValidateRebuildReleaseState();
}
catch (InvalidOperationException exception)
{
Console.WriteLine($"- Error: {exception.Message}");
return;
}
await this.ApplyRebuildReleaseState(releaseState, DateTime.UtcNow);
await this.UpdateReleaseDependenciesAndLicence();
Console.WriteLine();
await this.BuildPreparedRelease(offline);
}
private async Task BuildPreparedRelease(bool offline)
{
// Build once to allow the Rust compiler to read the changed metadata
// and to update all .NET artifacts:
await this.Build(offline);
@ -124,18 +158,23 @@ public sealed partial class UpdateMetadataCommands
var buildTime = await this.UpdateBuildTime();
await this.UpdateChangelog(buildNumber, appVersion.VersionText, buildTime);
await this.CreateNextChangelog(buildNumber, appVersion);
await this.UpdateDotnetVersion();
await this.UpdateRustVersion();
await this.UpdateMudBlazorVersion();
await this.UpdateTauriVersion();
await this.UpdateVectorStoreVersion();
await this.UpdateProjectCommitHash();
await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "..", "..", "LICENSE.md")));
await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "Pages", "Information.razor.cs")));
await this.UpdateReleaseDependenciesAndLicence();
Console.WriteLine();
}
}
private async Task UpdateReleaseDependenciesAndLicence()
{
await this.UpdateDotnetVersion();
await this.UpdateRustVersion();
await this.UpdateMudBlazorVersion();
await this.UpdateTauriVersion();
await this.UpdateVectorStoreVersion();
await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "..", "..", "LICENSE.md")));
await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "Pages", "Information.razor.cs")));
}
[Command("build", Description = "Build MindWork AI Studio")]
public async Task Build(
[Option("offline", Description = "Skip downloads and use locally available build dependencies")] bool offline = false)
@ -358,6 +397,205 @@ public sealed partial class UpdateMetadataCommands
Console.WriteLine(" done.");
}
private async Task<RebuildReleaseState> ValidateRebuildReleaseState()
{
const int APP_VERSION_INDEX = 0;
const int BUILD_TIME_INDEX = 1;
const int BUILD_NUMBER_INDEX = 2;
var metadataPath = Environment.GetMetadataPath();
var metadataContent = await File.ReadAllTextAsync(metadataPath, Encoding.UTF8);
var metadataLines = SplitLines(metadataContent);
if (metadataLines.Length <= 8)
throw new InvalidOperationException("The metadata file does not contain all required release fields.");
var appVersion = metadataLines[APP_VERSION_INDEX].Trim();
if (!ExactAppVersionRegex().IsMatch(appVersion))
throw new InvalidOperationException($"The metadata version '{appVersion}' is not a valid app version.");
if (!DateTime.TryParseExact(metadataLines[BUILD_TIME_INDEX].Trim(), "yyyy-MM-dd HH:mm:ss 'UTC'", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, out var buildTime))
throw new InvalidOperationException($"The metadata build time '{metadataLines[BUILD_TIME_INDEX]}' is not a valid UTC build time.");
if (!int.TryParse(metadataLines[BUILD_NUMBER_INDEX].Trim(), out var buildNumber))
throw new InvalidOperationException($"The metadata build number '{metadataLines[BUILD_NUMBER_INDEX]}' is not a number.");
var changelogDirectory = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog");
var changelogFilename = $"v{appVersion}.md";
var changelogPath = Path.Combine(changelogDirectory, changelogFilename);
if (!File.Exists(changelogPath))
throw new InvalidOperationException($"The current changelog file '{changelogFilename}' does not exist.");
var changelogContent = await File.ReadAllTextAsync(changelogPath, Encoding.UTF8);
var changelogHeader = FormatChangelogHeader(appVersion, buildNumber, buildTime);
if (GetFirstLine(changelogContent) != changelogHeader)
throw new InvalidOperationException($"The current changelog header does not match v{appVersion}, build {buildNumber}, and the metadata build time.");
var changelogCodePath = Path.Combine(Environment.GetAIStudioDirectory(), "Components", "Changelog.Logs.cs");
var changelogCode = await File.ReadAllTextAsync(changelogCodePath, Encoding.UTF8);
var changelogLogEntry = FormatChangelogLogEntry(appVersion, buildNumber, buildTime, changelogFilename);
if (CountOccurrences(changelogCode, changelogLogEntry) != 1)
throw new InvalidOperationException($"The in-app changelog list must contain exactly one matching entry for v{appVersion}, build {buildNumber}.");
var nextChangelogBuildNumber = buildNumber + 1;
var nextChangelogPattern = new Regex($"^# v(?<version>[0-9]+\\.[0-9]+\\.[0-9]+), build {nextChangelogBuildNumber} \\(20[0-9]{{2}}-[0-9]{{2}}-xx xx:xx UTC\\)$");
var nextChangelogCandidates = new List<(string Path, string Content, string Header, string Version)>();
foreach (var candidatePath in Directory.GetFiles(changelogDirectory, "v*.md"))
{
if (candidatePath == changelogPath)
continue;
var candidateContent = await File.ReadAllTextAsync(candidatePath, Encoding.UTF8);
var candidateHeader = GetFirstLine(candidateContent);
var candidateMatch = nextChangelogPattern.Match(candidateHeader);
if (candidateMatch.Success)
nextChangelogCandidates.Add((candidatePath, candidateContent, candidateHeader, candidateMatch.Groups["version"].Value));
}
if (nextChangelogCandidates.Count != 1)
throw new InvalidOperationException($"Expected exactly one future changelog reserving build {nextChangelogBuildNumber}, but found {nextChangelogCandidates.Count}.");
var nextChangelog = nextChangelogCandidates[0];
var metainfoPath = Path.Combine(Environment.GetRustRuntimeDirectory(), "packaging", "linux", "org.mindworkai.AIStudio.metainfo.xml");
if (!File.Exists(metainfoPath))
throw new InvalidOperationException("The AppStream metainfo file does not exist.");
var metainfoContent = await File.ReadAllTextAsync(metainfoPath, Encoding.UTF8);
var releaseTags = ReleaseTagRegex().Matches(metainfoContent).Cast<Match>().ToList();
var matchingReleaseTags = releaseTags.Where(match => ReleaseTagHasVersion(match.Value, appVersion)).ToList();
if (matchingReleaseTags.Count != 1 || releaseTags.Count == 0 || matchingReleaseTags[0].Index != releaseTags[0].Index)
throw new InvalidOperationException($"The AppStream metainfo must contain v{appVersion} exactly once as its first release.");
var metainfoReleaseTag = matchingReleaseTags[0].Value;
if (!StableReleaseTypeRegex().IsMatch(metainfoReleaseTag) || !ReleaseDateRegex().IsMatch(metainfoReleaseTag))
throw new InvalidOperationException($"The AppStream entry for v{appVersion} must be stable and contain a release date.");
var headCommitHash = (await this.ReadCommandOutput(Environment.GetAIStudioDirectory(), "git", "rev-parse HEAD")).Trim();
if (!GitCommitHashRegex().IsMatch(headCommitHash))
throw new InvalidOperationException("The current Git commit hash could not be determined.");
return new(
metadataPath,
metadataContent,
metadataLines,
appVersion,
buildNumber,
changelogPath,
changelogContent,
changelogHeader,
changelogCodePath,
changelogCode,
changelogLogEntry,
nextChangelog.Path,
nextChangelog.Content,
nextChangelog.Header,
nextChangelog.Version,
metainfoPath,
metainfoContent,
metainfoReleaseTag,
headCommitHash[..11]);
}
private async Task ApplyRebuildReleaseState(RebuildReleaseState releaseState, DateTime buildTime)
{
const int BUILD_TIME_INDEX = 1;
const int BUILD_NUMBER_INDEX = 2;
const int COMMIT_HASH_INDEX = 8;
buildTime = buildTime.ToUniversalTime();
var buildNumber = releaseState.BuildNumber + 1;
var buildTimeString = buildTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) + " UTC";
Console.WriteLine($"- Updating build number from '{releaseState.BuildNumber}' to '{buildNumber}'.");
Console.WriteLine($"- Updating build time to '{buildTimeString}'.");
releaseState.MetadataLines[BUILD_TIME_INDEX] = buildTimeString;
releaseState.MetadataLines[BUILD_NUMBER_INDEX] = buildNumber.ToString(CultureInfo.InvariantCulture);
releaseState.MetadataLines[COMMIT_HASH_INDEX] = $"{releaseState.HeadCommitHash}, release";
var updatedMetadata = JoinLines(releaseState.MetadataContent, releaseState.MetadataLines);
await File.WriteAllTextAsync(releaseState.MetadataPath, updatedMetadata, Environment.UTF8_NO_BOM);
var updatedChangelogHeader = FormatChangelogHeader(releaseState.AppVersion, buildNumber, buildTime);
var updatedChangelog = ReplaceExactlyOnce(releaseState.ChangelogContent, releaseState.ChangelogHeader, updatedChangelogHeader);
await File.WriteAllTextAsync(releaseState.ChangelogPath, updatedChangelog, Environment.UTF8_NO_BOM);
Console.WriteLine($"- Updated the header of '{Path.GetFileName(releaseState.ChangelogPath)}'.");
var changelogFilename = Path.GetFileName(releaseState.ChangelogPath);
var updatedChangelogLogEntry = FormatChangelogLogEntry(releaseState.AppVersion, buildNumber, buildTime, changelogFilename);
var updatedChangelogCode = ReplaceExactlyOnce(releaseState.ChangelogCode, releaseState.ChangelogLogEntry, updatedChangelogLogEntry);
await File.WriteAllTextAsync(releaseState.ChangelogCodePath, updatedChangelogCode, Environment.UTF8_NO_BOM);
Console.WriteLine("- Updated the existing in-app changelog entry.");
var updatedNextChangelogHeader = $"# v{releaseState.NextChangelogVersion}, build {buildNumber + 1} ({GetPlaceholderBuildTime(releaseState.NextChangelogHeader)})";
var updatedNextChangelog = ReplaceExactlyOnce(releaseState.NextChangelogContent, releaseState.NextChangelogHeader, updatedNextChangelogHeader);
await File.WriteAllTextAsync(releaseState.NextChangelogPath, updatedNextChangelog, Environment.UTF8_NO_BOM);
Console.WriteLine($"- Reserved build {buildNumber + 1} for '{Path.GetFileName(releaseState.NextChangelogPath)}'.");
var releaseDate = buildTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
var updatedMetainfoReleaseTag = ReleaseDateRegex().Replace(releaseState.MetainfoReleaseTag, $"date=\"{releaseDate}\"", 1);
var updatedMetainfo = ReplaceExactlyOnce(releaseState.MetainfoContent, releaseState.MetainfoReleaseTag, updatedMetainfoReleaseTag);
await File.WriteAllTextAsync(releaseState.MetainfoPath, updatedMetainfo, Environment.UTF8_NO_BOM);
Console.WriteLine($"- Updated the AppStream release date to '{releaseDate}'.");
}
private static string FormatChangelogHeader(string appVersion, int buildNumber, DateTime buildTime)
{
return $"# v{appVersion}, build {buildNumber} ({buildTime.ToUniversalTime():yyyy-MM-dd HH:mm} UTC)";
}
private static string FormatChangelogLogEntry(string appVersion, int buildNumber, DateTime buildTime, string changelogFilename)
{
return $"new ({buildNumber}, \"v{appVersion}, build {buildNumber} ({buildTime.ToUniversalTime():yyyy-MM-dd HH:mm} UTC)\", \"{changelogFilename}\"),";
}
private static string GetFirstLine(string content)
{
var lineEnd = content.IndexOf('\n');
return (lineEnd < 0 ? content : content[..lineEnd]).TrimEnd('\r');
}
private static string GetPlaceholderBuildTime(string changelogHeader)
{
var start = changelogHeader.LastIndexOf('(') + 1;
return changelogHeader[start..^1];
}
private static bool ReleaseTagHasVersion(string releaseTag, string appVersion)
{
return Regex.IsMatch(releaseTag, $"\\bversion=\"{Regex.Escape(appVersion)}\"");
}
private static int CountOccurrences(string content, string value)
{
var count = 0;
var index = 0;
while ((index = content.IndexOf(value, index, StringComparison.Ordinal)) >= 0)
{
count++;
index += value.Length;
}
return count;
}
private static string ReplaceExactlyOnce(string content, string oldValue, string newValue)
{
if (CountOccurrences(content, oldValue) != 1)
throw new InvalidOperationException("A previously validated release value is no longer unique.");
return content.Replace(oldValue, newValue, StringComparison.Ordinal);
}
private static string[] SplitLines(string content)
{
return content.Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n');
}
private static string JoinLines(string originalContent, string[] lines)
{
var lineEnding = originalContent.Contains("\r\n", StringComparison.Ordinal) ? "\r\n" : "\n";
return string.Join(lineEnding, lines);
}
private async Task<string> ReadPdfiumVersion()
{
const int PDFIUM_VERSION_INDEX = 10;
@ -729,6 +967,27 @@ public sealed partial class UpdateMetadataCommands
return buildTime;
}
private sealed record RebuildReleaseState(
string MetadataPath,
string MetadataContent,
string[] MetadataLines,
string AppVersion,
int BuildNumber,
string ChangelogPath,
string ChangelogContent,
string ChangelogHeader,
string ChangelogCodePath,
string ChangelogCode,
string ChangelogLogEntry,
string NextChangelogPath,
string NextChangelogContent,
string NextChangelogHeader,
string NextChangelogVersion,
string MetainfoPath,
string MetainfoContent,
string MetainfoReleaseTag,
string HeadCommitHash);
[GeneratedRegex("""(?ms).?(NET\s+SDK|SDK\s+\.NET)\s*:\s+Version:\s+(?<sdkVersion>[0-9.]+).+Commit:\s+(?<sdkCommit>[a-zA-Z0-9]+).+Host:\s+Version:\s+(?<hostVersion>[0-9.]+).+Commit:\s+(?<hostCommit>[a-zA-Z0-9]+)""")]
private static partial Regex DotnetVersionRegex();
@ -747,9 +1006,24 @@ public sealed partial class UpdateMetadataCommands
[GeneratedRegex("""^\s*Copyright\s+(?<year>[0-9]{4})""")]
private static partial Regex FindCopyrightRegex();
[GeneratedRegex("""([0-9]{4})""")]
[GeneratedRegex("([0-9]{4})")]
private static partial Regex ReplaceCopyrightYearRegex();
[GeneratedRegex("""(?<major>[0-9]+)\.(?<minor>[0-9]+)\.(?<patch>[0-9]+)""")]
private static partial Regex AppVersionRegex();
[GeneratedRegex("""^[0-9]+\.[0-9]+\.[0-9]+$""")]
private static partial Regex ExactAppVersionRegex();
[GeneratedRegex("""<release\b[^>]*>""")]
private static partial Regex ReleaseTagRegex();
[GeneratedRegex("\\btype=\"stable\"")]
private static partial Regex StableReleaseTypeRegex();
[GeneratedRegex("\\bdate=\"[^\"]*\"")]
private static partial Regex ReleaseDateRegex();
[GeneratedRegex("^[0-9a-fA-F]{40,64}$")]
private static partial Regex GitCommitHashRegex();
}

View File

@ -13,7 +13,7 @@ public partial class Changelog
public static readonly Log[] LOGS =
[
new (245, "v26.7.3, build 245 (2026-07-15 19:10 UTC)", "v26.7.3.md"),
new (246, "v26.7.3, build 246 (2026-07-19 14:01 UTC)", "v26.7.3.md"),
new (244, "v26.7.2, build 244 (2026-07-06 18:35 UTC)", "v26.7.2.md"),
new (243, "v26.7.1, build 243 (2026-07-05 16:39 UTC)", "v26.7.1.md"),
new (242, "v26.6.2, build 242 (2026-06-21 14:07 UTC)", "v26.6.2.md"),

View File

@ -1,4 +1,4 @@
# v26.7.3, build 245 (2026-07-15 19:10 UTC)
# v26.7.3, build 246 (2026-07-19 14:01 UTC)
- Added support for OpenAI GPT-5.6 Sol, Terra, and Luna; Anthropic Claude Fable 5 and Mythos 5; and Google Gemini 3 Flash, Gemini 3.1 Flash-Lite, Gemini 3.1 Pro, and Gemini 3.5 Flash.
- Added a log viewer assistant that shows AI Studio log files in a read-only view with search, log filters, highlighting, and auto-refresh.
- Added audio and video transcription for chats and assistants. AI Studio now prepares supported media locally, sends only normalized audio to the configured transcription provider, and attaches the resulting transcript instead of the original media.
@ -17,7 +17,7 @@
- Fixed being able to switch document analysis policies while an analysis or media transcription was still in progress.
- Fixed file extension handling so files are recognized correctly regardless of uppercase or lowercase letters in their extensions. Thanks, Paul Schweiß, for reporting this issue.
- Fixed AI Studio failing to start on Linux systems & showing an outdated version on the Flatpak page.
- Upgraded Rust to v1.97.0.
- Upgraded Rust to v1.97.1.
- Upgraded .NET to v9.0.18.
- Upgraded Tauri to v2.11.5.
- Upgraded common dependencies.

View File

@ -1 +1 @@
# v26.7.4, build 246 (2026-07-xx xx:xx UTC)
# v26.7.4, build 247 (2026-07-xx xx:xx UTC)

View File

@ -62,3 +62,13 @@ In order to create a release:
8. Once the PR is merged, a member of the maintainers team will create & push an appropriate git tag in the format `vX.Y.Z`.
9. The GitHub Workflow will then build the release and upload it to the [release page](https://github.com/MindWorkAI/AI-Studio/releases/latest).
10. Building the release including virus scanning takes some time. Please be patient.
### Rebuild the current pre-release
If a pre-release must be rebuilt without changing its version, open a terminal in `/app/Build` and run:
```bash
dotnet run rebuild-release
```
The command keeps the current version, increments the build number, refreshes the release time and related changelog metadata, reserves the following build number for the next changelog, and performs the same two builds as the regular `release` command. Use `--offline` to skip downloads and rely on locally available build dependencies.

View File

@ -1,12 +1,12 @@
26.7.3
2026-07-15 19:10:35 UTC
245
2026-07-19 14:01:48 UTC
246
9.0.119 (commit 32cc3bdf5e)
9.0.18 (commit d839c41c85)
1.97.0 (commit 2d8144b78)
1.97.1 (commit 8bab26f4f)
8.15.0
2.11.5
d960e49e79d, release
a97dc7d7ccd, release
osx-arm64
148.0.7763.0
0.7.2