Skip to content

Commit 84a0cd8

Browse files
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
1 parent fcf4771 commit 84a0cd8

File tree

9 files changed

+169
-44
lines changed

9 files changed

+169
-44
lines changed

src/Umbraco.Core/Extensions/AssemblyExtensions.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) Umbraco.
22
// See LICENSE for more details.
33

4+
using System.Diagnostics.CodeAnalysis;
45
using System.Reflection;
6+
using Umbraco.Cms.Core.Semver;
57

68
namespace Umbraco.Extensions;
79

@@ -104,4 +106,35 @@ public static bool IsGlobalAsaxAssembly(this Assembly assembly) =>
104106

105107
return null;
106108
}
109+
110+
/// <summary>
111+
/// Gets the assembly informational version for the specified <paramref name="assembly" />.
112+
/// </summary>
113+
/// <param name="assembly">The assembly.</param>
114+
/// <param name="version">The assembly version.</param>
115+
/// <returns>
116+
/// <c>true</c> if the assembly information version is retrieved; otherwise, <c>false</c>.
117+
/// </returns>
118+
public static bool TryGetInformationalVersion(this Assembly assembly, [NotNullWhen(true)] out string? version)
119+
{
120+
AssemblyInformationalVersionAttribute? assemblyInformationalVersionAttribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
121+
if (assemblyInformationalVersionAttribute is not null &&
122+
SemVersion.TryParse(assemblyInformationalVersionAttribute.InformationalVersion, out SemVersion? semVersion))
123+
{
124+
version = semVersion.ToSemanticStringWithoutBuild();
125+
return true;
126+
}
127+
else
128+
{
129+
AssemblyName assemblyName = assembly.GetName();
130+
if (assemblyName.Version is not null)
131+
{
132+
version = assemblyName.Version.ToString(3);
133+
return true;
134+
}
135+
}
136+
137+
version = null;
138+
return false;
139+
}
107140
}

src/Umbraco.Core/IO/IOHelperExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using Umbraco.Cms.Core.IO;
23

34
namespace Umbraco.Extensions;
@@ -11,6 +12,7 @@ public static class IOHelperExtensions
1112
/// <param name="ioHelper"></param>
1213
/// <param name="path"></param>
1314
/// <returns></returns>
15+
[return: NotNullIfNotNull("path")]
1416
public static string? ResolveRelativeOrVirtualUrl(this IIOHelper ioHelper, string? path)
1517
{
1618
if (string.IsNullOrWhiteSpace(path))

src/Umbraco.Core/Manifest/PackageManifest.cs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55
namespace Umbraco.Cms.Core.Manifest;
66

77
/// <summary>
8-
/// Represents the content of a package manifest.
8+
/// Represents the content of a package manifest.
99
/// </summary>
1010
[DataContract]
1111
public class PackageManifest
1212
{
1313
private string? _packageName;
1414

1515
/// <summary>
16-
/// An optional package name. If not specified then the directory name is used.
16+
/// Gets or sets the name of the package. If not specified, uses the directory name instead.
1717
/// </summary>
18+
/// <value>
19+
/// The name of the package.
20+
/// </value>
1821
[DataMember(Name = "name")]
1922
public string? PackageName
2023
{
@@ -35,81 +38,132 @@ public string? PackageName
3538
set => _packageName = value;
3639
}
3740

41+
/// <summary>
42+
/// Gets or sets the package view.
43+
/// </summary>
44+
/// <value>
45+
/// The package view.
46+
/// </value>
3847
[DataMember(Name = "packageView")]
3948
public string? PackageView { get; set; }
4049

4150
/// <summary>
42-
/// Gets the source path of the manifest.
51+
/// Gets or sets the source path of the manifest.
4352
/// </summary>
53+
/// <value>
54+
/// The source path.
55+
/// </value>
4456
/// <remarks>
45-
/// <para>
46-
/// Gets the full absolute file path of the manifest,
47-
/// using system directory separators.
48-
/// </para>
57+
/// Gets the full/absolute file path of the manifest, using system directory separators.
4958
/// </remarks>
5059
[IgnoreDataMember]
5160
public string Source { get; set; } = null!;
5261

5362
/// <summary>
54-
/// Gets or sets the version of the package
63+
/// Gets or sets the version of the package.
5564
/// </summary>
65+
/// <value>
66+
/// The version of the package.
67+
/// </value>
5668
[DataMember(Name = "version")]
5769
public string Version { get; set; } = string.Empty;
5870

5971
/// <summary>
60-
/// Gets or sets a value indicating whether telemetry is allowed
72+
/// Gets or sets the assembly name to get the package version from.
6173
/// </summary>
74+
/// <value>
75+
/// The assembly name to get the package version from.
76+
/// </value>
77+
[DataMember(Name = "versionAssemblyName")]
78+
public string? VersionAssemblyName { get; set; }
79+
80+
/// <summary>
81+
/// Gets or sets a value indicating whether telemetry is allowed.
82+
/// </summary>
83+
/// <value>
84+
/// <c>true</c> if package telemetry is allowed; otherwise, <c>false</c>.
85+
/// </value>
6286
[DataMember(Name = "allowPackageTelemetry")]
6387
public bool AllowPackageTelemetry { get; set; } = true;
6488

89+
/// <summary>
90+
/// Gets or sets the bundle options.
91+
/// </summary>
92+
/// <value>
93+
/// The bundle options.
94+
/// </value>
6595
[DataMember(Name = "bundleOptions")]
6696
public BundleOptions BundleOptions { get; set; }
6797

6898
/// <summary>
69-
/// Gets or sets the scripts listed in the manifest.
99+
/// Gets or sets the scripts listed in the manifest.
70100
/// </summary>
101+
/// <value>
102+
/// The scripts.
103+
/// </value>
71104
[DataMember(Name = "javascript")]
72105
public string[] Scripts { get; set; } = Array.Empty<string>();
73106

74107
/// <summary>
75-
/// Gets or sets the stylesheets listed in the manifest.
108+
/// Gets or sets the stylesheets listed in the manifest.
76109
/// </summary>
110+
/// <value>
111+
/// The stylesheets.
112+
/// </value>
77113
[DataMember(Name = "css")]
78114
public string[] Stylesheets { get; set; } = Array.Empty<string>();
79115

80116
/// <summary>
81-
/// Gets or sets the property editors listed in the manifest.
117+
/// Gets or sets the property editors listed in the manifest.
82118
/// </summary>
119+
/// <value>
120+
/// The property editors.
121+
/// </value>
83122
[DataMember(Name = "propertyEditors")]
84123
public IDataEditor[] PropertyEditors { get; set; } = Array.Empty<IDataEditor>();
85124

86125
/// <summary>
87-
/// Gets or sets the parameter editors listed in the manifest.
126+
/// Gets or sets the parameter editors listed in the manifest.
88127
/// </summary>
128+
/// <value>
129+
/// The parameter editors.
130+
/// </value>
89131
[DataMember(Name = "parameterEditors")]
90132
public IDataEditor[] ParameterEditors { get; set; } = Array.Empty<IDataEditor>();
91133

92134
/// <summary>
93-
/// Gets or sets the grid editors listed in the manifest.
135+
/// Gets or sets the grid editors listed in the manifest.
94136
/// </summary>
137+
/// <value>
138+
/// The grid editors.
139+
/// </value>
95140
[DataMember(Name = "gridEditors")]
96141
public GridEditor[] GridEditors { get; set; } = Array.Empty<GridEditor>();
97142

98143
/// <summary>
99-
/// Gets or sets the content apps listed in the manifest.
144+
/// Gets or sets the content apps listed in the manifest.
100145
/// </summary>
146+
/// <value>
147+
/// The content apps.
148+
/// </value>
101149
[DataMember(Name = "contentApps")]
102150
public ManifestContentAppDefinition[] ContentApps { get; set; } = Array.Empty<ManifestContentAppDefinition>();
103151

104152
/// <summary>
105-
/// Gets or sets the dashboards listed in the manifest.
153+
/// Gets or sets the dashboards listed in the manifest.
106154
/// </summary>
155+
/// <value>
156+
/// The dashboards.
157+
/// </value>
107158
[DataMember(Name = "dashboards")]
108159
public ManifestDashboard[] Dashboards { get; set; } = Array.Empty<ManifestDashboard>();
109160

110161
/// <summary>
111-
/// Gets or sets the sections listed in the manifest.
162+
/// Gets or sets the sections listed in the manifest.
112163
/// </summary>
164+
/// <value>
165+
/// The sections.
166+
/// </value>
113167
[DataMember(Name = "sections")]
114168
public ManifestSection[] Sections { get; set; } = Array.Empty<ManifestSection>();
115169
}

src/Umbraco.Core/Semver/Semver.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Text.RegularExpressions;
1+
using System.Text.RegularExpressions;
2+
using System.Diagnostics.CodeAnalysis;
23
#if !NETSTANDARD
34
using System.Globalization;
45
using System.Runtime.Serialization;
@@ -195,7 +196,7 @@ public static SemVersion Parse(string version, bool strict = false)
195196
/// </param>
196197
/// <param name="strict">If set to <c>true</c> minor and patch version are required, else they default to 0.</param>
197198
/// <returns><c>False</c> when a invalid version string is passed, otherwise <c>true</c>.</returns>
198-
public static bool TryParse(string version, out SemVersion? semver, bool strict = false)
199+
public static bool TryParse(string version, [NotNullWhen(true)] out SemVersion? semver, bool strict = false)
199200
{
200201
try
201202
{

src/Umbraco.Infrastructure/Manifest/ManifestParser.cs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using System.Reflection;
3+
using System.Runtime.Loader;
14
using System.Text;
25
using Microsoft.Extensions.DependencyInjection;
36
using Microsoft.Extensions.FileProviders;
@@ -17,7 +20,7 @@
1720
namespace Umbraco.Cms.Core.Manifest;
1821

1922
/// <summary>
20-
/// Parses the Main.js file and replaces all tokens accordingly.
23+
/// Parses the Main.js file and replaces all tokens accordingly.
2124
/// </summary>
2225
public class ManifestParser : IManifestParser
2326
{
@@ -39,7 +42,7 @@ public class ManifestParser : IManifestParser
3942
private string _path = null!;
4043

4144
/// <summary>
42-
/// Initializes a new instance of the <see cref="ManifestParser" /> class.
45+
/// Initializes a new instance of the <see cref="ManifestParser" /> class.
4346
/// </summary>
4447
public ManifestParser(
4548
AppCaches appCaches,
@@ -163,31 +166,35 @@ public IEnumerable<PackageManifest> GetManifests()
163166
/// </summary>
164167
public PackageManifest ParseManifest(string text)
165168
{
166-
if (text == null)
167-
{
168-
throw new ArgumentNullException(nameof(text));
169-
}
169+
ArgumentNullException.ThrowIfNull(text);
170170

171171
if (string.IsNullOrWhiteSpace(text))
172172
{
173173
throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(text));
174174
}
175175

176-
PackageManifest? manifest = JsonConvert.DeserializeObject<PackageManifest>(
176+
PackageManifest manifest = JsonConvert.DeserializeObject<PackageManifest>(
177177
text,
178178
new DataEditorConverter(_dataValueEditorFactory, _ioHelper, _localizedTextService, _shortStringHelper, _jsonSerializer),
179179
new ValueValidatorConverter(_validators),
180-
new DashboardAccessRuleConverter());
180+
new DashboardAccessRuleConverter())!;
181+
182+
if (string.IsNullOrEmpty(manifest.Version) &&
183+
!string.IsNullOrEmpty(manifest.VersionAssemblyName) &&
184+
TryGetAssemblyInformationalVersion(manifest.VersionAssemblyName, out string? version))
185+
{
186+
manifest.Version = version;
187+
}
181188

182189
// scripts and stylesheets are raw string, must process here
183-
for (var i = 0; i < manifest!.Scripts.Length; i++)
190+
for (var i = 0; i < manifest.Scripts.Length; i++)
184191
{
185-
manifest.Scripts[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Scripts[i])!;
192+
manifest.Scripts[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Scripts[i]);
186193
}
187194

188195
for (var i = 0; i < manifest.Stylesheets.Length; i++)
189196
{
190-
manifest.Stylesheets[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Stylesheets[i])!;
197+
manifest.Stylesheets[i] = _ioHelper.ResolveRelativeOrVirtualUrl(manifest.Stylesheets[i]);
191198
}
192199

193200
foreach (ManifestContentAppDefinition contentApp in manifest.ContentApps)
@@ -197,7 +204,7 @@ public PackageManifest ParseManifest(string text)
197204

198205
foreach (ManifestDashboard dashboard in manifest.Dashboards)
199206
{
200-
dashboard.View = _ioHelper.ResolveRelativeOrVirtualUrl(dashboard.View)!;
207+
dashboard.View = _ioHelper.ResolveRelativeOrVirtualUrl(dashboard.View);
201208
}
202209

203210
foreach (GridEditor gridEditor in manifest.GridEditors)
@@ -217,6 +224,22 @@ public PackageManifest ParseManifest(string text)
217224
return manifest;
218225
}
219226

227+
private bool TryGetAssemblyInformationalVersion(string name, [NotNullWhen(true)] out string? version)
228+
{
229+
foreach (Assembly assembly in AssemblyLoadContext.Default.Assemblies)
230+
{
231+
AssemblyName assemblyName = assembly.GetName();
232+
if (string.Equals(assemblyName.Name, name, StringComparison.OrdinalIgnoreCase) &&
233+
assembly.TryGetInformationalVersion(out version))
234+
{
235+
return true;
236+
}
237+
}
238+
239+
version = null;
240+
return false;
241+
}
242+
220243
/// <summary>
221244
/// Merges all manifests into one.
222245
/// </summary>

0 commit comments

Comments
 (0)