Add VersionAssemblyName to package manifest (#14046)
* Add VersionAssemblyName to package manifest * Fix/improve nullability * Ensure package version from manifest is set when package migration exists * Set versionAssemblyName in umbracopackage template * Use Assembly.Load instead of ITypeFinder * Use AssemblyLoadContext to get asesmbly by name * Get version from package migration assembly * Hide unknown package version * Set versionAssemblyName in umbracopackage-rcl template
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Umbraco.
|
||||
// See LICENSE for more details.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using Umbraco.Cms.Core.Semver;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
@@ -104,4 +106,35 @@ public static class AssemblyExtensions
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the assembly informational version for the specified <paramref name="assembly" />.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly.</param>
|
||||
/// <param name="version">The assembly version.</param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the assembly information version is retrieved; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
public static bool TryGetInformationalVersion(this Assembly assembly, [NotNullWhen(true)] out string? version)
|
||||
{
|
||||
AssemblyInformationalVersionAttribute? assemblyInformationalVersionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
if (assemblyInformationalVersionAttribute is not null &&
|
||||
SemVersion.TryParse(assemblyInformationalVersionAttribute.InformationalVersion, out SemVersion? semVersion))
|
||||
{
|
||||
version = semVersion.ToSemanticStringWithoutBuild();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssemblyName assemblyName = assembly.GetName();
|
||||
if (assemblyName.Version is not null)
|
||||
{
|
||||
version = assemblyName.Version.ToString(3);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
version = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Umbraco.Cms.Core.IO;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
@@ -11,6 +12,7 @@ public static class IOHelperExtensions
|
||||
/// <param name="ioHelper"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
[return: NotNullIfNotNull("path")]
|
||||
public static string? ResolveRelativeOrVirtualUrl(this IIOHelper ioHelper, string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
|
||||
@@ -5,7 +5,7 @@ using Umbraco.Extensions;
|
||||
namespace Umbraco.Cms.Core.Manifest;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the content of a package manifest.
|
||||
/// Represents the content of a package manifest.
|
||||
/// </summary>
|
||||
[DataContract]
|
||||
public class PackageManifest
|
||||
@@ -13,8 +13,11 @@ public class PackageManifest
|
||||
private string? _packageName;
|
||||
|
||||
/// <summary>
|
||||
/// An optional package name. If not specified then the directory name is used.
|
||||
/// Gets or sets the name of the package. If not specified, uses the directory name instead.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The name of the package.
|
||||
/// </value>
|
||||
[DataMember(Name = "name")]
|
||||
public string? PackageName
|
||||
{
|
||||
@@ -35,81 +38,132 @@ public class PackageManifest
|
||||
set => _packageName = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the package view.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The package view.
|
||||
/// </value>
|
||||
[DataMember(Name = "packageView")]
|
||||
public string? PackageView { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source path of the manifest.
|
||||
/// Gets or sets the source path of the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The source path.
|
||||
/// </value>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Gets the full absolute file path of the manifest,
|
||||
/// using system directory separators.
|
||||
/// </para>
|
||||
/// Gets the full/absolute file path of the manifest, using system directory separators.
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
public string Source { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the version of the package
|
||||
/// Gets or sets the version of the package.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The version of the package.
|
||||
/// </value>
|
||||
[DataMember(Name = "version")]
|
||||
public string Version { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether telemetry is allowed
|
||||
/// Gets or sets the assembly name to get the package version from.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The assembly name to get the package version from.
|
||||
/// </value>
|
||||
[DataMember(Name = "versionAssemblyName")]
|
||||
public string? VersionAssemblyName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether telemetry is allowed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if package telemetry is allowed; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
[DataMember(Name = "allowPackageTelemetry")]
|
||||
public bool AllowPackageTelemetry { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the bundle options.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The bundle options.
|
||||
/// </value>
|
||||
[DataMember(Name = "bundleOptions")]
|
||||
public BundleOptions BundleOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the scripts listed in the manifest.
|
||||
/// Gets or sets the scripts listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The scripts.
|
||||
/// </value>
|
||||
[DataMember(Name = "javascript")]
|
||||
public string[] Scripts { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stylesheets listed in the manifest.
|
||||
/// Gets or sets the stylesheets listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The stylesheets.
|
||||
/// </value>
|
||||
[DataMember(Name = "css")]
|
||||
public string[] Stylesheets { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the property editors listed in the manifest.
|
||||
/// Gets or sets the property editors listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The property editors.
|
||||
/// </value>
|
||||
[DataMember(Name = "propertyEditors")]
|
||||
public IDataEditor[] PropertyEditors { get; set; } = Array.Empty<IDataEditor>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter editors listed in the manifest.
|
||||
/// Gets or sets the parameter editors listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The parameter editors.
|
||||
/// </value>
|
||||
[DataMember(Name = "parameterEditors")]
|
||||
public IDataEditor[] ParameterEditors { get; set; } = Array.Empty<IDataEditor>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the grid editors listed in the manifest.
|
||||
/// Gets or sets the grid editors listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The grid editors.
|
||||
/// </value>
|
||||
[DataMember(Name = "gridEditors")]
|
||||
public GridEditor[] GridEditors { get; set; } = Array.Empty<GridEditor>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content apps listed in the manifest.
|
||||
/// Gets or sets the content apps listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The content apps.
|
||||
/// </value>
|
||||
[DataMember(Name = "contentApps")]
|
||||
public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty<ManifestContentAppDefinition>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the dashboards listed in the manifest.
|
||||
/// Gets or sets the dashboards listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The dashboards.
|
||||
/// </value>
|
||||
[DataMember(Name = "dashboards")]
|
||||
public ManifestDashboard[] Dashboards { get; set; } = Array.Empty<ManifestDashboard>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the sections listed in the manifest.
|
||||
/// Gets or sets the sections listed in the manifest.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// The sections.
|
||||
/// </value>
|
||||
[DataMember(Name = "sections")]
|
||||
public ManifestSection[] Sections { get; set; } = Array.Empty<ManifestSection>();
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#if !NETSTANDARD
|
||||
using System.Globalization;
|
||||
using System.Runtime.Serialization;
|
||||
@@ -195,7 +196,7 @@ namespace Umbraco.Cms.Core.Semver
|
||||
/// </param>
|
||||
/// <param name="strict">If set to <c>true</c> minor and patch version are required, else they default to 0.</param>
|
||||
/// <returns><c>False</c> when a invalid version string is passed, otherwise <c>true</c>.</returns>
|
||||
public static bool TryParse(string version, out SemVersion? semver, bool strict = false)
|
||||
public static bool TryParse(string version, [NotNullWhen(true)] out SemVersion? semver, bool strict = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
@@ -17,7 +20,7 @@ using Umbraco.Extensions;
|
||||
namespace Umbraco.Cms.Core.Manifest;
|
||||
|
||||
/// <summary>
|
||||
/// Parses the Main.js file and replaces all tokens accordingly.
|
||||
/// Parses the Main.js file and replaces all tokens accordingly.
|
||||
/// </summary>
|
||||
public class ManifestParser : IManifestParser
|
||||
{
|
||||
@@ -39,7 +42,7 @@ public class ManifestParser : IManifestParser
|
||||
private string _path = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ManifestParser" /> class.
|
||||
/// Initializes a new instance of the <see cref="ManifestParser" /> class.
|
||||
/// </summary>
|
||||
public ManifestParser(
|
||||
AppCaches appCaches,
|
||||
@@ -163,31 +166,35 @@ public class ManifestParser : IManifestParser
|
||||
/// </summary>
|
||||
public PackageManifest ParseManifest(string text)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
ArgumentNullException.ThrowIfNull(text);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text));
|
||||
}
|
||||
|
||||
PackageManifest? manifest = JsonConvert.DeserializeObject<PackageManifest>(
|
||||
PackageManifest manifest = JsonConvert.DeserializeObject<PackageManifest>(
|
||||
text,
|
||||
new DataEditorConverter(_dataValueEditorFactory, _ioHelper, _localizedTextService, _shortStringHelper, _jsonSerializer),
|
||||
new ValueValidatorConverter(_validators),
|
||||
new DashboardAccessRuleConverter());
|
||||
new DashboardAccessRuleConverter())!;
|
||||
|
||||
if (string.IsNullOrEmpty(manifest.Version) &&
|
||||
!string.IsNullOrEmpty(manifest.VersionAssemblyName) &&
|
||||
TryGetAssemblyInformationalVersion(manifest.VersionAssemblyName, out string? version))
|
||||
{
|
||||
manifest.Version = version;
|
||||
}
|
||||
|
||||
// scripts and stylesheets are raw string, must process here
|
||||
for (var i = 0; i < manifest!.Scripts.Length; i++)
|
||||
for (var i = 0; i < manifest.Scripts.Length; i++)
|
||||
{
|
||||
manifest.Scripts[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Scripts[i])!;
|
||||
manifest.Scripts[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Scripts[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < manifest.Stylesheets.Length; i++)
|
||||
{
|
||||
manifest.Stylesheets[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Stylesheets[i])!;
|
||||
manifest.Stylesheets[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Stylesheets[i]);
|
||||
}
|
||||
|
||||
foreach (ManifestContentAppDefinition contentApp in manifest.ContentApps)
|
||||
@@ -197,7 +204,7 @@ public class ManifestParser : IManifestParser
|
||||
|
||||
foreach (ManifestDashboard dashboard in manifest.Dashboards)
|
||||
{
|
||||
dashboard.View = _ioHelper.ResolveRelativeOrVirtualUrl(dashboard.View)!;
|
||||
dashboard.View = _ioHelper.ResolveRelativeOrVirtualUrl(dashboard.View);
|
||||
}
|
||||
|
||||
foreach (GridEditor gridEditor in manifest.GridEditors)
|
||||
@@ -217,6 +224,22 @@ public class ManifestParser : IManifestParser
|
||||
return manifest;
|
||||
}
|
||||
|
||||
private bool TryGetAssemblyInformationalVersion(string name, [NotNullWhen(true)] out string? version)
|
||||
{
|
||||
foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies)
|
||||
{
|
||||
AssemblyName assemblyName = assembly.GetName();
|
||||
if (string.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase) &&
|
||||
assembly.TryGetInformationalVersion(out version))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
version = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges all manifests into one.
|
||||
/// </summary>
|
||||
|
||||
@@ -116,8 +116,7 @@ public class PackagingService : IPackagingService
|
||||
|
||||
public IEnumerable<InstalledPackage> GetAllInstalledPackages()
|
||||
{
|
||||
IReadOnlyDictionary<string, string?>? keyValues =
|
||||
_keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
|
||||
IReadOnlyDictionary<string, string?>? keyValues = _keyValueService.FindByKeyPrefix(Constants.Conventions.Migrations.KeyValuePrefix);
|
||||
|
||||
var installedPackages = new Dictionary<string, InstalledPackage>();
|
||||
|
||||
@@ -126,14 +125,21 @@ public class PackagingService : IPackagingService
|
||||
{
|
||||
if (!installedPackages.TryGetValue(plan.PackageName, out InstalledPackage? installedPackage))
|
||||
{
|
||||
installedPackage = new InstalledPackage { PackageName = plan.PackageName };
|
||||
installedPackage = new InstalledPackage
|
||||
{
|
||||
PackageName = plan.PackageName,
|
||||
};
|
||||
|
||||
if (plan.GetType().Assembly.TryGetInformationalVersion(out string? version))
|
||||
{
|
||||
installedPackage.Version = version;
|
||||
}
|
||||
|
||||
installedPackages.Add(plan.PackageName, installedPackage);
|
||||
}
|
||||
|
||||
var currentPlans = installedPackage.PackageMigrationPlans.ToList();
|
||||
if (keyValues is null || keyValues.TryGetValue(
|
||||
Constants.Conventions.Migrations.KeyValuePrefix + plan.Name,
|
||||
out var currentState) is false)
|
||||
if (keyValues is null || keyValues.TryGetValue(Constants.Conventions.Migrations.KeyValuePrefix + plan.Name, out var currentState) is false)
|
||||
{
|
||||
currentState = null;
|
||||
}
|
||||
@@ -157,14 +163,20 @@ public class PackagingService : IPackagingService
|
||||
|
||||
if (!installedPackages.TryGetValue(package.PackageName, out InstalledPackage? installedPackage))
|
||||
{
|
||||
installedPackage = new InstalledPackage {
|
||||
installedPackage = new InstalledPackage
|
||||
{
|
||||
PackageName = package.PackageName,
|
||||
Version = string.IsNullOrEmpty(package.Version) ? "Unknown" : package.Version,
|
||||
};
|
||||
|
||||
installedPackages.Add(package.PackageName, installedPackage);
|
||||
}
|
||||
|
||||
// Set additional values
|
||||
if (!string.IsNullOrEmpty(package.Version))
|
||||
{
|
||||
installedPackage.Version = package.Version;
|
||||
}
|
||||
|
||||
installedPackage.PackageView = package.PackageView;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
<div class="umb-package-list__item-content">
|
||||
<div class="umb-package-list__item-name">{{ installedPackage.name }}</div>
|
||||
<div class="umb-package-list__item-description">Version: {{ installedPackage.version }}</div>
|
||||
<div class="umb-package-list__item-description" ng-if="installedPackage.version">Version: {{ installedPackage.version }}</div>
|
||||
<div class="umb-package-list__item-description">
|
||||
<localize ng-if="installedPackage.hasMigrations && !installedPackage.hasPendingMigrations"
|
||||
key="packager_packageMigrationsNonePending">No pending migrations</localize>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
{
|
||||
"name": "UmbracoPackage",
|
||||
"version": "",
|
||||
"versionAssemblyName": "UmbracoPackage",
|
||||
"allowPackageTelemetry": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "UmbracoPackage",
|
||||
"version": "",
|
||||
"versionAssemblyName": "UmbracoPackage",
|
||||
"allowPackageTelemetry": true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user