mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-10 12:53:47 +00:00
Prepare release v26.7.3 (#871)
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
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:
1 parent
58f87277ef
commit
f666760415
6 files changed
+301
-17
No files matched your search
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using SharedTools;
|
||||
@@ -40,7 +41,40 @@ 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,17 +158,22 @@ 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(
|
||||
@@ -357,6 +396,205 @@ public sealed partial class UpdateMetadataCommands
|
||||
await File.WriteAllTextAsync(changelogCodePath, changelogCode, Environment.UTF8_NO_BOM);
|
||||
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()
|
||||
{
|
||||
@@ -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();
|
||||
}
|
||||
Reference in new issue
Block a user