Skip to content
Draft
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
37 changes: 33 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,39 @@ jobs:
10.0.201

- name: Run build pipeline script
run: bash ./build/build.sh
shell: bash
run: |
set -euo pipefail
LEGACY_VERSION="${NBGV_SemVer2}"
UMBRACO18_VERSION="7.${LEGACY_VERSION#*.}"

ARTICULATE_PACKAGE_LANE=legacy \
ARTICULATE_PACKAGE_VERSION="$LEGACY_VERSION" \
PACK_SAMPLE_THEME=true \
bash ./build/build.sh

ARTICULATE_PACKAGE_LANE=umbraco18 \
ARTICULATE_PACKAGE_VERSION="$UMBRACO18_VERSION" \
PACK_SAMPLE_THEME=true \
bash ./build/build.sh

- name: Export package versions
shell: bash
run: |
set -euo pipefail
LEGACY_VERSION="${NBGV_SemVer2}"
UMBRACO18_VERSION="7.${LEGACY_VERSION#*.}"
echo "LEGACY_VERSION=$LEGACY_VERSION" >> "$GITHUB_ENV"
echo "UMBRACO18_VERSION=$UMBRACO18_VERSION" >> "$GITHUB_ENV"

- name: Upload v6 packages
uses: actions/upload-artifact@v7.0.1
with:
name: Articulate-v6-${{ env.LEGACY_VERSION }}
path: build/Release/legacy/Articulate.*

- name: Upload packages
- name: Upload v7 packages
uses: actions/upload-artifact@v7.0.1
with:
name: Articulate.${{ env.NBGV_SemVer2 }}
path: ${{ env.RELEASE_FOLDER }}/Articulate.*
name: Articulate-v7-${{ env.UMBRACO18_VERSION }}
path: build/Release/umbraco18/Articulate.*
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -529,3 +529,4 @@ scratch/
/src/Articulate.Tests/umbraco-package-schema.json
/src/Articulate.Tests/appsettings-schema.Umbraco.Cms.json
src/Articulate.Tests/appsettings-schema.json
/build/test-site-publish
223 changes: 207 additions & 16 deletions DEVELOP.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,213 @@ pnpm run generate:api
- `BUILD_CONFIGURATION=Debug` is the default for local builds; Release is the default in packaging flows.
- `ENABLE_CLIENT_BUILD=true` enables local TypeScript Back Office client builds.
- `PACK_SAMPLE_THEME=true` forces packing `Articulate.Theme.Sample`; local builds pack it by default, but CI skips it unless explicitly enabled.
- The scripts clean, restore, build, and pack the current Articulate projects for .NET 9 and .NET 10.
- The packable NuGet package is produced by `src/Articulate.Web/Articulate.Web.csproj` (`PackageId=Articulate`). Packages are written under `build/$(Configuration)` by default.
- If you change packaged runtime dependencies or client/static assets, regenerate the Docker inputs before validating source-built or Docker-based installs:
- `dotnet pack src/Articulate.Web/Articulate.Web.csproj -c Release`
- `dotnet pack src/Articulate.Theme.Sample/Articulate.Theme.Sample.csproj -c Release`
- Keep `Articulate` and `Articulate.Theme.Sample` at the same package version in `build/Release`; the Docker site restores both using the selected Articulate package version.
- The Dockerfile selects the newest `Articulate.[0-9]*.nupkg` in `build/Release` by modified time and ignores `.snupkg` files and theme packages when choosing the version.
- Rebuilding the image is not enough on its own. A running Compose service can remain on an older image/container. Use `docker compose up -d --build --force-recreate articulate`, or run both steps explicitly:
- `docker compose build articulate`
- `docker compose up -d --force-recreate --no-deps articulate`
- The expected image tag is `articulate-local:net10`; the Compose container name will still be project/service based, for example `articulate-pr-articulate-1`.
- If the Docker back office still appears stale after a rebuild, check the running container, not just the image:
- `docker compose ps`
- `docker exec articulate-pr-articulate-1 /bin/sh -c "find /app -path '*App_Plugins/Articulate/BackOffice/articulate-backoffice.js' -o -path '*App_Plugins/Articulate/umbraco-package.json'"`
- `Invoke-WebRequest https://localhost:18443/App_Plugins/Articulate/BackOffice/articulate-backoffice.js -SkipCertificateCheck`
- The default unattended Docker backoffice user is `admin@localhost` with password `@rticulate` and display name `Jane Doe`. Override with `UMBRACO_USER_NAME`, `UMBRACO_USER_EMAIL`, and `UMBRACO_USER_PASSWORD` when needed.
- The scripts clean, restore, build, and pack one package lane at a time. The default lane is `legacy`.
- Running the local build script once does not produce both lanes; run it once with `ARTICULATE_PACKAGE_LANE=legacy` and once with `ARTICULATE_PACKAGE_LANE=umbraco18` when you need both NuGet package sets locally.
- The packable NuGet package is produced by `src/Articulate.Web/Articulate.Web.csproj` (`PackageId=Articulate`).
- Packages are written under `build/$(Configuration)/$(ARTICULATE_PACKAGE_LANE)`.

### Package Lanes

The source tree supports two package lanes:

| Lane | Package line | Umbraco support | Target frameworks | Output folder |
| --- | --- | --- | --- | --- |
| `legacy` | Articulate 6.x | Umbraco 16/17 | `net9.0`, `net10.0` | `build/Release/legacy` |
| `umbraco18` | Articulate 7.x | Umbraco 18 | `net10.0` | `build/Release/umbraco18` |

The lanes produce separate NuGet packages because the compiled Umbraco 17 and Umbraco 18 extension points are not binary-compatible. Do not install an Articulate 6 package into Umbraco 18, or an Articulate 7 package into Umbraco 16/17.

Build the Articulate 6 lane:

PowerShell:

```powershell
$env:ARTICULATE_PACKAGE_LANE='legacy'
$env:ARTICULATE_PACKAGE_VERSION='6.0.0-rc.2'
$env:PACK_SAMPLE_THEME='true'
./build/build.ps1
```

Bash:

```bash
ARTICULATE_PACKAGE_LANE=legacy \
ARTICULATE_PACKAGE_VERSION=6.0.0-rc.2 \
PACK_SAMPLE_THEME=true \
./build/build.sh
```

Build the Articulate 7 / Umbraco 18 lane:

PowerShell:

```powershell
$env:ARTICULATE_PACKAGE_LANE='umbraco18'
$env:ARTICULATE_PACKAGE_VERSION='7.0.0-rc.2'
$env:PACK_SAMPLE_THEME='true'
./build/build.ps1
```

Bash:

```bash
ARTICULATE_PACKAGE_LANE=umbraco18 \
ARTICULATE_PACKAGE_VERSION=7.0.0-rc.2 \
PACK_SAMPLE_THEME=true \
./build/build.sh
```

The build scripts temporarily patch `version.json` when `ARTICULATE_PACKAGE_VERSION` is set, then restore it before exiting. This keeps Nerdbank.GitVersioning as the source of package metadata while allowing one checkout to produce the Articulate 6 and 7 package lines.

## Local Docker Validation

Docker is a local validation tool. GitHub Actions builds package artifacts but does not run Docker.

### Quick smoke test

Build and smoke-test Docker images for any Umbraco target version using the provided script:

```powershell
# Windows PowerShell
.\scripts\docker-test.ps1 -Target umbraco16
.\scripts\docker-test.ps1 -Target umbraco17
.\scripts\docker-test.ps1 -Target umbraco18
.\scripts\docker-test.ps1 -Target all -Keep # leaves containers running for manual browsing
```

```bash
# Bash
./scripts/docker-test.sh umbraco16
./scripts/docker-test.sh umbraco17
./scripts/docker-test.sh umbraco18
./scripts/docker-test.sh all --keep # leaves containers running for manual browsing
```

The PowerShell and Bash Docker test scripts support the same targets:

| Target | Package lane | TFM | Umbraco version | Image tag | HTTPS port |
| --- | --- | --- | --- | --- | --- |
| `umbraco16` | `legacy` | `net9.0` | `[16.5.1,17.0.0)` | `articulate-local:umbraco16` | 16016 |
| `umbraco17` | `legacy` | `net10.0` | `[17.2.2,18.0.0)` | `articulate-local:umbraco17` | 17017 |
| `umbraco18` | `umbraco18` | `net10.0` | `18.0.0-rc*` | `articulate-local:umbraco18` | 18018 |

Each target builds a Docker image with the correct SDK, ASP.NET runtime, TFM, and Umbraco version range. The smoke test starts Umbraco behind a Caddy sidecar for TLS termination (required by Umbraco Production mode) and verifies both `/` and `/umbraco` respond with HTTP 200. Use `-Keep` in PowerShell or `--keep` in Bash to leave the scripted containers running for manual browser testing.

### Package prerequisites

The Docker test scripts check for both `Articulate.*.nupkg` and `Articulate.Theme.Sample.*.nupkg` in the required package lane. If either is missing, the script builds that lane with `PACK_SAMPLE_THEME=true`.

The package build scripts also assert that the main `Articulate` package contains the Material theme static web assets that previously regressed in the Umbraco 18 lane:

- `staticwebassets/App_Plugins/Articulate/Themes/Material/assets/dist/css/material.min.css`
- `staticwebassets/App_Plugins/Articulate/Themes/Material/assets/dist/js/material.min.js`
- `staticwebassets/App_Plugins/Articulate/Themes/Material/assets/vendor/mdl/material.min.js`
- `staticwebassets/App_Plugins/Articulate/Themes/Material/assets/vendor/mdl/material.pink-blue.min.css`

You can still build both lanes explicitly:

```powershell
$env:ARTICULATE_PACKAGE_LANE='legacy'
./build/build.ps1

$env:ARTICULATE_PACKAGE_LANE='umbraco18'
./build/build.ps1
```

```bash
ARTICULATE_PACKAGE_LANE=legacy ./build/build.sh
ARTICULATE_PACKAGE_LANE=umbraco18 ./build/build.sh
```

### HTTPS / Caddy

The smoke test runs each target behind Caddy for TLS. The default `Caddyfile` uses `tls internal`, which generates a local CA and server certificate. Browsers on Windows will show a certificate warning until the local CA is trusted.

To trust Caddy's root CA:

```powershell
powershell -ExecutionPolicy Bypass -File .\build\docker-site\Trust-CaddyRootCA.ps1
```

### HMAC image signing (Umbraco 17+)

Umbraco 17+ auto-generates an HMAC secret key on first boot. Image resize/crop URLs generated by `GetCropUrl()` include an `hmac` query parameter signed with this key. The server validates the HMAC before processing the image — requests without a valid HMAC return 400.

When rendering image URLs in `<style>` blocks, use the `ToCssStyleUrl()` extension method (returns `IHtmlContent`) instead of raw `@` expressions to prevent Razor from HTML-encoding the `&` query separator into `&amp;`:

```cshtml
@* Correct — & stays as & in the <style> block *@
background-image: url('@Model.BlogBanner.ToCssStyleUrl()');

@* Wrong — & becomes &amp; which the browser does not decode in raw text elements *@
background-image: url('@Model.BlogBanner);
```

### Manual browser testing

Use the Docker test scripts with their keep option for manual browser testing:

```powershell
.\scripts\docker-test.ps1 -Target umbraco18 -Keep
```

```bash
./scripts/docker-test.sh umbraco18 --keep
```

This keeps the Umbraco and Caddy containers alive on the target-specific HTTPS port shown above. Clean them up with `docker rm -f $(docker ps -q --filter name=art-)` when finished.

Default unattended backoffice credentials:
- Name: `Jane Doe`
- Email: `admin17@localhost` (legacy lane) or `admin18@localhost` (umbraco18 lane)
- Password: `@rticulate`

## Opt-in Umbraco 18 release-candidate validation (net10 only)

Default source validation lanes remain unchanged (`net9.0` => Umbraco 16, `net10.0` => Umbraco 17 stable).

Use explicit version pinning to validate Umbraco 18 RC locally.

OpenAPI note:

- Umbraco 16/17 lane uses legacy SwaggerGen/operation filter registration.
- Umbraco 18 lane uses native OpenAPI transformers for Articulate operation IDs and security requirements.

Baseline (`net10.0` + Umbraco 17 stable):

PowerShell:

```powershell
dotnet restore .\src\Articulate.sln -p:UmbracoCmsPackageVersion=17.2.2
dotnet build .\src\Articulate.sln -c Debug -f net10.0 --no-restore -p:UmbracoCmsPackageVersion=17.2.2
dotnet test .\src\Articulate.sln -c Debug -f net10.0 --no-build --no-restore -p:UmbracoCmsPackageVersion=17.2.2
```

Bash:

```bash
dotnet restore ./src/Articulate.sln -p:UmbracoCmsPackageVersion=17.2.2
dotnet build ./src/Articulate.sln -c Debug -f net10.0 --no-restore -p:UmbracoCmsPackageVersion=17.2.2
dotnet test ./src/Articulate.sln -c Debug -f net10.0 --no-build --no-restore -p:UmbracoCmsPackageVersion=17.2.2
```

PowerShell:

```powershell
dotnet restore .\src\Articulate.sln -p:UmbracoCmsPackageVersion=18.0.0-rc*
dotnet build .\src\Articulate.sln -c Debug -f net10.0 --no-restore -p:UmbracoCmsPackageVersion=18.0.0-rc*
dotnet test .\src\Articulate.sln -c Debug -f net10.0 --no-build --no-restore -p:UmbracoCmsPackageVersion=18.0.0-rc*
dotnet run -f net10.0 --project .\src\Articulate.Tests.Website\Articulate.Tests.Website.csproj -p:UmbracoCmsPackageVersion=18.0.0-rc*
```

Bash:

```bash
dotnet restore ./src/Articulate.sln -p:UmbracoCmsPackageVersion=18.0.0-rc*
dotnet build ./src/Articulate.sln -c Debug -f net10.0 --no-restore -p:UmbracoCmsPackageVersion=18.0.0-rc*
dotnet test ./src/Articulate.sln -c Debug -f net10.0 --no-build --no-restore -p:UmbracoCmsPackageVersion=18.0.0-rc*
dotnet run -f net10.0 --project ./src/Articulate.Tests.Website/Articulate.Tests.Website.csproj -p:UmbracoCmsPackageVersion=18.0.0-rc*
```

## Back Office Client Builds

Expand Down
40 changes: 19 additions & 21 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,22 @@
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
<!-- Support Umbraco 15.x-16.x on .NET 9 -->
<UmbracoCmsPackageVersion>[16.5.1,17.0.0)</UmbracoCmsPackageVersion>
<MicrosoftOpenApiPackageVersion>[1.6.22,2.0.0)</MicrosoftOpenApiPackageVersion>
<MicrosoftCodeAnalysisPackageVersion>[4.13.0,5.3.0)</MicrosoftCodeAnalysisPackageVersion>
<SystemServiceModelSyndicationPackageVersion>[9.0.14,10.0.3)</SystemServiceModelSyndicationPackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>[9.0.15,10.0.0)</SystemSecurityCryptographyXmlPackageVersion>
<MailKitPackageVersion>[4.16.0,5.0.0)</MailKitPackageVersion>
<OpenMcdfPackageVersion>3.1.3</OpenMcdfPackageVersion>
<!-- Package versions are managed centrally in Directory.Packages.props to keep build props KISS -->
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0'">
<!-- Support Umbraco 17.x (including pre-release) on .NET 10 -->
<UmbracoCmsPackageVersion>[17.2.2,18.0.0)</UmbracoCmsPackageVersion>
<MicrosoftOpenApiPackageVersion>[2.3.0,3.0.0)</MicrosoftOpenApiPackageVersion>
<MicrosoftCodeAnalysisPackageVersion>[5.3.0,6.0.0)</MicrosoftCodeAnalysisPackageVersion>
<SystemServiceModelSyndicationPackageVersion>[10.0.5,11.0.0)</SystemServiceModelSyndicationPackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>[10.0.6,11.0.0)</SystemSecurityCryptographyXmlPackageVersion>
<MailKitPackageVersion>[4.16.0,5.0.0)</MailKitPackageVersion>
<OpenMcdfPackageVersion>3.1.3</OpenMcdfPackageVersion>
<UmbracoCmsPackageVersion>[17.4.2,18.0.0)</UmbracoCmsPackageVersion>
<!-- Package versions are managed centrally in Directory.Packages.props to keep build props KISS -->
<MarkdigPackageVersion>[1.2.0,2.0.0)</MarkdigPackageVersion>
<MicrosoftNetTestSdkPackageVersion>[18.5.1,19.0.0)</MicrosoftNetTestSdkPackageVersion>
<!-- net10 needs higher syndication/crypto lower bounds to satisfy Umbraco 17+ transitive requirements -->
<SystemServiceModelSyndicationPackageVersion>[10.0.8,11.0.0)</SystemServiceModelSyndicationPackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>[10.0.7,11.0.0)</SystemSecurityCryptographyXmlPackageVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net10.0' and $([System.String]::Copy('$(UmbracoCmsPackageVersion)').StartsWith('18.'))">
<DefineConstants>$(DefineConstants);UMBRACO_18_OR_GREATER</DefineConstants>
</PropertyGroup>

<PropertyGroup>
<!-- TODO: Enable when 6.0 version is shipped -->
<EnablePackageValidation>false</EnablePackageValidation>
Expand Down Expand Up @@ -60,14 +59,6 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis" Version="$(MicrosoftCodeAnalysisPackageVersion)" PrivateAssets="All" />
</ItemGroup>
<ItemGroup Condition="'$(UseArticulateUsings)' == 'true'">
<Using Include="Umbraco.Cms.Core.DependencyInjection" />
<Using Include="Umbraco.Extensions" />
<Using Include="Articulate" />
<Using Include="Articulate.Models" />
</ItemGroup>
<ItemGroup Condition="'$(IsPackable)' != 'false'">
<None Include="$(MSBuildThisFileDirectory)LICENSE">
<Pack>true</Pack>
<PackagePath>\</PackagePath>
Expand All @@ -77,6 +68,13 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup Condition="'$(UseArticulateUsings)' == 'true'">
<Using Include="Umbraco.Cms.Core.DependencyInjection" />
<Using Include="Umbraco.Extensions" />
<Using Include="Articulate" />
<Using Include="Articulate.Models" />
</ItemGroup>

<PropertyGroup>
<GitVersionBaseDirectory>$(MSBuildThisFileDirectory)</GitVersionBaseDirectory>
</PropertyGroup>
Expand Down
21 changes: 21 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project>
<!-- Centralized package-version properties (kept KISS: wide ranges that satisfy current TFMs) -->
<PropertyGroup>
<!-- OpenAPI / OpenApi.NET ranges covering net9/net10 lanes -->
<MicrosoftOpenApiPackageVersion>[1.6.22,3.0.0)</MicrosoftOpenApiPackageVersion>

<!-- Roslyn/CodeAnalysis: allow 4.13+ to satisfy workspaces/msbuild transitive constraints -->
<MicrosoftCodeAnalysisPackageVersion>[4.13.0,6.0.0)</MicrosoftCodeAnalysisPackageVersion>

<!-- Syndication / Cryptography ranges covering both TFMs -->
<SystemServiceModelSyndicationPackageVersion>[9.0.16,11.0.0)</SystemServiceModelSyndicationPackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>[10.0.7,11.0.0)</SystemSecurityCryptographyXmlPackageVersion>

<!-- MailKit / Markdig / Test SDK -->
<MailKitPackageVersion>[4.16.0,5.0.0)</MailKitPackageVersion>
<MarkdigPackageVersion>[1.2.0,2.0.0)</MarkdigPackageVersion>
<OpenMcdfPackageVersion>[3.1.4,4.0.0)</OpenMcdfPackageVersion>
<MicrosoftNetTestSdkPackageVersion>[18.5.1,19.0.0)</MicrosoftNetTestSdkPackageVersion>
</PropertyGroup>
</Project>
Loading
Loading