Skip to content

Record v17/main as merged into v18/main (baseline, no changes) - #1104

Merged
KevinJump merged 109 commits into
v18/mainfrom
merge/v17-to-v18-baseline-2026-09-30
Sep 30, 2026
Merged

KevinJump merged 109 commits into
v18/mainfrom
merge/v17-to-v18-baseline-2026-09-30

Conversation

@KevinJump

Copy link
Copy Markdown
Owner

Summary

A git merge -s ours origin/v17/main onto v18/main, with no file changes. It records v17/main (up to e89bd81, #1102) as merged, so git has a real baseline between the two lines again.

Earlier forward merges were squashed, which left the merge base between v17/main and v18/main at 17.3.2 (May 2026). A straight merge therefore conflicted in 45 files, and each forward-port had to work out its baseline from PR titles. After this, git merge origin/v17/main on v18 brings only new v17 work.

What this declares as merged: everything on v17/main up to e89bd81. Nearly all of it has reached v18 through the forward ports up to #1103, or v18 has it under its own PR number. #1082 and #1096 were left out on purpose. The older dependabot bumps weren't checked one by one, since v18 keeps its own lockfiles and dependabot. If any of those matter they need porting by hand, because a merge won't bring them now.

Checks

  • git diff origin/v18/main HEAD is empty
  • git merge-base HEAD origin/v17/main = e89bd81 (the v17 tip)
  • git rev-list HEAD..origin/v17/main is empty

⚠️ This must be merged with a merge commit. A squash or rebase would throw away the second parent and undo the whole point.

🤖 Generated with Claude Code

KevinJump and others added 30 commits May 28, 2026 11:53
* update packages

* Split image upload and Image cropper mappers (to handle uploads in media slightly diffrently)

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* add missing xml comments

* add debug , so we can see which mapper we hit.

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ister anything for uSync. (only works on backoffice servers) (#962)
* revert the encoding of blocks from #955 - because that breaks rendering as per #958

* Add Null checks to health check (cause it can load without the services).

* v17.3.4 - package files.
…der has no children (so last items get deleted) (#964)
Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.0 to 3.1.2.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](fastify/fast-uri@v3.1.0...v3.1.2)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.8 to 8.5.14.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.8...8.5.14)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

Bumps [postcss](https://github.com/postcss/postcss) from 8.5.8 to 8.5.14.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.8...8.5.14)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
#960)

Bumps [uuid](https://github.com/uuidjs/uuid) to 14.0.0 and updates ancestor dependency [@umbraco-cms/backoffice](https://github.com/umbraco/Umbraco-CMS). These dependencies need to be updated together.


Updates `uuid` from 13.0.0 to 14.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](uuidjs/uuid@v13.0.0...v14.0.0)

Updates `@umbraco-cms/backoffice` from 17.3.0 to 17.5.0-rc
- [Release notes](https://github.com/umbraco/Umbraco-CMS/releases)
- [Commits](umbraco/Umbraco-CMS@release-17.3.0...release-17.5.0-rc)

---
updated-dependencies:
- dependency-name: "@umbraco-cms/backoffice"
  dependency-version: 18.0.0-rc1
  dependency-type: direct:development
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.5 to 8.0.16.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.5 to 8.0.16.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v8.0.16/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.0.16
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…971)

Bumps [markdown-it](https://github.com/markdown-it/markdown-it) from 14.1.1 to 14.2.0.
- [Changelog](https://github.com/markdown-it/markdown-it/blob/master/CHANGELOG.md)
- [Commits](markdown-it/markdown-it@14.1.1...14.2.0)

---
updated-dependencies:
- dependency-name: markdown-it
  dependency-version: 14.2.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…#974)

Removes [js-yaml](https://github.com/nodeca/js-yaml). It's no longer used after updating ancestor dependency [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts). These dependencies need to be updated together.


Removes `js-yaml`

Updates `@hey-api/openapi-ts` from 0.95.0 to 0.97.0
- [Release notes](https://github.com/hey-api/openapi-ts/releases)
- [Changelog](https://github.com/hey-api/hey-api/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hey-api/openapi-ts/compare/@hey-api/openapi-ts@0.95.0...@hey-api/openapi-ts@0.97.0)

---
updated-dependencies:
- dependency-name: "@hey-api/openapi-ts"
  dependency-version: 0.97.0
  dependency-type: direct:development
- dependency-name: js-yaml
  dependency-version:
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…rs (#985)

Content template (blueprint) exports stored the parent DocumentBlueprintContainer
as a raw, environment-specific int id in the friendly Path, and lost the parent
key entirely, because the lookups assumed the parent was always the same object
type as the item (DocumentBlueprint) rather than allowing for a container/folder.

- ContentSerializerBase: fall back to an untyped entity lookup for both the
  friendly path and the exported Parent key/name when the typed lookup fails,
  and add virtual hooks (FindItemAsTreeEntityAsync, CreateParentIfMissingAsync)
  so parents can be resolved/created as ITreeEntity rather than TObject.
- SyncTreeSerializerBase: CalculateNodePath/CalculateNodeLevel accept
  ITreeEntity? parents instead of TObject?, since TObject already implements it.
- ContentTemplateSerializer: resolve the parent as either an existing blueprint
  or its DocumentBlueprintContainer folder, and create the missing folder chain
  on import (same find-or-create-on-the-way pattern used for content/data types)
  instead of dropping the blueprint at the root or failing.

Fixes Jumoo/uSync.Complete.Issues#300.
…972)

* Add localization fallback defaults and translate UI into 5 languages

Replace all uSync-owned localization calls (`localize.term`) with
`localize.termOrDefault`, supplying the English string as a fallback so
the UI renders correctly even before the localization system has loaded.
Umbraco core keys (e.g. `general_close`) are left as plain `term` calls
since those are guaranteed to exist in every Umbraco installation.

Add translations for Danish (da-dk), French (fr-fr), Spanish (es-es),
German (de-de), and Dutch (nl-nl), covering all uSync and USyncSettings
keys. Register all five new locales in the lang manifest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add more localization changes.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Use stack allocated spans instead of heap allocated arrays

* avoid the last tiny heap allocation in the copyTo, TryWriteBytes will do this allocation free.

---------

Co-authored-by: Kevin Jump <kevin@thejumps.co.uk>
Two sources of swallowed first-chance InvalidCastExceptions during
push, ~100 each per run, that slow the app when a debugger is attached.

1. SyncEntityCache.GetName read from the entity cache (`cache`) while
   AddName wrote to `nameCache`. The entity cache holds IEntitySlim
   objects under the same id key, so every name lookup threw
   IEntitySlim -> CachedName and returned null - the name cache never
   actually worked. GetName now reads from nameCache.

2. Config/setting values arrive as JsonElement; Umbraco's TryConvertTo
   throws (and swallows) InvalidCastException turning a JsonElement into
   a value type e.g. bool. Added JsonTextExtensions.TryConvertPreChecked
   which does the JsonElement conversion with System.Text.Json first and
   only falls back to TryConvertTo. Routed the value/config wrappers
   (ConversionExtensions, SyncValueMapperBase, SyncSerializerOptions,
   HandlerSettingsExtensions) through it.

Adds tests for both fixes.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…eck) (#987)

* Merge JsonElement pre-check into existing TryGetValueAs

TryGetValueAs already did object -> T conversion with the same shape as
the new TryConvertPreChecked, so fold the JsonElement pre-check into it
(nicer name, one method) and use it everywhere.

TryGetValueAs is now public and skips the pre-check for string targets:
Umbraco's TryConvertTo already turns a JsonElement into a string without
throwing, and routing an object/array JsonElement through
Deserialize<string> would throw - reintroducing the exact first-chance
exception this change avoids. Its two existing string callers keep their
current behaviour.

Renames the test file to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Route remaining TryConvertTo calls through TryGetValueAs

Consolidates the object -> T conversions onto the single TryGetValueAs
method so they all get the JsonElement pre-check, and adds a non-generic
Type overload for the two callers that only have a runtime Type
(ContentTypeSerializer history-cleanup, ContentSerializerBase value diff).

Converted: XElementExtensions (value/attribute ValueOrDefault,
CreateOrSetElement), ObjectPropertyExtensions, ListExtensions,
JsonTextExtensions.GetPropertyValueOrDefault, MediaPicker3Mapper,
MemberGroupPickerManager, DomainSerializer, ContentTypeSerializer,
ContentTypeBaseSerializer.

Left ContentTypeBaseSerializer.SerializeNewProperty on TryConvertTo: it
writes an empty XElement when conversion succeeds with a null result, a
success-vs-null distinction TryGetValueAs deliberately collapses (null ->
false). Converting it would drop empty elements and cause spurious diffs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Route SerializeNewProperty through TryGetValueAs too

Converts the last object -> T conversion so TryConvertTo is only used in
one place (inside TryGetValueAs itself). TryGetValueAs treats a null
conversion result as failure, so the else branch keeps writing an empty
XElement - the property is still recorded in the xml, matching the old
success-with-null behaviour. The old hard-fail-writes-nothing case is
unreachable in practice here (reflected value-type properties return
boxed defaults, not null; string already took the empty path).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Two hot-path optimisations in XElementExtensions:

1. ValueOrDefault<TObject> (XElement + XAttribute overloads) now fast-paths
   the value types actually used (int, Guid, bool, enum) with direct parsers
   before falling back to Umbraco's reflection-based TryConvertTo. These
   getters run for (almost) every attribute of every node during a
   report/import, so avoiding the reflection/exception overhead adds up.
   The typeof(TObject) checks are JIT-folded per generic instantiation.

2. MakePlatformSafeHashAsync streams the XML straight through a CryptoStream
   instead of buffering the entire serialised document into a MemoryStream
   before hashing. The hashed byte sequence is unchanged, so hashes stay
   stable across platforms.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The 'clean' import path needs only the Key attribute for every item in a
folder, but was fully parsing each .config file (XElement.LoadAsync with
PreserveWhitespace) to read it. On large content/media trees this is a
second full file-IO + parse pass over the folder during every import that
contains clean markers.

Add ISyncFileService.LoadKeyFromFileAsync which streams the file with an
XmlReader, reads the Key attribute off the root element and stops - no DOM
allocation, no reading the rest of the document.

Wire it into the clean path:
 - SyncHandlerRoot.GetFolderKeysAsync (the per-folder key scan)
 - SyncHandlerRoot.GetCleanParentAsync
 - SyncHandlerBase.GetCleanParentKeyAsync, which also now reuses the key
   for the parent lookup instead of loading the clean file twice.

Behaviour is unchanged: the root Key attribute is present on both normal
and 'empty' (delete/rename/clean) nodes, files without a Key still yield
Guid.Empty, and read errors still throw so a corrupt file aborts the clean
rather than silently allowing deletes.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…+guid (#990)

Two ExportOnSave / bulk-operation performance fixes from the perf review.

#5 - runtime-cache scope (was keyed by managed thread id):
GetCacheKeyBase() built its key from Environment.CurrentManagedThreadId.
Across an await a continuation can resume on a different thread, so the
key changed mid-operation - the child-item / folder-key caches missed
(extra entityService calls) and the end-of-operation cleanup (on another
thread) never cleared the entries it created, leaking them into the shared
runtime cache.

Replace the thread id with an AsyncLocal<string> scope id that flows with
the async operation. PrepCaches/CleanCaches (import + report) and the
top-level ExportAllAsync now begin/end a scope via BeginCacheScope/
EndCacheScope, and ImportAllAsync/ReportAsync run the cleanup in a finally
so entries are always released. Callers with no active scope (paged import,
ad-hoc tree/dependency lookups) fall back to the thread id, so their
behaviour is unchanged.

#3 - skip CleanUp folder scan on flat + guid names:
On every save/move/delete, SyncHandlerRoot.CleanUpAsync recursively reads
the whole handler folder tree looking for duplicate files to mark as
renames. With a flat structure and guid file names an item's file is always
"{key}.{ext}" in the same folder - it never changes name or location - so
there is nothing to clean. Add an early return for that case to the base
handler (Content/Media already made this check in their override, this
extends it to the settings/level/container handlers).

Build clean; uSync.Tests 137/137 pass.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Findings doc (no code changes). Decompiled Umbraco 17.3 ContentService to
compare Save(item) vs Save(IEnumerable), and traced uSync's import scope
handling.

Conclusion: bulk Save is not a safe general win for content/media -
 - the batch overload still writes one row per item (no set-based SQL), so
   the dominant cost is unchanged;
 - its only saving (N->1 transactions + N->1 notifications) either is already
   provided by uSync's ambient suppressed scope (DisableNotificationSuppression
   = false), or, in the default config, directly conflicts with the per-item
   failure isolation that default is intentionally designed to give;
 - it also drops per-item error attribution and two validations.

Recommends leaving the (already-present but dormant) bulk hook off for
content/media and using the existing config levers instead.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Findings doc (no code changes). Decompiled Umbraco 17.3 ContentService to
compare Save(item) vs Save(IEnumerable), and traced uSync's import scope
handling.

Conclusion: bulk Save is not a safe general win for content/media -
 - the batch overload still writes one row per item (no set-based SQL), so
   the dominant cost is unchanged;
 - its only saving (N->1 transactions + N->1 notifications) either is already
   provided by uSync's ambient suppressed scope (DisableNotificationSuppression
   = false), or, in the default config, directly conflicts with the per-item
   failure isolation that default is intentionally designed to give;
 - it also drops per-item error attribution and two validations.

Recommends leaving the (already-present but dormant) bulk hook off for
content/media and using the existing config levers instead.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* Fix: move property out of group on import (#1009)

Importing a content type where a property has been moved out of all
groups (empty <Tab> and no matching <Tabs> entry) did nothing - the
property stayed in its original group.

DeserializePropertiesAsync only handled properties moving *into* a tab.
When the tab alias resolved to empty for an existing property, the else
branch fell through without action.

Now, when an existing property has no tab in the config but is still in a
group, it is queued to move to "no group" via MovePropertyType(alias,
null), which Umbraco treats as removing it from its current group.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix: re-home property into no-group so move persists in one import (#1009)

Moving a property out of all groups still needed two imports. Root cause:
Umbraco's MovePropertyType(alias, null) removes the property from its
group but does NOT add it to the 'no group' collection - it orphans the
property, so the first import's save doesn't persist the move.

MoveProperties now re-adds the property with AddPropertyType after the
move to null, which lands it in NoGroupPropertyTypes. Added unit tests
documenting the orphaning behaviour and verifying the workaround.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…ets (#999)

Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 2.0.3 to 2.1.2.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](juliangruber/brace-expansion@v2.0.3...v2.1.2)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 2.1.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…1007)

Bumps [linkify-it](https://github.com/markdown-it/linkify-it) from 5.0.1 to 5.0.2.
- [Changelog](https://github.com/markdown-it/linkify-it/blob/master/CHANGELOG.md)
- [Commits](markdown-it/linkify-it@5.0.1...5.0.2)

---
updated-dependencies:
- dependency-name: linkify-it
  dependency-version: 5.0.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
)

Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.2 to 3.1.4.
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](fastify/fast-uri@v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
KevinJump and others added 28 commits September 12, 2026 08:03
…check-v17

[AMEND] Make bad file name blocklist configurable
Use `ExportAtStartup` when startup export runs. Keep `ExportOnSave` as
the fallback when startup export is off and sync folders are absent.

Add SQLite-backed integration tests for startup group selection and the
fallback. The tests invoke the startup notification handler and inspect
language exports. The full suite passes 159/159.
Run `npm ci` and `npm run build` for the History client in the
release and prerelease workflows before dotnet packaging. This includes
the client assets in published `uSync.History` packages.
Add a Windows PR job for the History client.
Build the client and pack the project without publishing a package.

Check the entrypoint, file presence, sizes, and SHA-256 hashes.
Catch missing lazy-loaded chunks, empty files, truncation, and stale
content.
Avoid pinning generated chunk names or counts.
Add a known relation type to the startup group-precedence test and
assert its exported XML alongside the existing Language exclusion.
This checks that the Content handler runs, not just that the Settings
handler omits the Language export.
Format the History pack command as a folded YAML scalar while keeping
the command string unchanged.

Add spacing between setup steps and normalise GitHub expression spacing
in the restore and schema-generation commands.
Restore the upstream title-case names for the pull request workflow and
the schema generation step. This keeps unrelated renames out of the
History packaging change.
Add a test with startup export off, save export on, and an existing
sync folder. It asserts that startup exports no Language files.

The test fails if the first-run fallback drops its folder check.
uSync.Publisher's ExcludeContentTypes / IncludeContentTypes handler
settings were only applied when importing files - a 'clean' delete
missing items pass ignored them, so items never sent by a partial
sync (e.g. an excluded doc type) were treated as missing and deleted.

DeleteMissingItemsAsync now runs each delete candidate through the
same ShouldImportAsync rules used on import, via a new
ShouldImportDeletedItemAsync helper that serializes the item and
checks it. Reporting and real deletes use the same check so a report
can no longer say an item 'will be deleted' when it actually won't.

New HandlerSettings-aware overloads of CleanFolderAsync and
DeleteMissingItemsAsync carry the config through; the old overloads
are marked Obsolete (removal in v20) rather than changed, to avoid
breaking existing overrides.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
fix(startup): honour the ExportAtStartup group
fix(history): build client assets before packaging
Respect handler import rules when cleaning missing items
v17's and v18's CI had drifted independently: action version pins,
dotnet SDK pinning method, docs paths-ignore, the restore config fix,
and (in prerelease.yml) the nightly pipeline's job structure and
version scheme. Adopt v18's current shape wholesale so the two only
differ by branch name, per the standing exception that repo/CI files
travel backward (v18 -> v17) rather than only forward.

- dotnet-build.yml / release.yml: actions/checkout@v7,
  setup-dotnet@v6 with global-json-file (was a hardcoded
  dotnet-version), setup-node@v7, docs/**.md paths-ignore, the
  restore -p:Configuration fix, and building the History client in
  the main build-project job too (previously only in the standalone
  smoke-check job).
- prerelease.yml: split into package-up (packs, uploads a build
  artifact) and publish-nightly (downloads it, pushes to the feed)
  instead of one job doing both; packaging switched from a
  PowerShell loop to the same named per-project dotnet pack steps
  release.yml uses; nightly version gained a date component
  (majorMinorPatch-prerelease.{date}.{run_number}).
- global.json: pinned sdk.version (was empty) - global-json-file
  based dotnet pinning needs an actual version to pin, and the
  solution already targets net10.0 throughout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bring CI workflows in step with v18
An invariant block editor holding culture variant elements is re-serialized
by Umbraco on publish (a per culture merge), and the pending changes flag is
a string compare of the draft and published values. The draft uSync wrote
differed in three ways: editorAlias was null (PropertyType was never set),
properties were in alphabetical order, and values were not sorted by culture.

On import we now set each value's PropertyType, sort values by culture, and
serialize with Umbraco's property order. Export output is unchanged.

Tests run the import through Umbraco's own BlockListPropertyEditor merge and
compare the draft to the published value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
publish-nightly was limited to v17/main, so a prerelease run from a feature
branch built and packed but never reached the feed. The workflow is manual
only, so let any branch publish.

To keep branch builds distinguishable from main nightlies, non-main branches
are versioned "{version}-branch.{branch}.{date}.{run}" instead of
"{version}-prerelease.{date}.{run}". They sort below main nightlies, so a
floating prerelease range won't pick one up by accident.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
publish-nightly was limited to v17/main, so a prerelease run from a feature
branch built and packed but never reached the feed. The workflow is manual
only, so let any branch publish.

To keep branch builds distinguishable from main nightlies, non-main branches
are versioned "{version}-branch.{branch}.{date}.{run}" instead of
"{version}-prerelease.{date}.{run}". They sort below main nightlies, so a
floating prerelease range won't pick one up by accident.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Allow prerelease builds from any branch to publish to the nightly feed
Imported block values match what Umbraco publishes (#1097)
Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 2.1.4 to 2.1.7.
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](juliangruber/brace-expansion@v2.1.4...v2.1.7)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 2.1.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
…17.7

Umbraco 17.7 fixes MovePropertyType(alias, null) orphaning the property
(umbraco/Umbraco-CMS#23493), so the test asserting that bug would fail.
The re-home workaround stays for 17.3-17.6 and does nothing on 17.7.

The move logic is now an internal helper, and the tests call it rather
than a copy of it. Adds save-and-reload coverage for both directions.

Refs #1067

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A language stored as 'da-dk' exported as 'da-dk' before Umbraco 17.7.
From 17.7 (umbraco/Umbraco-CMS#23425) the name and published cultures
come back as 'da-DK' while property values stay 'da-dk', so files mixed
the two and changed on upgrade. Normalising in uSync gives the same
output on every 17.x version.

Tests also cover importing a culture in other casing, and that an
unknown or invalid culture fails as a normal uSync import failure.

Refs #1067

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Umbraco 17.7 readiness: property move tests and culture casing
….Backoffice.Management.Client/usync-assets/brace-expansion-2.1.7

Bump brace-expansion from 2.1.4 to 2.1.7 in /uSync.Backoffice.Management.Client/usync-assets
…braco 17.7

Two core bugs, fixed in 17.7, leave the published cache stale after a
uSync content type import on 17.3-17.6:

- Adding a composition only refreshed the type itself, so its children
  kept a published type without the new properties
  (umbraco/Umbraco-CMS#23433). After saving a composition change we now
  refresh the types composed of it.
- A content type loaded from the cache kept its property types' dirty
  state from creation, so a variance-only change was not treated as
  structural (umbraco/Umbraco-CMS#23445). We now clear that state when
  the item is loaded for import.

Both are harmless on 17.7. DistributedCache comes from
StaticServiceProvider so the serializer constructors don't change.

Refs #1067

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ounds

Keep the published cache current after content type imports before Umbraco 17.7
A "-s ours" merge: it keeps v18's tree exactly as it is and records
v17/main (up to e89bd81, #1102) as merged. Earlier forward merges were
squashed, which left the git merge base between the two lines at 17.3.2
(May 2026), so a normal merge conflicted in 45 files.

Every v17 change up to that point is on v18 already, either through a
forward port (latest #1103) or deliberately left out (#1082, #1096).
From now on `git merge origin/v17/main` only brings new v17 work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@KevinJump
KevinJump merged commit a731500 into v18/main Sep 30, 2026
@KevinJump
KevinJump deleted the merge/v17-to-v18-baseline-2026-09-30 branch September 30, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants