@decocms/blocks 7.20.6 → 7.20.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks",
3
- "version": "7.20.6",
3
+ "version": "7.20.7",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -237,3 +237,24 @@ describe("composeMeta framework option", () => {
237
237
  expect(meta.manifest.blocks.pages).toHaveProperty("website/pages/Page.tsx");
238
238
  });
239
239
  });
240
+
241
+ describe("composeMeta idempotency", () => {
242
+ it("returns an already-composed meta unchanged (no double-compose)", () => {
243
+ const once = composeMeta(emptySiteMeta());
244
+ const twice = composeMeta(once);
245
+ // The sentinel is the top-level `framework` field, which only composeMeta
246
+ // sets — re-composing must be a no-op, returning the same object.
247
+ expect(twice).toBe(once);
248
+ });
249
+
250
+ it("does not duplicate framework section refs when re-composed", () => {
251
+ // Simulate the tanstack runtime path: a self-contained meta.gen.json (baked
252
+ // at gen time) is loaded from disk and composeMeta runs again on top of it.
253
+ const composed = composeMeta(emptySiteMeta());
254
+ const sectionsAnyOf = composed.schema.root.sections.anyOf;
255
+ const recomposed = composeMeta(composed);
256
+ // Without the guard, framework section refs (Lazy/Seo/…) would be appended
257
+ // to root.sections.anyOf a second time.
258
+ expect(recomposed.schema.root.sections.anyOf).toHaveLength(sectionsAnyOf.length);
259
+ });
260
+ });
package/src/cms/schema.ts CHANGED
@@ -1007,6 +1007,20 @@ export function composeMeta(
1007
1007
  siteMeta: MetaResponse,
1008
1008
  options?: ComposeMetaOptions,
1009
1009
  ): MetaResponse {
1010
+ // Idempotency guard. composeMeta is NOT structurally idempotent — it appends
1011
+ // the framework section refs to `root.sections.anyOf` (and `__SECTION_REF__`),
1012
+ // so composing an already-composed meta a second time duplicates those refs.
1013
+ // Since self-contained meta.gen.json is now baked at generation time (via the
1014
+ // CLI's --compose), the tanstack runtime re-runs composeMeta on the file it
1015
+ // loads from disk. A composed meta always carries a top-level `framework`
1016
+ // field, which raw generateMeta() output never sets — use it as the sentinel
1017
+ // and return the already-composed meta unchanged. Raw metas (framework unset,
1018
+ // e.g. sites generated by an older CLI) still compose normally, so this stays
1019
+ // backward compatible.
1020
+ if (siteMeta.framework) {
1021
+ return siteMeta;
1022
+ }
1023
+
1010
1024
  const siteAnyOf = siteMeta.schema?.root?.sections?.anyOf || [];
1011
1025
 
1012
1026
  // Build all framework components