Files
aeqw89.tools.Publish/aeqw89.tools.Publish/ProjectFile.cs
T
qwsdcvghyu89 5467de0d1a Add version update logic and improve tool configuration
- Introduced `ChangeVersion` method for version manipulation based on major, minor, and patch increments.
- Updated .gitignore to include `obj/` directory.
- Added `Aigamo.ResXGenerator` dependency and configuration for ResX generation.
- Refactored argument parsing in `Program.cs` for improved readability and error handling.
- Cleaned up `Exceptions.resx` file format and extended comments for clarity.
2026-06-04 16:15:02 +10:00

269 lines
9.9 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Xml;
using aeqw89.xml.ProjectFile;
namespace aeqw89.tools.Publish;
// -------------------------------------------------------------------------------------------------
// Minimal replacements for VsTools.Projects so ProjectFile can keep its public surface intact
// -------------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------------
// Your original public surface, now backed by System.Xml
// -------------------------------------------------------------------------------------------------
internal class ProjectFile {
public Project Project { get; private set; }
public string Path { get; private set; }
private PropertyGroup MainPropertyGroup { get; set; }
private ProjectFile(Project project, string path) {
MainPropertyGroup = project.PropertyGroups.FirstOrDefault()!;
Project = project;
Path = path;
}
public static bool TryLoad(string path, [NotNullWhen(true)] out ProjectFile? projectFile, [NotNullWhen(false)] out string? error) {
projectFile = null;
error = null;
if (!path.EndsWith(".csproj")) {
if (!Directory.Exists(path)) {
error = Exceptions.tried_loading_non_csproj_file;
return false;
}
var csproj = Directory.EnumerateFiles(path, "*.csproj", SearchOption.TopDirectoryOnly).ToArray();
if (csproj.Length == 0) {
error = Exceptions.no_project_in_directory;
return false;
}
if (csproj.Length > 1) {
error = Exceptions.found_multiple_csproj;
return false;
}
path = csproj[0];
}
try {
projectFile = new ProjectFile(Project.Load(path), path);
return true;
}
catch (Exception e) {
error = string.Format(Exceptions.generic_error, e);
return false;
}
}
public void Backup(string? backupPath = null) {
backupPath ??= Path + ".bak";
File.Copy(Path, backupPath, true);
}
public string GetDefaultBackupLocation() => Path + ".bak";
public bool Restore(string? backupPath = null) {
backupPath ??= GetDefaultBackupLocation();
if (!File.Exists(backupPath)) return false;
File.Copy(backupPath, Path, true);
return true;
}
public void Save() {
// Check for empty property or item groups
foreach (var ig in Project.ItemGroups.ToList().Where(ig => ig.Items.Count == 0)) {
ig.Remove();
}
foreach (var pg in Project.PropertyGroups.ToList().Where(pg => pg.Count == 0)) {
pg.Remove();
}
Project.Save();
}
public bool TryRepair([NotNullWhen(false)] out string? error) {
// Ensure we have a PropertyGroup with TargetFramework
if (MainPropertyGroup?.HasProperty("TargetFramework") != true) {
if (!Project.PropertyGroups.Any(x => x.HasProperty("TargetFramework"))) {
error = string.Format(Exceptions.project_file_irreparable, Path, "TargetFramework");
return false;
}
MainPropertyGroup = Project.PropertyGroups.First(x => x.HasProperty("TargetFramework"));
}
List<string> failed = [];
void set(string key, string value, bool required = false) {
if (!MainPropertyGroup.HasProperty(key) || string.IsNullOrEmpty(MainPropertyGroup.GetProperty(key))) {
if (required) {
failed.Add(key);
return;
}
MainPropertyGroup.SetProperty(key, value);
}
}
set("Version", "1.0.0");
set("PackageVersion", "1.0.0");
set("Title", System.IO.Path.GetFileNameWithoutExtension(Path));
set("Authors", "");
set("Company", "");
set("Description", "");
set("PackageProjectUrl", "", required: false);
set("RepositoryUrl", "", required: true);
set("PackageId", "", required: true);
if (failed.Count > 0) {
error = string.Format(Exceptions.project_file_irreparable, Path, string.Join(", ", failed));
return false;
}
error = null;
return true;
}
public List<PackageReference> GetPackageReferences() {
return Project.ItemGroups
.SelectMany(g => g.Items)
.Where(i => i.ElementName == "PackageReference")
.OfType<PackageReference>()
.ToList();
}
public List<ProjectReference> GetProjectReferences() {
return Project.ItemGroups
.SelectMany(g => g.Items)
.Where(i => i.ElementName == "ProjectReference")
.OfType<ProjectReference>()
.ToList();
}
public List<Content> GetPackageDependencies() {
return Project.ItemGroups
.SelectMany(g => g.Items)
.Where(i => i.ElementName == "Content")
.OfType<Content>()
.ToList();
}
private void CreateOrUpdateChild(Item item, string childName, string value) {
var child = item.Node.SelectSingleNode(childName) as XmlElement;
if (child != null) {
child.InnerText = value;
return;
}
child = item.Node.OwnerDocument!.CreateElement(childName, item.Node.NamespaceURI);
child.InnerText = value;
item.Node.AppendChild(child);
}
public string GetPackageId() {
return MainPropertyGroup.GetProperty("PackageId");
}
public void SetPrivateAssets(Item item, PrivateAssetsValue value) {
if (value == PrivateAssetsValue.None) {
var child = item.Node.SelectSingleNode("./PrivateAssets") as XmlElement;
if (child != null)
item.Node.RemoveChild(child);
return;
}
CreateOrUpdateChild(item, "PrivateAssets", value.ToString().ToLowerInvariant());
}
public void SetTransitive(Item item, bool value)
=> CreateOrUpdateChild(item, "Transitive", value.ToString().ToLowerInvariant());
public bool IsTransitive(Item item) {
var transitive = item.Node.SelectSingleNode("./Transitive") as XmlElement;
return transitive?.InnerText == "true";
}
public string GetAbsoluteIncludePath(Item item) {
return System.IO.Path.GetFullPath(item.Include);
}
public Item AddPackage(Item otherPackage) {
var allItems = Project.ItemGroups.SelectMany(x => x.Items);
var existing = allItems.FirstOrDefault(x =>
x.ElementName == "PackageReference" && x.Include == otherPackage.Include);
if (existing != null) return existing;
var itemGroup = Project.ItemGroups.First();
// Import the original XmlElement to preserve Version (attr or child) and any metadata
itemGroup.Add(otherPackage);
return itemGroup.Items.Last();
}
public bool RemovePackage(Item otherPackage) {
var grp = Project.ItemGroups
.FirstOrDefault(g => g.Items.Any(y => y.ElementName == "PackageReference" && y.Include == otherPackage.Include));
var pkg = grp?.Items.FirstOrDefault(x => x.ElementName == otherPackage.ElementName && x.Include == otherPackage.Include);
if (pkg == null) return false;
pkg.Node.ParentNode!.RemoveChild(pkg.Node);
grp!.Items.Remove(pkg);
return true;
}
public string GetVersion() => MainPropertyGroup.GetProperty("Version");
public void SetVersion(string version) {
MainPropertyGroup.SetProperty("Version", version);
MainPropertyGroup.SetProperty("PackageVersion", version);
}
/// <summary>
/// Updates the version string by applying the specified operation to the major, minor, and patch components of the version.
/// </summary>
/// <param name="version">The current version string in the format "major.minor.patch[-tag]".</param>
/// <param name="patch">The value to apply to the patch component.</param>
/// <param name="minor">The value to apply to the minor component.</param>
/// <param name="major">The value to apply to the major component.</param>
/// <param name="operation">A function that defines the adjustment operation to be performed on each version component.</param>
/// <returns>A new version string with the updated major, minor, and patch components, preserving any existing tag.</returns>
/// <exception cref="Exception">Thrown if the version string is not in the correct format.</exception>
public static Result<string, ReadableError> ChangeVersion(string version, int delta, IncrementTarget target) {
string[] split = version.Split('.');
if (split.Length != 3) {
return new ReadableError(string.Format(Exceptions.version_string_not_formatted_correctly, version));
}
string tag = "";
if (split[2].Contains('-')) {
var split2 = split[2].Split('-');
split[2] = split2[0];
tag = "-" + split2[1];
}
if (split.Any(x => !int.TryParse(x, out _)))
return new ReadableError(string.Format(Exceptions.version_string_not_formatted_correctly, version));
int[] parsedVersion = split.Select(int.Parse).ToArray();
switch (target) {
case IncrementTarget.Major:
parsedVersion[0] += delta;
parsedVersion[1] = 0;
parsedVersion[2] = 0;
break;
case IncrementTarget.Minor:
parsedVersion[1] += delta;
parsedVersion[2] = 0;
break;
case IncrementTarget.Patch:
parsedVersion[2] += delta;
break;
}
return
$"{parsedVersion[0]}.{parsedVersion[1]}.{parsedVersion[2]}{tag}".Ok();
}
}