mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
feat.PLONDS系统会不断地改进
This commit is contained in:
17
.github/workflows/plonds-comparator.yml
vendored
17
.github/workflows/plonds-comparator.yml
vendored
@@ -28,7 +28,7 @@ on:
|
|||||||
- stable
|
- stable
|
||||||
- preview
|
- preview
|
||||||
compare_method:
|
compare_method:
|
||||||
description: '比较方法'
|
description: 'Compare method'
|
||||||
required: false
|
required: false
|
||||||
type: choice
|
type: choice
|
||||||
default: file-compare
|
default: file-compare
|
||||||
@@ -36,7 +36,7 @@ on:
|
|||||||
- file-compare
|
- file-compare
|
||||||
- commit-analyze
|
- commit-analyze
|
||||||
hash_algorithm:
|
hash_algorithm:
|
||||||
description: '哈希算法(仅 file-compare 模式)'
|
description: 'Hash algorithm (file-compare only)'
|
||||||
required: false
|
required: false
|
||||||
type: choice
|
type: choice
|
||||||
default: sha256
|
default: sha256
|
||||||
@@ -157,8 +157,8 @@ jobs:
|
|||||||
mv plonds-input/files-windows-x64.zip plonds-input/current-files-windows-x64.zip
|
mv plonds-input/files-windows-x64.zip plonds-input/current-files-windows-x64.zip
|
||||||
|
|
||||||
if [[ -n "$BASELINE_TAG" ]]; then
|
if [[ -n "$BASELINE_TAG" ]]; then
|
||||||
gh release download "$BASELINE_TAG" -p "files-windows-x64.zip" -D /tmp/baseline-dl
|
gh release download "$BASELINE_TAG" -p "files-windows-x64.zip" -D plonds-input
|
||||||
mv /tmp/baseline-dl/files-windows-x64.zip plonds-input/baseline-files-windows-x64.zip
|
mv plonds-input/files-windows-x64.zip plonds-input/baseline-files-windows-x64.zip
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Run build-delta (file-compare)
|
- name: Run build-delta (file-compare)
|
||||||
@@ -205,14 +205,13 @@ jobs:
|
|||||||
'--current-zip' "$PWD/plonds-input/current-files-windows-x64.zip"
|
'--current-zip' "$PWD/plonds-input/current-files-windows-x64.zip"
|
||||||
'--output-dir' "$PWD/plonds-output"
|
'--output-dir' "$PWD/plonds-output"
|
||||||
'--channel' "$RELEASE_CHANNEL"
|
'--channel' "$RELEASE_CHANNEL"
|
||||||
'--baseline-tag' "${BASELINE_TAG:-v0.0.0}"
|
'--baseline-tag' "${BASELINE_TAG:-$RELEASE_TAG}"
|
||||||
'--current-tag' "$RELEASE_TAG"
|
'--current-tag' "$RELEASE_TAG"
|
||||||
'--hash-algorithm' "$HASH_ALGORITHM"
|
'--hash-algorithm' "$HASH_ALGORITHM"
|
||||||
)
|
)
|
||||||
|
|
||||||
if [[ -n "$BASELINE_TAG" ]]; then
|
if [[ -n "$BASELINE_TAG" ]]; then
|
||||||
ARGS+=(
|
ARGS+=(
|
||||||
'--baseline-version' "$BASELINE_VERSION"
|
|
||||||
'--fallback-zip' "$PWD/plonds-input/baseline-files-windows-x64.zip"
|
'--fallback-zip' "$PWD/plonds-input/baseline-files-windows-x64.zip"
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
@@ -246,12 +245,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir -p plonds-run-metadata
|
mkdir -p plonds-run-metadata
|
||||||
printf '%s' "$RELEASE_TAG" > plonds-run-metadata/tag.txt
|
printf '%s' "$RELEASE_TAG" > plonds-run-metadata/tag.txt
|
||||||
printf '%s' "$COMPARE_METHOD" >> plonds-run-metadata/tag.txt
|
printf '%s' "$COMPARE_METHOD" > plonds-run-metadata/compare-method.txt
|
||||||
|
|
||||||
- name: Upload run metadata artifact
|
- name: Upload run metadata artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: plonds-run-metadata
|
name: plonds-run-metadata
|
||||||
path: plonds-run-metadata/tag.txt
|
path: |
|
||||||
|
plonds-run-metadata/tag.txt
|
||||||
|
plonds-run-metadata/compare-method.txt
|
||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
namespace Plonds.Core.Publishing;
|
|
||||||
|
|
||||||
public static class PlondsCommitAnalyzer
|
|
||||||
{
|
|
||||||
private static readonly string[] SourceDirectories =
|
|
||||||
[
|
|
||||||
"LanMountainDesktop/",
|
|
||||||
"LanMountainDesktop.Launcher/",
|
|
||||||
"LanMountainDesktop.Shared.Contracts/",
|
|
||||||
"LanMountainDesktop.PluginSdk/",
|
|
||||||
"LanMountainDesktop.Appearance/",
|
|
||||||
"LanMountainDesktop.Settings.Core/",
|
|
||||||
"LanMountainDesktop.ComponentSystem/"
|
|
||||||
];
|
|
||||||
|
|
||||||
private static readonly (string Prefix, string[] Artifacts)[] SourceToArtifactMappings =
|
|
||||||
[
|
|
||||||
("LanMountainDesktop/", ["LanMountainDesktop.dll", "LanMountainDesktop.exe"]),
|
|
||||||
("LanMountainDesktop.Launcher/", ["LanMountainDesktop.Launcher.exe", "LanMountainDesktop.Launcher.dll"]),
|
|
||||||
("LanMountainDesktop.Shared.Contracts/", ["LanMountainDesktop.Shared.Contracts.dll"]),
|
|
||||||
("LanMountainDesktop.PluginSdk/", ["LanMountainDesktop.PluginSdk.dll"]),
|
|
||||||
("LanMountainDesktop.Appearance/", ["LanMountainDesktop.Appearance.dll"]),
|
|
||||||
("LanMountainDesktop.Settings.Core/", ["LanMountainDesktop.Settings.Core.dll"]),
|
|
||||||
("LanMountainDesktop.ComponentSystem/", ["LanMountainDesktop.ComponentSystem.dll"])
|
|
||||||
];
|
|
||||||
|
|
||||||
private static readonly string[] SourceCodeExtensions =
|
|
||||||
[
|
|
||||||
".cs", ".axaml", ".xaml", ".csproj"
|
|
||||||
];
|
|
||||||
|
|
||||||
public static HashSet<string> GetChangedSourceFiles(string baselineTag, string currentTag)
|
|
||||||
{
|
|
||||||
var changedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var start = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
|
||||||
{
|
|
||||||
FileName = "git",
|
|
||||||
Arguments = $"log --name-only --pretty=format: {baselineTag}..{currentTag}",
|
|
||||||
RedirectStandardOutput = true,
|
|
||||||
UseShellExecute = false,
|
|
||||||
CreateNoWindow = true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (start is null)
|
|
||||||
{
|
|
||||||
return changedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
var output = start.StandardOutput.ReadToEnd();
|
|
||||||
start.WaitForExit();
|
|
||||||
|
|
||||||
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
|
||||||
{
|
|
||||||
var trimmed = line.Trim().Replace('\\', '/');
|
|
||||||
if (string.IsNullOrWhiteSpace(trimmed))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsSourceDirectoryFile(trimmed))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
changedFiles.Add(trimmed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return changedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HashSet<string> MapSourceFilesToArtifacts(IReadOnlySet<string> sourceFiles)
|
|
||||||
{
|
|
||||||
var artifacts = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
var hasUnmappedChanges = false;
|
|
||||||
|
|
||||||
foreach (var sourceFile in sourceFiles)
|
|
||||||
{
|
|
||||||
var normalized = sourceFile.Replace('\\', '/');
|
|
||||||
var mapped = false;
|
|
||||||
|
|
||||||
foreach (var (prefix, artifactList) in SourceToArtifactMappings)
|
|
||||||
{
|
|
||||||
if (!normalized.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsSourceCodeFile(normalized) && !IsConfigFile(normalized))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var artifact in artifactList)
|
|
||||||
{
|
|
||||||
artifacts.Add(artifact);
|
|
||||||
}
|
|
||||||
|
|
||||||
mapped = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mapped && IsConfigFile(normalized))
|
|
||||||
{
|
|
||||||
var artifactPath = MapConfigToArtifact(normalized);
|
|
||||||
if (artifactPath is not null)
|
|
||||||
{
|
|
||||||
artifacts.Add(artifactPath);
|
|
||||||
mapped = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!mapped)
|
|
||||||
{
|
|
||||||
hasUnmappedChanges = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasUnmappedChanges)
|
|
||||||
{
|
|
||||||
foreach (var (_, artifactList) in SourceToArtifactMappings)
|
|
||||||
{
|
|
||||||
foreach (var artifact in artifactList)
|
|
||||||
{
|
|
||||||
artifacts.Add(artifact);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return artifacts;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsSourceDirectoryFile(string path)
|
|
||||||
{
|
|
||||||
var normalized = path.Replace('\\', '/');
|
|
||||||
return SourceDirectories.Any(d => normalized.StartsWith(d, StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsSourceCodeFile(string path)
|
|
||||||
{
|
|
||||||
var ext = Path.GetExtension(path);
|
|
||||||
return SourceCodeExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsConfigFile(string path)
|
|
||||||
{
|
|
||||||
var ext = Path.GetExtension(path);
|
|
||||||
return string.Equals(ext, ".json", StringComparison.OrdinalIgnoreCase)
|
|
||||||
|| string.Equals(ext, ".xml", StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string? MapConfigToArtifact(string sourcePath)
|
|
||||||
{
|
|
||||||
var fileName = Path.GetFileName(sourcePath);
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,7 @@ public sealed record PlondsCommitDeltaBuildOptions(
|
|||||||
string Channel,
|
string Channel,
|
||||||
string BaselineTag,
|
string BaselineTag,
|
||||||
string CurrentTag,
|
string CurrentTag,
|
||||||
|
string HashAlgorithm = "sha256",
|
||||||
|
string? SourceDirs = null,
|
||||||
string? FallbackBaselineZip = null,
|
string? FallbackBaselineZip = null,
|
||||||
string? BaselineVersion = null,
|
string LauncherRelativePath = "LanMountainDesktop.Launcher.exe");
|
||||||
string LauncherRelativePath = "LanMountainDesktop.Launcher.exe",
|
|
||||||
string HashAlgorithm = "sha256");
|
|
||||||
|
|||||||
@@ -13,11 +13,35 @@ public sealed class PlondsCommitDeltaBuilder
|
|||||||
WriteIndented = true
|
WriteIndented = true
|
||||||
};
|
};
|
||||||
|
|
||||||
public PlondsCommitDeltaBuildResult Build(PlondsCommitDeltaBuildOptions options)
|
private static readonly Dictionary<string, string[]> SourceToArtifactMap = new(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
["LanMountainDesktop"] = ["LanMountainDesktop.dll", "LanMountainDesktop.exe"],
|
||||||
|
["LanMountainDesktop.Launcher"] = ["LanMountainDesktop.Launcher.exe"],
|
||||||
|
["LanMountainDesktop.Shared.Contracts"] = ["LanMountainDesktop.Shared.Contracts.dll"],
|
||||||
|
["LanMountainDesktop.PluginSdk"] = ["LanMountainDesktop.PluginSdk.dll"],
|
||||||
|
["LanMountainDesktop.Appearance"] = ["LanMountainDesktop.Appearance.dll"],
|
||||||
|
["LanMountainDesktop.Settings.Core"] = ["LanMountainDesktop.Settings.Core.dll"],
|
||||||
|
["LanMountainDesktop.ComponentSystem"] = ["LanMountainDesktop.ComponentSystem.dll"]
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly string[] FallbackAllArtifacts =
|
||||||
|
[
|
||||||
|
"LanMountainDesktop.dll",
|
||||||
|
"LanMountainDesktop.exe",
|
||||||
|
"LanMountainDesktop.Launcher.exe",
|
||||||
|
"LanMountainDesktop.Shared.Contracts.dll",
|
||||||
|
"LanMountainDesktop.PluginSdk.dll",
|
||||||
|
"LanMountainDesktop.Appearance.dll",
|
||||||
|
"LanMountainDesktop.Settings.Core.dll",
|
||||||
|
"LanMountainDesktop.ComponentSystem.dll"
|
||||||
|
];
|
||||||
|
|
||||||
|
public PlondsDeltaBuildResult Build(PlondsCommitDeltaBuildOptions options)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(options);
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
|
||||||
var hashAlgorithm = PlondsDeltaBuilder.ValidateHashAlgorithmInternal(options.HashAlgorithm);
|
var hashAlgorithm = ValidateHashAlgorithm(options.HashAlgorithm);
|
||||||
|
var sourceDirs = ParseSourceDirs(options.SourceDirs);
|
||||||
|
|
||||||
var currentPayloadZip = Path.GetFullPath(options.CurrentPayloadZip);
|
var currentPayloadZip = Path.GetFullPath(options.CurrentPayloadZip);
|
||||||
if (!File.Exists(currentPayloadZip))
|
if (!File.Exists(currentPayloadZip))
|
||||||
@@ -32,50 +56,62 @@ public sealed class PlondsCommitDeltaBuilder
|
|||||||
Directory.CreateDirectory(outputRoot);
|
Directory.CreateDirectory(outputRoot);
|
||||||
PayloadUtilities.ExtractZip(currentPayloadZip, currentExtractRoot);
|
PayloadUtilities.ExtractZip(currentPayloadZip, currentExtractRoot);
|
||||||
|
|
||||||
var changedSourceFiles = PlondsCommitAnalyzer.GetChangedSourceFiles(options.BaselineTag, options.CurrentTag);
|
var changedSourceFiles = GetChangedSourceFiles(options.BaselineTag, options.CurrentTag, sourceDirs);
|
||||||
|
|
||||||
if (changedSourceFiles.Count == 0)
|
if (changedSourceFiles.Count == 0)
|
||||||
{
|
{
|
||||||
return FallbackToFileCompare(options, currentPayloadZip, outputRoot, workRoot, hashAlgorithm);
|
Console.WriteLine("No source code changes detected between tags. Falling back to file-compare method.");
|
||||||
|
return FallbackToFileCompare(options, currentExtractRoot, outputRoot, hashAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
var mappedArtifacts = PlondsCommitAnalyzer.MapSourceFilesToArtifacts(changedSourceFiles);
|
Console.WriteLine($"Detected {changedSourceFiles.Count} changed source file(s) between {options.BaselineTag} and {options.CurrentTag}.");
|
||||||
|
foreach (var file in changedSourceFiles.Take(20))
|
||||||
|
{
|
||||||
|
Console.WriteLine($" {file}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changedSourceFiles.Count > 20)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" ... and {changedSourceFiles.Count - 20} more");
|
||||||
|
}
|
||||||
|
|
||||||
|
var artifactFiles = MapSourceToArtifacts(changedSourceFiles, sourceDirs);
|
||||||
var currentManifest = PayloadUtilities.ScanDirectory(currentExtractRoot);
|
var currentManifest = PayloadUtilities.ScanDirectory(currentExtractRoot);
|
||||||
|
|
||||||
var filesMap = new Dictionary<string, PlondsFileEntry>(StringComparer.OrdinalIgnoreCase);
|
var filesMap = new Dictionary<string, PlondsFileEntry>(StringComparer.OrdinalIgnoreCase);
|
||||||
var changedFilesMap = new Dictionary<string, PlondsChangedFileEntry>(StringComparer.OrdinalIgnoreCase);
|
var changedFilesMap = new Dictionary<string, PlondsChangedFileEntry>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (var artifact in mappedArtifacts.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
foreach (var artifactFile in artifactFiles)
|
||||||
{
|
{
|
||||||
if (!currentManifest.TryGetValue(artifact, out var fingerprint))
|
var normalizedPath = artifactFile.Replace('\\', '/');
|
||||||
|
if (!currentManifest.TryGetValue(normalizedPath, out var fingerprint))
|
||||||
{
|
{
|
||||||
|
Console.WriteLine($" Artifact not found in current zip: {normalizedPath}, skipping.");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hash = GetHash(fingerprint, hashAlgorithm);
|
var fileHash = PlondsDeltaBuilder.ComputeHash(fingerprint.FullPath, hashAlgorithm);
|
||||||
var action = PlondsConstants.ActionReplace;
|
var action = PlondsConstants.ActionReplace;
|
||||||
|
|
||||||
filesMap[artifact] = new PlondsFileEntry(action, hash, fingerprint.Size, hashAlgorithm);
|
filesMap[normalizedPath] = new PlondsFileEntry(action, fileHash, fingerprint.Size, hashAlgorithm);
|
||||||
changedFilesMap[artifact] = new PlondsChangedFileEntry(artifact, hash, fingerprint.Size, hashAlgorithm);
|
changedFilesMap[normalizedPath] = new PlondsChangedFileEntry(normalizedPath, fileHash, fingerprint.Size, hashAlgorithm);
|
||||||
}
|
}
|
||||||
|
|
||||||
var changedZipPath = CreateChangedZipFromArtifacts(currentExtractRoot, mappedArtifacts, outputRoot, options.Platform);
|
var changedZipPath = CreateChangedZipFromList(currentExtractRoot, artifactFiles, outputRoot, options.Platform);
|
||||||
|
|
||||||
var requiresCleanInstall = mappedArtifacts.Contains(options.LauncherRelativePath, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var changedZipMd5 = ComputeMd5Hex(changedZipPath);
|
var changedZipMd5 = ComputeMd5Hex(changedZipPath);
|
||||||
|
|
||||||
|
var launcherInChanges = artifactFiles.Any(f =>
|
||||||
|
string.Equals(Path.GetFileName(f), "LanMountainDesktop.Launcher.exe", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
var manifest = new PlondsManifest(
|
var manifest = new PlondsManifest(
|
||||||
FormatVersion: PlondsConstants.FormatVersion,
|
FormatVersion: PlondsConstants.FormatVersion,
|
||||||
CurrentVersion: options.CurrentVersion,
|
CurrentVersion: options.CurrentVersion,
|
||||||
PreviousVersion: options.BaselineVersion ?? options.BaselineTag.TrimStart('v'),
|
PreviousVersion: options.BaselineTag.TrimStart('v'),
|
||||||
IsFullUpdate: false,
|
IsFullUpdate: false,
|
||||||
RequiresCleanInstall: requiresCleanInstall,
|
RequiresCleanInstall: launcherInChanges,
|
||||||
Channel: options.Channel,
|
Channel: options.Channel,
|
||||||
Platform: options.Platform,
|
Platform: options.Platform,
|
||||||
UpdatedAt: DateTimeOffset.UtcNow,
|
UpdatedAt: DateTimeOffset.UtcNow,
|
||||||
CompareMethod: PlondsConstants.CompareMethodCommitAnalyze,
|
|
||||||
HashAlgorithm: hashAlgorithm,
|
|
||||||
FilesMap: filesMap,
|
FilesMap: filesMap,
|
||||||
ChangedFilesMap: changedFilesMap,
|
ChangedFilesMap: changedFilesMap,
|
||||||
Checksums: new Dictionary<string, string>
|
Checksums: new Dictionary<string, string>
|
||||||
@@ -87,24 +123,125 @@ public sealed class PlondsCommitDeltaBuilder
|
|||||||
var manifestJson = JsonSerializer.Serialize(manifest, JsonOptions);
|
var manifestJson = JsonSerializer.Serialize(manifest, JsonOptions);
|
||||||
File.WriteAllText(manifestPath, manifestJson);
|
File.WriteAllText(manifestPath, manifestJson);
|
||||||
|
|
||||||
return new PlondsCommitDeltaBuildResult(
|
return new PlondsDeltaBuildResult(
|
||||||
Platform: options.Platform,
|
Platform: options.Platform,
|
||||||
ChangedZipPath: changedZipPath,
|
ChangedZipPath: changedZipPath,
|
||||||
ManifestPath: manifestPath,
|
ManifestPath: manifestPath,
|
||||||
IsFullUpdate: false,
|
IsFullUpdate: false,
|
||||||
RequiresCleanInstall: requiresCleanInstall,
|
RequiresCleanInstall: launcherInChanges,
|
||||||
FellBackToFileCompare: false,
|
|
||||||
CurrentVersion: options.CurrentVersion,
|
CurrentVersion: options.CurrentVersion,
|
||||||
BaselineVersion: options.BaselineVersion,
|
BaselineVersion: options.BaselineTag.TrimStart('v'));
|
||||||
ChangedSourceFiles: changedSourceFiles.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray(),
|
|
||||||
MappedArtifactFiles: mappedArtifacts.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlondsCommitDeltaBuildResult FallbackToFileCompare(
|
private static List<string> GetChangedSourceFiles(string baselineTag, string currentTag, string[] sourceDirs)
|
||||||
|
{
|
||||||
|
var changedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var normalizedBaseline = baselineTag.StartsWith("v") ? baselineTag : $"v{baselineTag}";
|
||||||
|
var normalizedCurrent = currentTag.StartsWith("v") ? currentTag : $"v{currentTag}";
|
||||||
|
|
||||||
|
var psi = new System.Diagnostics.ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "git",
|
||||||
|
Arguments = $"diff --name-only {normalizedBaseline}..{normalizedCurrent}",
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using var process = System.Diagnostics.Process.Start(psi)
|
||||||
|
?? throw new InvalidOperationException("Failed to start git process.");
|
||||||
|
|
||||||
|
var output = process.StandardOutput.ReadToEnd();
|
||||||
|
process.WaitForExit();
|
||||||
|
|
||||||
|
if (process.ExitCode != 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"git diff failed with exit code {process.ExitCode}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var line in output.Split('\n', StringSplitOptions.RemoveEmptyEntries))
|
||||||
|
{
|
||||||
|
var trimmed = line.Trim();
|
||||||
|
if (string.IsNullOrWhiteSpace(trimmed))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSourceFile = sourceDirs.Any(dir =>
|
||||||
|
trimmed.StartsWith(dir + "/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
trimmed.StartsWith(dir + "\\", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (isSourceFile)
|
||||||
|
{
|
||||||
|
changedFiles.Add(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return changedFiles.OrderBy(x => x, StringComparer.OrdinalIgnoreCase).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<string> MapSourceToArtifacts(IReadOnlyList<string> changedSourceFiles, string[] sourceDirs)
|
||||||
|
{
|
||||||
|
var artifacts = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var hasUnmappedChanges = false;
|
||||||
|
|
||||||
|
foreach (var sourceFile in changedSourceFiles)
|
||||||
|
{
|
||||||
|
var mapped = false;
|
||||||
|
|
||||||
|
foreach (var dir in sourceDirs)
|
||||||
|
{
|
||||||
|
if (!sourceFile.StartsWith(dir + "/", StringComparison.OrdinalIgnoreCase) &&
|
||||||
|
!sourceFile.StartsWith(dir + "\\", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SourceToArtifactMap.TryGetValue(dir, out var artifactList))
|
||||||
|
{
|
||||||
|
foreach (var artifact in artifactList)
|
||||||
|
{
|
||||||
|
artifacts.Add(artifact);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mapped)
|
||||||
|
{
|
||||||
|
var extension = Path.GetExtension(sourceFile).ToLowerInvariant();
|
||||||
|
if (extension is ".json" or ".xml" or ".config")
|
||||||
|
{
|
||||||
|
var fileName = Path.GetFileName(sourceFile);
|
||||||
|
artifacts.Add(fileName);
|
||||||
|
mapped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mapped)
|
||||||
|
{
|
||||||
|
hasUnmappedChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasUnmappedChanges)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Unmapped source changes detected. Including all core artifacts as a conservative fallback.");
|
||||||
|
foreach (var artifact in FallbackAllArtifacts)
|
||||||
|
{
|
||||||
|
artifacts.Add(artifact);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PlondsDeltaBuildResult FallbackToFileCompare(
|
||||||
PlondsCommitDeltaBuildOptions options,
|
PlondsCommitDeltaBuildOptions options,
|
||||||
string currentPayloadZip,
|
string currentExtractRoot,
|
||||||
string outputRoot,
|
string outputRoot,
|
||||||
string workRoot,
|
|
||||||
string hashAlgorithm)
|
string hashAlgorithm)
|
||||||
{
|
{
|
||||||
var fallbackZip = string.IsNullOrWhiteSpace(options.FallbackBaselineZip)
|
var fallbackZip = string.IsNullOrWhiteSpace(options.FallbackBaselineZip)
|
||||||
@@ -113,97 +250,35 @@ public sealed class PlondsCommitDeltaBuilder
|
|||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(fallbackZip) || !File.Exists(fallbackZip))
|
if (string.IsNullOrWhiteSpace(fallbackZip) || !File.Exists(fallbackZip))
|
||||||
{
|
{
|
||||||
var currentExtractRoot = Path.Combine(workRoot, "current");
|
Console.WriteLine("No fallback baseline zip available. Generating full update.");
|
||||||
PayloadUtilities.ExtractZip(currentPayloadZip, currentExtractRoot);
|
var fullBuilder = new PlondsDeltaBuilder();
|
||||||
var currentManifest = PayloadUtilities.ScanDirectory(currentExtractRoot);
|
return fullBuilder.Build(new PlondsDeltaBuildOptions(
|
||||||
|
Platform: options.Platform,
|
||||||
var filesMap = new Dictionary<string, PlondsFileEntry>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
var changedFilesMap = new Dictionary<string, PlondsChangedFileEntry>(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
foreach (var path in currentManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
var fp = currentManifest[path];
|
|
||||||
var hash = GetHash(fp, hashAlgorithm);
|
|
||||||
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionAdd, hash, fp.Size, hashAlgorithm);
|
|
||||||
changedFilesMap[path] = new PlondsChangedFileEntry(path, hash, fp.Size, hashAlgorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
var changedZipPath = CreateChangedZipFromArtifacts(currentExtractRoot, filesMap.Keys.ToHashSet(), outputRoot, options.Platform);
|
|
||||||
var changedZipMd5 = ComputeMd5Hex(changedZipPath);
|
|
||||||
|
|
||||||
var manifest = new PlondsManifest(
|
|
||||||
FormatVersion: PlondsConstants.FormatVersion,
|
|
||||||
CurrentVersion: options.CurrentVersion,
|
CurrentVersion: options.CurrentVersion,
|
||||||
PreviousVersion: "0.0.0",
|
CurrentPayloadZip: options.CurrentPayloadZip,
|
||||||
IsFullUpdate: true,
|
OutputRoot: outputRoot,
|
||||||
RequiresCleanInstall: false,
|
|
||||||
Channel: options.Channel,
|
Channel: options.Channel,
|
||||||
Platform: options.Platform,
|
|
||||||
UpdatedAt: DateTimeOffset.UtcNow,
|
|
||||||
CompareMethod: PlondsConstants.CompareMethodCommitAnalyze,
|
|
||||||
HashAlgorithm: hashAlgorithm,
|
HashAlgorithm: hashAlgorithm,
|
||||||
FilesMap: filesMap,
|
LauncherRelativePath: options.LauncherRelativePath));
|
||||||
ChangedFilesMap: changedFilesMap,
|
|
||||||
Checksums: new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
["changed.zip"] = $"md5:{changedZipMd5}"
|
|
||||||
});
|
|
||||||
|
|
||||||
var manifestPath = Path.Combine(outputRoot, "PLONDS.json");
|
|
||||||
var manifestJson = JsonSerializer.Serialize(manifest, JsonOptions);
|
|
||||||
File.WriteAllText(manifestPath, manifestJson);
|
|
||||||
|
|
||||||
return new PlondsCommitDeltaBuildResult(
|
|
||||||
Platform: options.Platform,
|
|
||||||
ChangedZipPath: changedZipPath,
|
|
||||||
ManifestPath: manifestPath,
|
|
||||||
IsFullUpdate: true,
|
|
||||||
RequiresCleanInstall: false,
|
|
||||||
FellBackToFileCompare: true,
|
|
||||||
CurrentVersion: options.CurrentVersion,
|
|
||||||
BaselineVersion: options.BaselineVersion,
|
|
||||||
ChangedSourceFiles: [],
|
|
||||||
MappedArtifactFiles: []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Falling back to file-compare using baseline: {fallbackZip}");
|
||||||
var deltaBuilder = new PlondsDeltaBuilder();
|
var deltaBuilder = new PlondsDeltaBuilder();
|
||||||
var deltaResult = deltaBuilder.Build(new PlondsDeltaBuildOptions(
|
return deltaBuilder.Build(new PlondsDeltaBuildOptions(
|
||||||
Platform: options.Platform,
|
Platform: options.Platform,
|
||||||
CurrentVersion: options.CurrentVersion,
|
CurrentVersion: options.CurrentVersion,
|
||||||
CurrentPayloadZip: currentPayloadZip,
|
CurrentPayloadZip: options.CurrentPayloadZip,
|
||||||
OutputRoot: outputRoot,
|
OutputRoot: outputRoot,
|
||||||
Channel: options.Channel,
|
Channel: options.Channel,
|
||||||
BaselineVersion: options.BaselineVersion,
|
BaselineVersion: options.BaselineTag.TrimStart('v'),
|
||||||
BaselinePayloadZip: fallbackZip,
|
BaselinePayloadZip: fallbackZip,
|
||||||
LauncherRelativePath: options.LauncherRelativePath,
|
HashAlgorithm: hashAlgorithm,
|
||||||
HashAlgorithm: hashAlgorithm));
|
LauncherRelativePath: options.LauncherRelativePath));
|
||||||
|
|
||||||
return new PlondsCommitDeltaBuildResult(
|
|
||||||
Platform: deltaResult.Platform,
|
|
||||||
ChangedZipPath: deltaResult.ChangedZipPath,
|
|
||||||
ManifestPath: deltaResult.ManifestPath,
|
|
||||||
IsFullUpdate: deltaResult.IsFullUpdate,
|
|
||||||
RequiresCleanInstall: deltaResult.RequiresCleanInstall,
|
|
||||||
FellBackToFileCompare: true,
|
|
||||||
CurrentVersion: deltaResult.CurrentVersion,
|
|
||||||
BaselineVersion: deltaResult.BaselineVersion,
|
|
||||||
ChangedSourceFiles: [],
|
|
||||||
MappedArtifactFiles: []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetHash(PayloadUtilities.FileFingerprint fingerprint, string hashAlgorithm)
|
private static string CreateChangedZipFromList(
|
||||||
{
|
|
||||||
if (hashAlgorithm == PlondsConstants.HashAlgorithmMd5)
|
|
||||||
{
|
|
||||||
return ComputeMd5Hex(fingerprint.FullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fingerprint.Sha256;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string CreateChangedZipFromArtifacts(
|
|
||||||
string currentExtractRoot,
|
string currentExtractRoot,
|
||||||
IReadOnlySet<string> artifacts,
|
IEnumerable<string> artifactFiles,
|
||||||
string outputRoot,
|
string outputRoot,
|
||||||
string platform)
|
string platform)
|
||||||
{
|
{
|
||||||
@@ -211,15 +286,16 @@ public sealed class PlondsCommitDeltaBuilder
|
|||||||
var stagingRoot = Path.Combine(outputRoot, "work", platform, "staging");
|
var stagingRoot = Path.Combine(outputRoot, "work", platform, "staging");
|
||||||
PayloadUtilities.EnsureCleanDirectory(stagingRoot);
|
PayloadUtilities.EnsureCleanDirectory(stagingRoot);
|
||||||
|
|
||||||
foreach (var artifact in artifacts)
|
foreach (var artifactFile in artifactFiles)
|
||||||
{
|
{
|
||||||
var sourcePath = Path.Combine(currentExtractRoot, artifact);
|
var normalizedPath = artifactFile.Replace('\\', '/');
|
||||||
|
var sourcePath = Path.Combine(currentExtractRoot, normalizedPath);
|
||||||
if (!File.Exists(sourcePath))
|
if (!File.Exists(sourcePath))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var destPath = Path.Combine(stagingRoot, artifact);
|
var destPath = Path.Combine(stagingRoot, normalizedPath);
|
||||||
var destDir = Path.GetDirectoryName(destPath);
|
var destDir = Path.GetDirectoryName(destPath);
|
||||||
if (!string.IsNullOrWhiteSpace(destDir))
|
if (!string.IsNullOrWhiteSpace(destDir))
|
||||||
{
|
{
|
||||||
@@ -233,6 +309,27 @@ public sealed class PlondsCommitDeltaBuilder
|
|||||||
return changedZipPath;
|
return changedZipPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ValidateHashAlgorithm(string algorithm)
|
||||||
|
{
|
||||||
|
var normalized = algorithm.Trim().ToLowerInvariant();
|
||||||
|
if (normalized is not (PlondsConstants.HashAlgorithmSha256 or PlondsConstants.HashAlgorithmMd5))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Unsupported hash algorithm: {algorithm}. Supported: sha256, md5");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] ParseSourceDirs(string? sourceDirs)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(sourceDirs))
|
||||||
|
{
|
||||||
|
return PlondsConstants.DefaultSourceDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sourceDirs.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||||
|
}
|
||||||
|
|
||||||
private static string ComputeMd5Hex(string filePath)
|
private static string ComputeMd5Hex(string filePath)
|
||||||
{
|
{
|
||||||
using var stream = File.OpenRead(filePath);
|
using var stream = File.OpenRead(filePath);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public sealed class PlondsDeltaBuilder
|
|||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(options);
|
ArgumentNullException.ThrowIfNull(options);
|
||||||
|
|
||||||
var hashAlgorithm = ValidateHashAlgorithmInternal(options.HashAlgorithm);
|
var hashAlgorithm = ValidateHashAlgorithm(options.HashAlgorithm);
|
||||||
|
|
||||||
var currentPayloadZip = Path.GetFullPath(options.CurrentPayloadZip);
|
var currentPayloadZip = Path.GetFullPath(options.CurrentPayloadZip);
|
||||||
if (!File.Exists(currentPayloadZip))
|
if (!File.Exists(currentPayloadZip))
|
||||||
@@ -71,8 +71,6 @@ public sealed class PlondsDeltaBuilder
|
|||||||
Channel: options.Channel,
|
Channel: options.Channel,
|
||||||
Platform: options.Platform,
|
Platform: options.Platform,
|
||||||
UpdatedAt: DateTimeOffset.UtcNow,
|
UpdatedAt: DateTimeOffset.UtcNow,
|
||||||
CompareMethod: PlondsConstants.CompareMethodFileCompare,
|
|
||||||
HashAlgorithm: hashAlgorithm,
|
|
||||||
FilesMap: filesMap,
|
FilesMap: filesMap,
|
||||||
ChangedFilesMap: changedFilesMap,
|
ChangedFilesMap: changedFilesMap,
|
||||||
Checksums: new Dictionary<string, string>
|
Checksums: new Dictionary<string, string>
|
||||||
@@ -94,7 +92,7 @@ public sealed class PlondsDeltaBuilder
|
|||||||
BaselineVersion: options.BaselineVersion);
|
BaselineVersion: options.BaselineVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static string ValidateHashAlgorithmInternal(string algorithm)
|
private static string ValidateHashAlgorithm(string algorithm)
|
||||||
{
|
{
|
||||||
var normalized = algorithm.Trim().ToLowerInvariant();
|
var normalized = algorithm.Trim().ToLowerInvariant();
|
||||||
if (normalized is not (PlondsConstants.HashAlgorithmSha256 or PlondsConstants.HashAlgorithmMd5))
|
if (normalized is not (PlondsConstants.HashAlgorithmSha256 or PlondsConstants.HashAlgorithmMd5))
|
||||||
@@ -115,22 +113,22 @@ public sealed class PlondsDeltaBuilder
|
|||||||
foreach (var path in currentManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
foreach (var path in currentManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var current = currentManifest[path];
|
var current = currentManifest[path];
|
||||||
var currentHash = GetHash(current, hashAlgorithm);
|
var currentHash = ComputeHash(current.FullPath, hashAlgorithm);
|
||||||
|
|
||||||
if (previousManifest.TryGetValue(path, out var previous))
|
if (previousManifest.TryGetValue(path, out var previous))
|
||||||
{
|
{
|
||||||
var previousHash = GetHash(previous, hashAlgorithm);
|
var previousHash = ComputeHash(previous.FullPath, hashAlgorithm);
|
||||||
if (string.Equals(currentHash, previousHash, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(currentHash, previousHash, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionReuse, currentHash, current.Size, hashAlgorithm);
|
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionReuse, currentHash, current.Size, hashAlgorithm);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionReplace, currentHash, current.Size, hashAlgorithm);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var action = previousManifest.ContainsKey(path)
|
filesMap[path] = new PlondsFileEntry(PlondsConstants.ActionAdd, currentHash, current.Size, hashAlgorithm);
|
||||||
? PlondsConstants.ActionReplace
|
|
||||||
: PlondsConstants.ActionAdd;
|
|
||||||
filesMap[path] = new PlondsFileEntry(action, currentHash, current.Size, hashAlgorithm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var path in previousManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
foreach (var path in previousManifest.Keys.OrderBy(x => x, StringComparer.OrdinalIgnoreCase))
|
||||||
@@ -144,16 +142,6 @@ public sealed class PlondsDeltaBuilder
|
|||||||
return filesMap;
|
return filesMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetHash(PayloadUtilities.FileFingerprint fingerprint, string hashAlgorithm)
|
|
||||||
{
|
|
||||||
if (hashAlgorithm == PlondsConstants.HashAlgorithmMd5)
|
|
||||||
{
|
|
||||||
return ComputeMd5Hex(fingerprint.FullPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fingerprint.Sha256;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<string, PlondsChangedFileEntry> BuildChangedFilesMap(
|
private static Dictionary<string, PlondsChangedFileEntry> BuildChangedFilesMap(
|
||||||
IReadOnlyDictionary<string, PlondsFileEntry> filesMap,
|
IReadOnlyDictionary<string, PlondsFileEntry> filesMap,
|
||||||
string hashAlgorithm)
|
string hashAlgorithm)
|
||||||
@@ -228,6 +216,15 @@ public sealed class PlondsDeltaBuilder
|
|||||||
return !string.Equals(current.Sha256, previous.Sha256, StringComparison.OrdinalIgnoreCase);
|
return !string.Equals(current.Sha256, previous.Sha256, StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static string ComputeHash(string filePath, string hashAlgorithm)
|
||||||
|
{
|
||||||
|
using var stream = File.OpenRead(filePath);
|
||||||
|
var hash = hashAlgorithm == PlondsConstants.HashAlgorithmMd5
|
||||||
|
? MD5.HashData(stream)
|
||||||
|
: SHA256.HashData(stream);
|
||||||
|
return Convert.ToHexString(hash).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
private static string ComputeMd5Hex(string filePath)
|
private static string ComputeMd5Hex(string filePath)
|
||||||
{
|
{
|
||||||
using var stream = File.OpenRead(filePath);
|
using var stream = File.OpenRead(filePath);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Text.Json.Serialization;
|
|
||||||
|
|
||||||
namespace Plonds.Shared.Models;
|
namespace Plonds.Shared.Models;
|
||||||
|
|
||||||
public sealed record PlondsManifest(
|
public sealed record PlondsManifest(
|
||||||
@@ -11,9 +9,6 @@ public sealed record PlondsManifest(
|
|||||||
string Channel,
|
string Channel,
|
||||||
string Platform,
|
string Platform,
|
||||||
DateTimeOffset UpdatedAt,
|
DateTimeOffset UpdatedAt,
|
||||||
string CompareMethod,
|
|
||||||
[property: JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
|
||||||
string? HashAlgorithm,
|
|
||||||
IReadOnlyDictionary<string, PlondsFileEntry> FilesMap,
|
IReadOnlyDictionary<string, PlondsFileEntry> FilesMap,
|
||||||
IReadOnlyDictionary<string, PlondsChangedFileEntry> ChangedFilesMap,
|
IReadOnlyDictionary<string, PlondsChangedFileEntry> ChangedFilesMap,
|
||||||
IReadOnlyDictionary<string, string> Checksums);
|
IReadOnlyDictionary<string, string> Checksums);
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ public static class PlondsConstants
|
|||||||
public const string ActionReuse = "reuse";
|
public const string ActionReuse = "reuse";
|
||||||
public const string ActionDelete = "delete";
|
public const string ActionDelete = "delete";
|
||||||
|
|
||||||
public const string CompareMethodFileCompare = "file-compare";
|
|
||||||
public const string CompareMethodCommitAnalyze = "commit-analyze";
|
|
||||||
|
|
||||||
public const string HashAlgorithmSha256 = "sha256";
|
public const string HashAlgorithmSha256 = "sha256";
|
||||||
public const string HashAlgorithmMd5 = "md5";
|
public const string HashAlgorithmMd5 = "md5";
|
||||||
|
|
||||||
public const string DefaultLauncherRelativePath = "LanMountainDesktop.Launcher.exe";
|
public const string DefaultLauncherRelativePath = "LanMountainDesktop.Launcher.exe";
|
||||||
|
|
||||||
|
public const string CompareMethodFileCompare = "file-compare";
|
||||||
|
public const string CompareMethodCommitAnalyze = "commit-analyze";
|
||||||
|
|
||||||
public static readonly string[] SupportedActions =
|
public static readonly string[] SupportedActions =
|
||||||
[
|
[
|
||||||
ActionAdd,
|
ActionAdd,
|
||||||
@@ -33,9 +33,14 @@ public static class PlondsConstants
|
|||||||
HashAlgorithmMd5
|
HashAlgorithmMd5
|
||||||
];
|
];
|
||||||
|
|
||||||
public static readonly string[] SupportedCompareMethods =
|
public static readonly string[] DefaultSourceDirs =
|
||||||
[
|
[
|
||||||
CompareMethodFileCompare,
|
"LanMountainDesktop",
|
||||||
CompareMethodCommitAnalyze
|
"LanMountainDesktop.Launcher",
|
||||||
|
"LanMountainDesktop.Shared.Contracts",
|
||||||
|
"LanMountainDesktop.PluginSdk",
|
||||||
|
"LanMountainDesktop.Appearance",
|
||||||
|
"LanMountainDesktop.Settings.Core",
|
||||||
|
"LanMountainDesktop.ComponentSystem"
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,17 +73,14 @@ internal static class PlondsCli
|
|||||||
Channel: Require(options, "channel"),
|
Channel: Require(options, "channel"),
|
||||||
BaselineTag: Require(options, "baseline-tag"),
|
BaselineTag: Require(options, "baseline-tag"),
|
||||||
CurrentTag: Require(options, "current-tag"),
|
CurrentTag: Require(options, "current-tag"),
|
||||||
|
HashAlgorithm: Get(options, "hash-algorithm", "sha256") ?? "sha256",
|
||||||
|
SourceDirs: Get(options, "source-dirs"),
|
||||||
FallbackBaselineZip: Get(options, "fallback-zip"),
|
FallbackBaselineZip: Get(options, "fallback-zip"),
|
||||||
BaselineVersion: Get(options, "baseline-version"),
|
LauncherRelativePath: Get(options, "launcher-path", "LanMountainDesktop.Launcher.exe") ?? "LanMountainDesktop.Launcher.exe"));
|
||||||
LauncherRelativePath: Get(options, "launcher-path", "LanMountainDesktop.Launcher.exe") ?? "LanMountainDesktop.Launcher.exe",
|
|
||||||
HashAlgorithm: Get(options, "hash-algorithm", "sha256") ?? "sha256"));
|
|
||||||
|
|
||||||
Console.WriteLine($"Built PLONDS commit-delta for {result.Platform}:");
|
Console.WriteLine($"Built PLONDS commit-delta for {result.Platform}:");
|
||||||
Console.WriteLine($" IsFullUpdate: {result.IsFullUpdate}");
|
Console.WriteLine($" IsFullUpdate: {result.IsFullUpdate}");
|
||||||
Console.WriteLine($" RequiresCleanInstall: {result.RequiresCleanInstall}");
|
Console.WriteLine($" RequiresCleanInstall: {result.RequiresCleanInstall}");
|
||||||
Console.WriteLine($" FellBackToFileCompare: {result.FellBackToFileCompare}");
|
|
||||||
Console.WriteLine($" ChangedSourceFiles: {result.ChangedSourceFiles.Count}");
|
|
||||||
Console.WriteLine($" MappedArtifactFiles: {result.MappedArtifactFiles.Count}");
|
|
||||||
Console.WriteLine($" ChangedZip: {result.ChangedZipPath}");
|
Console.WriteLine($" ChangedZip: {result.ChangedZipPath}");
|
||||||
Console.WriteLine($" Manifest: {result.ManifestPath}");
|
Console.WriteLine($" Manifest: {result.ManifestPath}");
|
||||||
}
|
}
|
||||||
@@ -137,8 +134,34 @@ internal static class PlondsCli
|
|||||||
private static void PrintUsage()
|
private static void PrintUsage()
|
||||||
{
|
{
|
||||||
Console.WriteLine("PLONDS Tool");
|
Console.WriteLine("PLONDS Tool");
|
||||||
Console.WriteLine(" build-delta --platform <p> --current-version <v> --current-zip <file> --output-dir <dir> [--channel <ch>] [--baseline-version <v>] [--baseline-zip <file>] [--launcher-path <path>] [--hash-algorithm sha256|md5]");
|
Console.WriteLine();
|
||||||
Console.WriteLine(" build-delta-from-commits --platform <p> --current-version <v> --current-zip <file> --output-dir <dir> --channel <ch> --baseline-tag <tag> --current-tag <tag> [--fallback-zip <file>] [--baseline-version <v>] [--launcher-path <path>] [--hash-algorithm sha256|md5]");
|
Console.WriteLine("Commands:");
|
||||||
Console.WriteLine(" pack-payload --source-dir <dir> --output-zip <file>");
|
Console.WriteLine(" build-delta Build delta by comparing two payload zips");
|
||||||
|
Console.WriteLine(" --platform <platform> Platform identifier (e.g. windows-x64)");
|
||||||
|
Console.WriteLine(" --current-version <v> Current release version");
|
||||||
|
Console.WriteLine(" --current-zip <file> Current payload zip path");
|
||||||
|
Console.WriteLine(" --output-dir <dir> Output directory");
|
||||||
|
Console.WriteLine(" [--channel <ch>] Update channel (default: stable)");
|
||||||
|
Console.WriteLine(" [--baseline-version <v>] Baseline version");
|
||||||
|
Console.WriteLine(" [--baseline-zip <file>] Baseline payload zip path");
|
||||||
|
Console.WriteLine(" [--hash-algorithm <alg>] sha256 or md5 (default: sha256)");
|
||||||
|
Console.WriteLine(" [--launcher-path <path>] Launcher exe relative path");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine(" build-delta-from-commits Build delta by analyzing git commits");
|
||||||
|
Console.WriteLine(" --platform <platform> Platform identifier");
|
||||||
|
Console.WriteLine(" --current-version <v> Current release version");
|
||||||
|
Console.WriteLine(" --current-zip <file> Current payload zip path");
|
||||||
|
Console.WriteLine(" --output-dir <dir> Output directory");
|
||||||
|
Console.WriteLine(" --channel <ch> Update channel");
|
||||||
|
Console.WriteLine(" --baseline-tag <tag> Baseline git tag");
|
||||||
|
Console.WriteLine(" --current-tag <tag> Current git tag");
|
||||||
|
Console.WriteLine(" [--hash-algorithm <alg>] sha256 or md5 (default: sha256)");
|
||||||
|
Console.WriteLine(" [--source-dirs <dirs>] Comma-separated source dirs to analyze");
|
||||||
|
Console.WriteLine(" [--fallback-zip <file>] Baseline zip for fallback to file-compare");
|
||||||
|
Console.WriteLine(" [--launcher-path <path>] Launcher exe relative path");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine(" pack-payload Pack a directory into a payload zip");
|
||||||
|
Console.WriteLine(" --source-dir <dir> Source directory");
|
||||||
|
Console.WriteLine(" --output-zip <file> Output zip path");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
99
docs/auto_commit_md/20260530_6a65087.md
Normal file
99
docs/auto_commit_md/20260530_6a65087.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Git 提交分析报告
|
||||||
|
|
||||||
|
## 基本信息
|
||||||
|
- **哈希**: 6a650873bc4ec18c59abcbde8a778ce82b3c42fc
|
||||||
|
- **短哈希**: 6a65087
|
||||||
|
- **作者**: lincube <lincube3@hotmail.com>
|
||||||
|
- **时间**: 2026-05-30 11:56:50 +0800
|
||||||
|
|
||||||
|
## 提交信息摘要
|
||||||
|
feat..去除了冗余的字体文件,又修改了PLONDS系统
|
||||||
|
|
||||||
|
## 变更统计
|
||||||
|
| 指标 | 数值 |
|
||||||
|
|------|------|
|
||||||
|
| 变更文件数 | 23 |
|
||||||
|
| 新增行数 | 898 |
|
||||||
|
| 删除行数 | 357 |
|
||||||
|
| 净变化 | +541 |
|
||||||
|
|
||||||
|
## 详细变更分析
|
||||||
|
|
||||||
|
### 新增文件
|
||||||
|
1. `CheckIpcAot/CheckIpcAot.csproj` - 新增项目文件
|
||||||
|
2. `CheckIpcAot/Program.cs` - 新增程序入口
|
||||||
|
3. `.trae/specs/plonds-comparator-redesign/spec.md` - PLONDS 比较器重设计规格文档
|
||||||
|
|
||||||
|
### 删除文件
|
||||||
|
1. `LanMountainDesktop/Assets/Fonts/MiSans-Bold.otf` - MiSans 粗体字体
|
||||||
|
2. `LanMountainDesktop/Assets/Fonts/MiSans-Demibold.otf` - MiSans 特粗字体
|
||||||
|
3. `LanMountainDesktop/Assets/Fonts/MiSans-ExtraLight.otf` - MiSans 特细字体
|
||||||
|
4. `LanMountainDesktop/Assets/Fonts/MiSans-Heavy.otf` - MiSans 重字体
|
||||||
|
5. `LanMountainDesktop/Assets/Fonts/MiSans-Light.otf` - MiSans 细字体
|
||||||
|
6. `LanMountainDesktop/Assets/Fonts/MiSans-Medium.otf` - MiSans 中等字体
|
||||||
|
7. `LanMountainDesktop/Assets/Fonts/MiSans-Normal.otf` - MiSans 常规字体
|
||||||
|
8. `LanMountainDesktop/Assets/Fonts/MiSans-Regular.otf` - MiSans 标准字体
|
||||||
|
9. `LanMountainDesktop/Assets/Fonts/MiSans-Semibold.otf` - MiSans 半粗字体
|
||||||
|
10. `LanMountainDesktop/Assets/Fonts/MiSans-Thin.otf` - MiSans 纤细字体
|
||||||
|
11. `LanMountainDesktop/Assets/Fonts/MiSans-NOTICE.md` - MiSans 字体说明文档
|
||||||
|
|
||||||
|
### 主要变更文件
|
||||||
|
1. `.github/workflows/plonds-comparator.yml` - PLONDS 比较器工作流更新
|
||||||
|
2. `LanMountainDesktop/App.axaml.cs` - 应用入口文件精简
|
||||||
|
3. `LanMountainDesktop/Services/FusedDesktopManagerService.cs` - 融合桌面管理器服务
|
||||||
|
4. `LanMountainDesktop/Views/DesktopWidgetWindow.axaml.cs` - 桌面组件窗口
|
||||||
|
5. `LanMountainDesktop/Views/FusedDesktopComponentLibraryWindow.axaml.cs` - 融合桌面组件库窗口
|
||||||
|
6. `LanMountainDesktop.Launcher/AppJsonContext.cs` - Launcher JSON 上下文
|
||||||
|
7. `LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.AOT.props` - Launcher AOT 属性
|
||||||
|
8. `LanMountainDesktop.Tests/WindowLayerIsolationTests.cs` - 窗口层隔离测试(删除)
|
||||||
|
|
||||||
|
### 主要变更点
|
||||||
|
|
||||||
|
#### 1. 字体文件清理
|
||||||
|
- **删除所有 MiSans 字体文件**:移除了 10 个不同字重的 MiSans 字体文件(Bold、Demibold、ExtraLight、Heavy、Light、Medium、Normal、Regular、Semibold、Thin)
|
||||||
|
- **删除字体说明文档**:移除了 MiSans-NOTICE.md
|
||||||
|
- **潜在目的**:减小应用体积,可能改用系统字体或其他字体方案
|
||||||
|
|
||||||
|
#### 2. PLONDS 系统改进
|
||||||
|
- **工作流更新**:`.github/workflows/plonds-comparator.yml` 有 138 行新增,157 行删除
|
||||||
|
- 移除了 `edited` 事件类型触发
|
||||||
|
- 工作流名称保持不变
|
||||||
|
- **新增规格文档**:`.trae/specs/plonds-comparator-redesign/spec.md` 有 512 行新内容,描述了 PLONDS 比较器的重设计
|
||||||
|
|
||||||
|
#### 3. 桌面组件系统调整
|
||||||
|
- **FusedDesktopManagerService.cs**:106 行新增,41 行删除
|
||||||
|
- **DesktopWidgetWindow.axaml.cs**:135 行新增,1 行删除
|
||||||
|
- **FusedDesktopComponentLibraryWindow.axaml.cs**:23 行变更
|
||||||
|
- **App.axaml.cs**:19 行新增,117 行删除(大幅精简)
|
||||||
|
|
||||||
|
#### 4. 新增 CheckIpcAot 项目
|
||||||
|
- 新增独立的项目 `CheckIpcAot`,包含项目文件和简单的 Program.cs
|
||||||
|
- 可能用于测试 IPC 的 AOT 兼容性
|
||||||
|
|
||||||
|
#### 5. Launcher 相关调整
|
||||||
|
- `AppJsonContext.cs` 新增 5 行
|
||||||
|
- AOT 配置文件有小幅变更
|
||||||
|
|
||||||
|
#### 6. 测试清理
|
||||||
|
- 删除了 `WindowLayerIsolationTests.cs` 中的 5 行测试代码
|
||||||
|
|
||||||
|
## 代码审查要点
|
||||||
|
|
||||||
|
### 优势
|
||||||
|
1. **体积优化**:删除大量字体文件可显著减小应用体积
|
||||||
|
2. **PLONDS 改进**:工作流优化和重设计规格表明对发布流程有改进
|
||||||
|
3. **代码精简**:App.axaml.cs 的大幅精简符合代码整洁原则
|
||||||
|
4. **文档完善**:新增 PLONDS 重设计规格文档,便于理解和维护
|
||||||
|
|
||||||
|
### 潜在风险
|
||||||
|
1. **字体缺失**:删除所有 MiSans 字体文件可能导致应用显示问题,需确保已提供替代方案
|
||||||
|
2. **测试覆盖减少**:删除了窗口层隔离测试代码,需确认是否有替代测试
|
||||||
|
3. **工作流变更**:PLONDS 工作流的修改需要验证发布流程是否仍然正常工作
|
||||||
|
4. **兼容性**:CheckIpcAot 项目的添加可能引入新的依赖或 AOT 相关问题
|
||||||
|
|
||||||
|
### 建议
|
||||||
|
1. **验证字体替代方案**:确认应用是否已配置使用系统字体或其他字体
|
||||||
|
2. **测试 PLONDS 工作流**:手动触发一次工作流验证其正常运行
|
||||||
|
3. **补充测试**:如果删除的测试是重要的,考虑补充替代测试
|
||||||
|
4. **验证桌面组件功能**:重点测试桌面组件窗口和管理器的变更是否正常工作
|
||||||
|
5. **检查 AOT 配置**:确保新增的 CheckIpcAot 项目和 Launcher AOT 变更不会影响现有 AOT 构建
|
||||||
Reference in New Issue
Block a user