Skip to content

Commit cd00dfa

Browse files
committed
Merge branch 'main' into v17/dev
2 parents 6d5271f + cebfb21 commit cd00dfa

File tree

137 files changed

+4636
-371
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+4636
-371
lines changed

.github/copilot-instructions.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# Umbraco CMS Development Guide
2+
3+
Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
4+
5+
## Working Effectively
6+
7+
Bootstrap, build, and test the repository:
8+
9+
- Install .NET SDK (version specified in global.json):
10+
- `curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version $(jq -r '.sdk.version' global.json)`
11+
- `export PATH="/home/runner/.dotnet:$PATH"`
12+
- Install Node.js (version specified in src/Umbraco.Web.UI.Client/.nvmrc):
13+
- `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash`
14+
- `export NVM_DIR="$HOME/.nvm" && [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"`
15+
- `nvm install $(cat src/Umbraco.Web.UI.Client/.nvmrc) && nvm use $(cat src/Umbraco.Web.UI.Client/.nvmrc)`
16+
- Fix shallow clone issue (required for GitVersioning):
17+
- `git fetch --unshallow`
18+
- Restore packages:
19+
- `dotnet restore` -- takes 50 seconds. NEVER CANCEL. Set timeout to 90+ seconds.
20+
- Build the solution:
21+
- `dotnet build` -- takes 4.5 minutes. NEVER CANCEL. Set timeout to 10+ minutes.
22+
- Install and build frontend:
23+
- `cd src/Umbraco.Web.UI.Client`
24+
- `npm ci --no-fund --no-audit --prefer-offline` -- takes 11 seconds.
25+
- `npm run build:for:cms` -- takes 1.25 minutes. NEVER CANCEL. Set timeout to 5+ minutes.
26+
- Install and build Login
27+
- `cd src/Umbraco.Web.UI.Login`
28+
- `npm ci --no-fund --no-audit --prefer-offline`
29+
- `npm run build`
30+
- Run the application:
31+
- `cd src/Umbraco.Web.UI`
32+
- `dotnet run --no-build` -- Application runs on https://localhost:44339 and http://localhost:11000
33+
34+
## Validation
35+
36+
- ALWAYS run through at least one complete end-to-end scenario after making changes.
37+
- Build and unit tests must pass before committing changes.
38+
- Frontend build produces output in src/Umbraco.Web.UI.Client/dist-cms/ which gets copied to src/Umbraco.Web.UI/wwwroot/umbraco/backoffice/
39+
- Always run `dotnet build` and `npm run build:for:cms` before running the application to see your changes.
40+
- For login-only changes, you can run `npm run build` from src/Umbraco.Web.UI.Login and then `dotnet run --no-build` from src/Umbraco.Web.UI.
41+
- For frontend-only changes, you can run `npm run dev:server` from src/Umbraco.Web.UI.Client for hot reloading.
42+
- Frontend changes should be linted using `npm run lint:fix` which uses Eslint.
43+
44+
## Testing
45+
46+
### Unit Tests (.NET)
47+
- Location: tests/Umbraco.Tests.UnitTests/
48+
- Run: `dotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj --configuration Release --verbosity minimal`
49+
- Duration: ~1 minute with 3,343 tests
50+
- NEVER CANCEL: Set timeout to 5+ minutes
51+
52+
### Integration Tests (.NET)
53+
- Location: tests/Umbraco.Tests.Integration/
54+
- Run: `dotnet test tests/Umbraco.Tests.Integration/Umbraco.Tests.Integration.csproj --configuration Release --verbosity minimal`
55+
- NEVER CANCEL: Set timeout to 10+ minutes
56+
57+
### Frontend Tests
58+
- Location: src/Umbraco.Web.UI.Client/
59+
- Run: `npm test` (requires `npx playwright install` first)
60+
- Frontend tests use Web Test Runner with Playwright
61+
62+
### Acceptance Tests (E2E)
63+
- Location: tests/Umbraco.Tests.AcceptanceTest/
64+
- Requires running Umbraco application and configuration
65+
- See tests/Umbraco.Tests.AcceptanceTest/README.md for detailed setup (requires `npx playwright install` first)
66+
67+
## Project Structure
68+
69+
The solution contains 30 C# projects organized as follows:
70+
71+
### Main Application Projects
72+
- **Umbraco.Web.UI**: Main web application project (startup project)
73+
- **Umbraco.Web.UI.Client**: TypeScript frontend (backoffice)
74+
- **Umbraco.Web.UI.Login**: Separate login screen frontend
75+
- **Umbraco.Core**: Core domain models and interfaces
76+
- **Umbraco.Infrastructure**: Data access and infrastructure
77+
- **Umbraco.Cms**: Main CMS package
78+
79+
### API Projects
80+
- **Umbraco.Cms.Api.Management**: Management API
81+
- **Umbraco.Cms.Api.Delivery**: Content Delivery API
82+
- **Umbraco.Cms.Api.Common**: Shared API components
83+
84+
### Persistence Projects
85+
- **Umbraco.Cms.Persistence.SqlServer**: SQL Server support
86+
- **Umbraco.Cms.Persistence.Sqlite**: SQLite support
87+
- **Umbraco.Cms.Persistence.EFCore**: Entity Framework Core abstractions
88+
89+
### Test Projects
90+
- **Umbraco.Tests.UnitTests**: Unit tests
91+
- **Umbraco.Tests.Integration**: Integration tests
92+
- **Umbraco.Tests.AcceptanceTest**: End-to-end tests with Playwright
93+
- **Umbraco.Tests.Common**: Shared test utilities
94+
95+
## Common Tasks
96+
97+
### Frontend Development
98+
For frontend-only changes:
99+
1. Configure backend for frontend development:
100+
```json
101+
<!-- Add to src/Umbraco.Web.UI/appsettings.json under Umbraco:Cms:Security: -->
102+
```json
103+
"BackOfficeHost": "http://localhost:5173",
104+
"AuthorizeCallbackPathName": "/oauth_complete",
105+
"AuthorizeCallbackLogoutPathName": "/logout",
106+
"AuthorizeCallbackErrorPathName": "/error"
107+
```
108+
2. Run backend: `cd src/Umbraco.Web.UI && dotnet run --no-build`
109+
3. Run frontend dev server: `cd src/Umbraco.Web.UI.Client && npm run dev:server`
110+
111+
### Backend-Only Development
112+
For backend-only changes, disable frontend builds:
113+
- Comment out the target named "BuildStaticAssetsPreconditions" in src/Umbraco.Cms.StaticAssets.csproj:
114+
```
115+
<!--<Target Name="BuildStaticAssetsPreconditions" BeforeTargets="AssignTargetPaths">
116+
[...]
117+
</Target>-->
118+
```
119+
- Remember to uncomment before committing
120+
121+
### Building NuGet Packages
122+
To build custom NuGet packages for testing:
123+
```bash
124+
dotnet pack -c Release -o Build.Out
125+
dotnet nuget add source [Path to Build.Out folder] -n MyLocalFeed
126+
```
127+
128+
### Regenerating Frontend API Types
129+
When changing Management API:
130+
```bash
131+
cd src/Umbraco.Web.UI.Client
132+
npm run generate:server-api-dev
133+
```
134+
Also update OpenApi.json from /umbraco/swagger/management/swagger.json
135+
136+
## Database Setup
137+
138+
Default configuration supports SQLite for development. For production-like testing:
139+
- Use SQL Server/LocalDb for better performance
140+
- Configure connection string in src/Umbraco.Web.UI/appsettings.json
141+
142+
## Clean Up / Reset
143+
144+
To reset development environment:
145+
```bash
146+
# Remove configuration and database
147+
rm src/Umbraco.Web.UI/appsettings.json
148+
rm -rf src/Umbraco.Web.UI/umbraco/Data
149+
150+
# Full clean (removes all untracked files)
151+
git clean -xdf .
152+
```
153+
154+
## Version Information
155+
156+
- Target Framework: .NET (version specified in global.json)
157+
- Current Version: (specified in version.json)
158+
- Node.js Requirement: (specified in src/Umbraco.Web.UI.Client/.nvmrc)
159+
- npm Requirement: Latest compatible version
160+
161+
## Known Issues
162+
163+
- Build requires full git history (not shallow clone) due to GitVersioning
164+
- Some NuGet package security warnings are expected (SixLabors.ImageSharp vulnerabilities)
165+
- Frontend tests require Playwright browser installation: `npx playwright install`
166+
- Older Node.js versions may show engine compatibility warnings (check .nvmrc for current requirement)
167+
168+
## Timing Expectations
169+
170+
**NEVER CANCEL** these operations - they are expected to take time:
171+
172+
| Operation | Expected Time | Timeout Setting |
173+
|-----------|--------------|-----------------|
174+
| `dotnet restore` | 50 seconds | 90+ seconds |
175+
| `dotnet build` | 4.5 minutes | 10+ minutes |
176+
| `npm ci` | 11 seconds | 30+ seconds |
177+
| `npm run build:for:cms` | 1.25 minutes | 5+ minutes |
178+
| `npm test` | 2 minutes | 5+ minutes |
179+
| `npm run lint` | 1 minute | 5+ minutes |
180+
| Unit tests | 1 minute | 5+ minutes |
181+
| Integration tests | Variable | 10+ minutes |
182+
183+
Always wait for commands to complete rather than canceling and retrying.

src/Umbraco.Cms.Api.Management/Controllers/Content/ContentCollectionControllerBase.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using Microsoft.AspNetCore.Http;
22
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.DependencyInjection;
34
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
5+
using Umbraco.Cms.Api.Management.Services.Signs;
46
using Umbraco.Cms.Api.Management.ViewModels.Content;
7+
using Umbraco.Cms.Core.DependencyInjection;
58
using Umbraco.Cms.Core.Mapping;
69
using Umbraco.Cms.Core.Models;
710
using Umbraco.Cms.Core.Models.ContentEditing;
@@ -18,8 +21,19 @@ public abstract class ContentCollectionControllerBase<TContent, TCollectionRespo
1821
where TVariantResponseModel : VariantResponseModelBase
1922
{
2023
private readonly IUmbracoMapper _mapper;
24+
private readonly SignProviderCollection _signProviders;
2125

22-
protected ContentCollectionControllerBase(IUmbracoMapper mapper) => _mapper = mapper;
26+
protected ContentCollectionControllerBase(IUmbracoMapper mapper, SignProviderCollection signProvider)
27+
{
28+
_mapper = mapper;
29+
_signProviders = signProvider;
30+
}
31+
32+
[Obsolete("Use the constructer with all parameters. To be removed in Umbraco 18")]
33+
protected ContentCollectionControllerBase(IUmbracoMapper mapper)
34+
: this(mapper, StaticServiceProvider.Instance.GetRequiredService<SignProviderCollection>())
35+
{
36+
}
2337

2438
[Obsolete("This method is no longer used and will be removed in Umbraco 17.")]
2539
protected IActionResult CollectionResult(ListViewPagedModel<TContent> result)
@@ -48,6 +62,9 @@ protected IActionResult CollectionResult(ListViewPagedModel<TContent> result)
4862
return Ok(pageViewModel);
4963
}
5064

65+
/// <summary>
66+
/// Creates a collection result from the provided collection response models and total number of items.
67+
/// </summary>
5168
protected IActionResult CollectionResult(List<TCollectionResponseModel> collectionResponseModels, long totalNumberOfItems)
5269
{
5370
var pageViewModel = new PagedViewModel<TCollectionResponseModel>
@@ -104,4 +121,15 @@ protected IActionResult ContentCollectionOperationStatusResult(ContentCollection
104121
StatusCode = StatusCodes.Status500InternalServerError,
105122
},
106123
});
124+
125+
/// <summary>
126+
/// Populates the signs for the collection response models.
127+
/// </summary>
128+
protected async Task PopulateSigns(IEnumerable<TCollectionResponseModel> itemViewModels)
129+
{
130+
foreach (ISignProvider signProvider in _signProviders.Where(x => x.CanProvideSigns<TCollectionResponseModel>()))
131+
{
132+
await signProvider.PopulateSignsAsync(itemViewModels);
133+
}
134+
}
107135
}

src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/AncestorsDataTypeTreeController.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Asp.Versioning;
22
using Microsoft.AspNetCore.Http;
33
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Umbraco.Cms.Api.Management.Services.Signs;
46
using Umbraco.Cms.Api.Management.ViewModels.Tree;
57
using Umbraco.Cms.Core.Services;
68

@@ -9,11 +11,18 @@ namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
911
[ApiVersion("1.0")]
1012
public class AncestorsDataTypeTreeController : DataTypeTreeControllerBase
1113
{
14+
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
1215
public AncestorsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
1316
: base(entityService, dataTypeService)
1417
{
1518
}
1619

20+
[ActivatorUtilitiesConstructor]
21+
public AncestorsDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService)
22+
: base(entityService, signProviders, dataTypeService)
23+
{
24+
}
25+
1726
[HttpGet("ancestors")]
1827
[MapToApiVersion("1.0")]
1928
[ProducesResponseType(typeof(IEnumerable<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]

src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/ChildrenDataTypeTreeController.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
1-
using Asp.Versioning;
1+
using Asp.Versioning;
22
using Microsoft.AspNetCore.Http;
33
using Microsoft.AspNetCore.Mvc;
44
using Umbraco.Cms.Core.Services;
55
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
66
using Umbraco.Cms.Api.Management.ViewModels.Tree;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Umbraco.Cms.Api.Management.Services.Signs;
79

810
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
911

1012
[ApiVersion("1.0")]
1113
public class ChildrenDataTypeTreeController : DataTypeTreeControllerBase
1214
{
15+
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
1316
public ChildrenDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
1417
: base(entityService, dataTypeService)
1518
{
1619
}
1720

21+
[ActivatorUtilitiesConstructor]
22+
public ChildrenDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService)
23+
: base(entityService, signProviders, dataTypeService)
24+
{
25+
}
26+
1827
[HttpGet("children")]
1928
[MapToApiVersion("1.0")]
2029
[ProducesResponseType(typeof(PagedViewModel<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]

src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/DataTypeTreeControllerBase.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
using Microsoft.AspNetCore.Authorization;
1+
using Microsoft.AspNetCore.Authorization;
22
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.DependencyInjection;
34
using Umbraco.Cms.Api.Management.Controllers.Tree;
45
using Umbraco.Cms.Api.Management.Routing;
6+
using Umbraco.Cms.Api.Management.Services.Signs;
57
using Umbraco.Cms.Api.Management.ViewModels.Tree;
68
using Umbraco.Cms.Core;
9+
using Umbraco.Cms.Core.DependencyInjection;
710
using Umbraco.Cms.Core.Models;
811
using Umbraco.Cms.Core.Models.Entities;
912
using Umbraco.Cms.Core.Services;
@@ -19,8 +22,17 @@ public class DataTypeTreeControllerBase : FolderTreeControllerBase<DataTypeTreeI
1922
{
2023
private readonly IDataTypeService _dataTypeService;
2124

25+
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
2226
public DataTypeTreeControllerBase(IEntityService entityService, IDataTypeService dataTypeService)
23-
: base(entityService) =>
27+
: this(
28+
entityService,
29+
StaticServiceProvider.Instance.GetRequiredService<SignProviderCollection>(),
30+
dataTypeService)
31+
{
32+
}
33+
34+
public DataTypeTreeControllerBase(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService)
35+
: base(entityService, signProviders) =>
2436
_dataTypeService = dataTypeService;
2537

2638
protected override UmbracoObjectTypes ItemObjectType => UmbracoObjectTypes.DataType;

src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/RootDataTypeTreeController.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
1-
using Asp.Versioning;
1+
using Asp.Versioning;
22
using Microsoft.AspNetCore.Http;
33
using Microsoft.AspNetCore.Mvc;
44
using Umbraco.Cms.Core.Services;
55
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
6-
using Umbraco.Cms.Api.Management.ViewModels.DataType.Item;
76
using Umbraco.Cms.Api.Management.ViewModels.Tree;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Umbraco.Cms.Api.Management.Services.Signs;
89

910
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
1011

1112
[ApiVersion("1.0")]
1213
public class RootDataTypeTreeController : DataTypeTreeControllerBase
1314
{
15+
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
1416
public RootDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
1517
: base(entityService, dataTypeService)
1618
{
1719
}
1820

21+
[ActivatorUtilitiesConstructor]
22+
public RootDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService)
23+
: base(entityService, signProviders, dataTypeService)
24+
{
25+
}
26+
1927
[HttpGet("root")]
2028
[MapToApiVersion("1.0")]
2129
[ProducesResponseType(typeof(PagedViewModel<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]

src/Umbraco.Cms.Api.Management/Controllers/DataType/Tree/SiblingsDataTypeTreeController.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,27 @@
11
using Microsoft.AspNetCore.Http;
22
using Microsoft.AspNetCore.Mvc;
3+
using Microsoft.Extensions.DependencyInjection;
34
using Umbraco.Cms.Api.Common.ViewModels.Pagination;
5+
using Umbraco.Cms.Api.Management.Services.Signs;
46
using Umbraco.Cms.Api.Management.ViewModels.Tree;
57
using Umbraco.Cms.Core.Services;
68

79
namespace Umbraco.Cms.Api.Management.Controllers.DataType.Tree;
810

911
public class SiblingsDataTypeTreeController : DataTypeTreeControllerBase
1012
{
13+
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 18.")]
1114
public SiblingsDataTypeTreeController(IEntityService entityService, IDataTypeService dataTypeService)
1215
: base(entityService, dataTypeService)
1316
{
1417
}
1518

19+
[ActivatorUtilitiesConstructor]
20+
public SiblingsDataTypeTreeController(IEntityService entityService, SignProviderCollection signProviders, IDataTypeService dataTypeService)
21+
: base(entityService, signProviders, dataTypeService)
22+
{
23+
}
24+
1625
[HttpGet("siblings")]
1726
[ProducesResponseType(typeof(SubsetViewModel<DataTypeTreeItemResponseModel>), StatusCodes.Status200OK)]
1827
public async Task<ActionResult<SubsetViewModel<DataTypeTreeItemResponseModel>>> Siblings(CancellationToken cancellationToken, Guid target, int before, int after, bool foldersOnly = false)

0 commit comments

Comments
 (0)