Merge v17/main into v18/main (2026-07-11) - #992
Merged
Conversation
* 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)
…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>
…lures uSync.Extend is excluded from the Debug solution configuration in uSync.slnx (<Build Solution="Debug|*" Project="false" />). The restore step added previously (dotnet restore $sln_name --force-evaluate) had no configuration, so it defaulted to Debug and silently skipped restoring uSync.Extend - its lock file stayed stale while every other project's got fixed. The later build/pack steps run with --no-restore in $env (release), so they picked up the stale lock file, which manifested as a NU1903 audit failure specifically on uSync.Extend when packing. Pass -p:Configuration=$env to the restore so it matches the build/pack configuration and actually restores every project, including uSync.Extend. Also commit the corrected uSync.Extend/packages.lock.json produced by that restore, so the repo isn't left stale for the next person to hit this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.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>
Brings 24 commits from v17/main into v18. Notable integrations where the two branches had diverged: - Blueprint path/parent resolution (#985): ported v17's auto-create of missing DocumentBlueprintContainer folders into v18's SyncParentItem / FindItemAsParent model (CreateParentIfMissingAsync now returns SyncParentItem and is wired into GetParentPathAndLevelAsync; the blueprint serializer overrides FindItemAsParent to resolve containers). - ContentHandler: v17's GetPagedChildren(loadTemplates) fix is superseded by v18's PublishableContentHandlerBase refactor - kept v18. - ApiComposer: kept v18's AddSyncOpenApi() (replaces the v17 IOperationIdHandler/Swagger registrations). - Backoffice-enabled gate: kept v18's behaviour (core always registers, client/manifest gated) - see note in PR for review. - UI: combined v18's action/progress box layout with v17's termOrDefault localization (5 new languages) and applied termOrDefault to the v18 action-button render helpers. - Dependencies/lock files: kept v18 versions; v17 dependabot bumps are superseded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3 tasks
KevinJump
added a commit
that referenced
this pull request
Jul 28, 2026
… merge (#1012) An audit of the v17/main -> v18/main squash-merge (#992) found two v17 fixes that didn't survive the port: - uSyncBackOfficeComposer no longer gated uSync registration behind IsUmbracoBackOfficeEnabled(), so it would run on headless/delivery-only servers where it shouldn't (v17 #962). - ContentHandler lost its concrete-typed paged child fetching when child fetching was centralized into PublishableContentHandlerBase, causing an extra contentService.GetById per descendant item during export/import (v17 #990). MediaHandler already avoids this by overriding GetChildItemsAsync directly; ContentHandler now mirrors that pattern. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the latest 24 commits from
v17/mainintov18/main. Most changes auto-merged cleanly; this PR write-up focuses on the conflicts that needed manual/semantic resolution so reviewers can sanity-check the judgement calls.What's included from v17
Perf work (span-based JSON compare/hashing,
TryGetValueAsconversions, streamed clean-file keys, per-operation handler cache), the blueprint path/parent fix (#985), image-upload mapper, block encoding, "write clean files when folder has no children" (#964), localization fallback + 5 new UI languages (#972), and the dependabot bumps.Conflict resolutions (please review)
DocumentBlueprintContainerfolders using anITreeEntity/FindItemAsTreeEntityAsyncmodel; v18 had refactored parent resolution ontoSyncParentItem/FindItemAsParent/GetParentPathAndLevelAsync. Ported v17's feature into v18's model:CreateParentIfMissingAsyncnow returnsSyncParentItem?and is wired in as the last-chance fallback inGetParentPathAndLevelAsync;ContentTemplateSerializeroverridesFindItemAsParent+CreateParentIfMissingAsyncand convertsEntityContainer→SyncParentItem.GetPagedChildren(loadTemplates:true)fix targeted a method v18 removed when it moved child-paging intoPublishableContentHandlerBase. Kept v18's structure (v17 change is moot).builder.AddSyncOpenApi()(newAddBackOfficeOpenApiDocumentAPI) over v17'sIOperationIdHandler+ConfigSyncApiSwaggerGenOptionsregistrations.AdduSync()) behindIsUmbracoBackOfficeEnabled(). v18 deliberately does not gate core (explicit code comment: core runs even with no backoffice) and only gates the client/manifest registration. Kept v18's behaviour. This is the one intentional-divergence call worth a second look — if v17's "don't register anything on front-end-only servers" behaviour is desired for v18, say so.termOrDefaultlocalization of group names, and appliedtermOrDefaultto v18's refactoredusync-action-buttonrender helpers.render()refactor; carried v17's localization into it.Cleanpackage (unreferenced) dropped as in v18.Verification
dotnet build uSync.slnx -c Release— succeedsnpm run build(usync-assets, tsc + vite) — succeeds; all 5 new language bundles emitpackage.json🤖 Generated with Claude Code