Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ai/prompts/create-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Scaffold a new installable GrandNode plugin end to end, with the correct project

1. `SystemName` in `Manifest.cs` must equal the value in `{Feature}Defaults` and the output folder name.
2. `Group` must be one of the existing group names in `.ai/knowledge/plugin-types.md`.
3. All GrandNode project references must be `Private="false"`.
3. Import `src/Build/Grand.Plugin.props` for the shared host references; do not repeat them in the plugin. Anything referenced beyond that set must be `Private="false"`.
4. Use `Microsoft.NET.Sdk.Razor` when the plugin contains `.cshtml` files, `Microsoft.NET.Sdk` otherwise.
5. `Install()` saves default settings and adds localization resources, then calls `base.Install()` last.
6. `Uninstall()` deletes settings and removes localization resources, then calls `base.Uninstall()` last.
Expand Down
3 changes: 2 additions & 1 deletion .ai/standards/dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Do not override `TargetFramework` or `LangVersion` in an individual project.

## Project references

- Reference GrandNode projects with `<Private>false</Private>` in plugins and modules — the host already loads those assemblies.
- Plugins import `src/Build/Grand.Plugin.props`, which carries the host projects every plugin compiles against, each already `Private="false"`. Do not restate that list in a plugin — a new core project goes in the props file, so it reaches all plugins at once.
- Reference GrandNode projects with `<Private>false</Private>` in modules, and in a plugin for anything beyond the shared set — the host already loads those assemblies. `Private="false"` does not carry to a reference's own dependencies, so each project has to be named.
- Use `<ExcludeAssets>all</ExcludeAssets>` (plugins referencing `Grand.Web` / `Grand.Web.Common`) or `<ExcludeAssets>runtime</ExcludeAssets>` (modules) following the nearest existing project of the same kind.
- Never reference a plugin from core, business, or web projects. Dependencies point inward only.

Expand Down
32 changes: 6 additions & 26 deletions .ai/templates/plugin/base-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Placeholders: `{SystemName}` = `{Group}.{Name}`, `{Feature}` = type-name prefix.
```xml
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\..\Build\Grand.Common.props" />
<Import Project="..\..\Build\Grand.Plugin.props" />
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
Expand All @@ -28,31 +29,6 @@ Placeholders: `{SystemName}` = `{Group}.{Name}`, `{Feature}` = type-name prefix.
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Core\Grand.Data\Grand.Data.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Domain\Grand.Domain.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mapping\Grand.Mapping.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Infrastructure\Grand.Infrastructure.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.SharedKernel\Grand.SharedKernel.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Business\Grand.Business.Core\Grand.Business.Core.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Web\Grand.Web.Common\Grand.Web.Common.csproj">
<Private>false</Private>
<ExcludeAssets>all</ExcludeAssets>
</ProjectReference>
</ItemGroup>

<ItemGroup>
<None Update="logo.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand All @@ -61,7 +37,11 @@ Placeholders: `{SystemName}` = `{Group}.{Name}`, `{Feature}` = type-name prefix.
</Project>
```

Use `Microsoft.NET.Sdk` and drop the Razor properties when the plugin ships no `.cshtml`. Trim project references down to what the plugin actually uses.
Use `Microsoft.NET.Sdk` and drop the Razor properties when the plugin ships no `.cshtml`.

`Grand.Plugin.props` brings in the host projects every plugin compiles against — `Grand.SharedKernel`, `Grand.Domain`, `Grand.Data`, `Grand.Mapping`, `Grand.Mediator`, `Grand.Infrastructure`, `Grand.Business.Core` and `Grand.Web.Common` — each with `Private=false` so nothing is copied into the plugin folder. Do not restate them in the plugin: they were duplicated across all 16 plugins until `Grand.Mediator` was added to the solution, added to none of them, and copied into every plugin's output. Add a new core project to `src/Build/Grand.Plugin.props`, not here.

The unused references cost nothing at runtime, so there is no need to trim the list. A plugin needing something beyond that set — a NuGet package, or `Grand.Web` as `Theme.Modern` does — declares it in its own `ItemGroup`.

## 2. `Manifest.cs`

Expand Down
50 changes: 50 additions & 0 deletions src/Build/Grand.Plugin.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<Project>

<!--
A plugin is a library, and the SDK does not copy NuGet assemblies into a library's
output - it expects the runtime to resolve them from deps.json, which does not happen
for an assembly side-loaded from a plugin folder. So the plugin's own packages have to
be copied, and CopyLocalLockFileAssemblies does that.

On its own it would also copy the host's entire package graph - 62 assemblies for a
plugin that needs one. ExcludeAssets=runtime on each host reference is what prevents
that: the plugin still compiles against the project, but nothing from its runtime
closure is copied. The same pairing is what the projects in src/Modules use.
-->
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<!--
The host assemblies every plugin compiles against.

Private=false keeps them out of the plugin's output folder: the plugin is loaded
into the host's AssemblyLoadContext, which already has them, and a second copy of
a host assembly beside a plugin is a hazard - if that copy is ever the one loaded,
its types are not the host's types and DI resolution fails in ways that are hard
to trace.

Private=false does not carry to a reference's own dependencies, which is why each
project is listed rather than relying on the transitive graph. Keeping the list in
one file is the point: it used to be duplicated across every plugin, so a new core
project was added to some and forgotten in others - Grand.Mediator shipped that way
and was copied into all 16 plugin folders.

Add a new core project here, not in the individual plugins.

Paths are anchored to this file so a plugin can sit at any depth.
-->

<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Core\Grand.SharedKernel\Grand.SharedKernel.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Core\Grand.Domain\Grand.Domain.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Core\Grand.Data\Grand.Data.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Core\Grand.Mapping\Grand.Mapping.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Core\Grand.Mediator\Grand.Mediator.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Core\Grand.Infrastructure\Grand.Infrastructure.csproj" Private="false" ExcludeAssets="runtime" />
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Business\Grand.Business.Core\Grand.Business.Core.csproj" Private="false" ExcludeAssets="runtime" />
<!-- ExcludeAssets=all: referenced for its views and tag helpers, nothing is linked from it -->
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Web\Grand.Web.Common\Grand.Web.Common.csproj" Private="false" ExcludeAssets="all" />
</ItemGroup>

</Project>
16 changes: 6 additions & 10 deletions src/Core/Grand.Infrastructure/Plugins/PluginInfoAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
using System.Reflection;

namespace Grand.Infrastructure.Plugins;
namespace Grand.Infrastructure.Plugins;

[AttributeUsage(AttributeTargets.Assembly)]
public class PluginInfoAttribute : Attribute
{
public PluginInfoAttribute()
{
var assembly = Assembly.GetExecutingAssembly();
var fullVersion = assembly.GetName().Version;
SupportedVersion = $"{fullVersion?.Major}.{fullVersion?.Minor}";
}

public string Group { get; set; } = string.Empty;
public string FriendlyName { get; set; } = string.Empty;
public string SystemName { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;

/// <summary>
/// The GrandNode version this plugin supports, as "Major.Minor". Left unset by convention -
/// it is then resolved from the assembly's Grand.Infrastructure reference by
/// <see cref="PluginVersionResolver" />.
/// </summary>
public string SupportedVersion { get; set; }

public string Version { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Grand.Infrastructure/Plugins/PluginManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private static PluginInfo PreparePluginInfo(FileInfo pluginFile)
Group = pluginInfo.Group,
SystemName = pluginInfo.SystemName,
Version = pluginInfo.Version,
SupportedVersion = pluginInfo.SupportedVersion,
SupportedVersion = PluginVersionResolver.ResolveSupportedVersion(assembly, pluginInfo.SupportedVersion),
Author = pluginInfo.Author,
PluginFileName = plug.Name,
OriginalAssemblyFile = pluginFile,
Expand Down
35 changes: 35 additions & 0 deletions src/Core/Grand.Infrastructure/Plugins/PluginVersionResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Reflection;

namespace Grand.Infrastructure.Plugins;

/// <summary>
/// Resolves which GrandNode version a plugin assembly was built against.
/// </summary>
public static class PluginVersionResolver
{
private const string CoreAssemblyName = "Grand.Infrastructure";

/// <summary>
/// Returns the GrandNode version the plugin supports, as "Major.Minor".
/// </summary>
/// <param name="pluginAssembly">The plugin assembly the info attribute was read from</param>
/// <param name="declaredVersion">The version declared on <see cref="PluginInfoAttribute" />, if any</param>
/// <returns>
/// The declared version when the plugin states one, otherwise the version of the
/// <see cref="CoreAssemblyName" /> reference it was compiled against. Null when neither is available -
/// such a plugin cannot be matched against <see cref="GrandVersion.SupportedPluginVersion" /> and is
/// therefore treated as incompatible.
/// </returns>
public static string ResolveSupportedVersion(Assembly pluginAssembly, string declaredVersion)
{
if (!string.IsNullOrWhiteSpace(declaredVersion))
return declaredVersion.Trim();

var coreReference = pluginAssembly?.GetReferencedAssemblies()
.FirstOrDefault(x => string.Equals(x.Name, CoreAssemblyName, StringComparison.OrdinalIgnoreCase));

return coreReference?.Version == null
? null
: $"{coreReference.Version.Major}.{coreReference.Version.Minor}";
}
}
4 changes: 4 additions & 0 deletions src/Modules/Grand.Module.Api/Grand.Module.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
<Private>False</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mediator\Grand.Mediator.csproj">
<Private>False</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Infrastructure\Grand.Infrastructure.csproj">
<Private>False</Private>
<ExcludeAssets>runtime</ExcludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
<ProjectReference Include="..\..\Core\Grand.Mapping\Grand.Mapping.csproj">
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mediator\Grand.Mediator.csproj">
<Private>False</Private>
</ProjectReference>
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
<ProjectReference Include="..\..\Core\Grand.Mapping\Grand.Mapping.csproj">
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mediator\Grand.Mediator.csproj">
<Private>False</Private>
</ProjectReference>
</ItemGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
<Private>False</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mediator\Grand.Mediator.csproj">
<Private>False</Private>
<ExcludeAssets>runtime</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Infrastructure\Grand.Infrastructure.csproj">
<Private>False</Private>
<ExcludeAssets>runtime</ExcludeAssets>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\..\Build\Grand.Common.props" />
<Import Project="..\..\Build\Grand.Plugin.props" />
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
Expand All @@ -19,37 +20,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.Facebook" />
</ItemGroup>

<Target Name="CopyFile" AfterTargets="AfterBuild">
<ItemGroup>
<CopyFiles Include="$(NuGetPackageRoot)\microsoft.aspnetcore.authentication.facebook\10.0.10\lib\net10.0\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(CopyFiles)" DestinationFolder="..\..\Web\Grand.Web\Plugins\Authentication.Facebook\" />
</Target>

<ItemGroup>
<ProjectReference Include="..\..\Core\Grand.Data\Grand.Data.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Domain\Grand.Domain.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mapping\Grand.Mapping.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Infrastructure\Grand.Infrastructure.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.SharedKernel\Grand.SharedKernel.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Web\Grand.Web.Common\Grand.Web.Common.csproj">
<Private>false</Private>
<ExcludeAssets>all</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="..\..\Business\Grand.Business.Core\Grand.Business.Core.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>

<ItemGroup>
<None Update="Assets\facebookstyles.css">
Expand Down
31 changes: 1 addition & 30 deletions src/Plugins/Authentication.Google/Authentication.Google.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\..\Build\Grand.Common.props" />
<Import Project="..\..\Build\Grand.Plugin.props" />
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
Expand All @@ -18,37 +19,7 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" />
</ItemGroup>

<Target Name="CopyFile" AfterTargets="AfterBuild">
<ItemGroup>
<CopyFiles Include="$(NuGetPackageRoot)\microsoft.aspnetcore.authentication.google\10.0.10\lib\net10.0\*.dll" />
</ItemGroup>
<Copy SourceFiles="@(CopyFiles)" DestinationFolder="..\..\Web\Grand.Web\Plugins\Authentication.Google\" />
</Target>

<ItemGroup>
<ProjectReference Include="..\..\Core\Grand.Data\Grand.Data.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mapping\Grand.Mapping.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Domain\Grand.Domain.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Infrastructure\Grand.Infrastructure.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.SharedKernel\Grand.SharedKernel.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Web\Grand.Web.Common\Grand.Web.Common.csproj">
<Private>false</Private>
<ExcludeAssets>all</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="..\..\Business\Grand.Business.Core\Grand.Business.Core.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>

<ItemGroup>
<None Update="Assets\googlestyles.css">
Expand Down
25 changes: 1 addition & 24 deletions src/Plugins/DiscountRules.Standard/DiscountRules.Standard.csproj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<Import Project="..\..\Build\Grand.Common.props" />
<Import Project="..\..\Build\Grand.Plugin.props" />
<PropertyGroup>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<StaticWebAssetsEnabled>false</StaticWebAssetsEnabled>
Expand All @@ -15,30 +16,6 @@
<OutDir>$(OutputPath)</OutDir>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Core\Grand.Data\Grand.Data.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Domain\Grand.Domain.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Mapping\Grand.Mapping.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.Infrastructure\Grand.Infrastructure.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Core\Grand.SharedKernel\Grand.SharedKernel.csproj">
<Private>false</Private>
</ProjectReference>
<ProjectReference Include="..\..\Web\Grand.Web.Common\Grand.Web.Common.csproj">
<Private>false</Private>
<ExcludeAssets>all</ExcludeAssets>
</ProjectReference>
<ProjectReference Include="..\..\Business\Grand.Business.Core\Grand.Business.Core.csproj">
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Update="logo.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
Expand Down
Loading
Loading