@decocms/blocks-admin 7.34.4 → 7.36.0

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-admin",
3
- "version": "7.34.4",
3
+ "version": "7.36.0",
4
4
  "type": "module",
5
5
  "description": "Deco admin protocol: /live/_meta, /.decofile, /deco/invoke, /deco/render, plus admin-facing site setup and commerce-app auto-configuration",
6
6
  "repository": {
@@ -25,7 +25,7 @@
25
25
  "lint:unused": "knip"
26
26
  },
27
27
  "dependencies": {
28
- "@decocms/blocks": "7.34.4"
28
+ "@decocms/blocks": "7.36.0"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "react": "^19.0.0",
@@ -58,7 +58,9 @@ describe("handleInvoke — Set-Cookie propagation (single)", () => {
58
58
  });
59
59
 
60
60
  const request = makeInvokeRequest("vtex/actions/addItemsToCart");
61
- const response = await RequestContext.run(request, () => handleInvoke(request));
61
+ const response = await RequestContext.run(request, () =>
62
+ handleInvoke(request),
63
+ );
62
64
 
63
65
  const cookies = response.headers.getSetCookie();
64
66
  expect(cookies).toHaveLength(3);
@@ -77,7 +79,9 @@ describe("handleInvoke — Set-Cookie propagation (single)", () => {
77
79
  });
78
80
 
79
81
  const request = makeInvokeRequest("vtex/actions/foo");
80
- const response = await RequestContext.run(request, () => handleInvoke(request));
82
+ const response = await RequestContext.run(request, () =>
83
+ handleInvoke(request),
84
+ );
81
85
 
82
86
  // The regressed bug appended a single comma-joined string, so
83
87
  // `getSetCookie()` returned a 1-element array. The fix appends each
@@ -94,7 +98,9 @@ describe("handleInvoke — Set-Cookie propagation (single)", () => {
94
98
  });
95
99
 
96
100
  const request = makeInvokeRequest("vtex/actions/withHeader");
97
- const response = await RequestContext.run(request, () => handleInvoke(request));
101
+ const response = await RequestContext.run(request, () =>
102
+ handleInvoke(request),
103
+ );
98
104
  expect(response.headers.get("x-vtex-trace-id")).toBe("abc-123");
99
105
  });
100
106
 
@@ -104,7 +110,9 @@ describe("handleInvoke — Set-Cookie propagation (single)", () => {
104
110
  });
105
111
 
106
112
  const request = makeInvokeRequest("vtex/loaders/productList");
107
- const response = await RequestContext.run(request, () => handleInvoke(request));
113
+ const response = await RequestContext.run(request, () =>
114
+ handleInvoke(request),
115
+ );
108
116
  expect(response.headers.getSetCookie()).toEqual([]);
109
117
  });
110
118
  });
@@ -131,7 +139,9 @@ describe("handleInvoke — Set-Cookie propagation (batch)", () => {
131
139
  "vtex/actions/addItemsToCart": { orderFormId: "x" },
132
140
  "vtex/loaders/productList": {},
133
141
  });
134
- const response = await RequestContext.run(request, () => handleInvoke(request));
142
+ const response = await RequestContext.run(request, () =>
143
+ handleInvoke(request),
144
+ );
135
145
 
136
146
  const cookies = response.headers.getSetCookie();
137
147
  expect(cookies).toHaveLength(3);
@@ -155,7 +165,8 @@ describe("handleInvoke — draft binding", () => {
155
165
  clearDraftCache();
156
166
  setBlocks({ "site/x": { value: "published" } });
157
167
  registerInvokeHandlers({
158
- "site/loaders/x.ts": async () => (loadBlocks() as Record<string, any>)["site/x"],
168
+ "site/loaders/x.ts": async () =>
169
+ (loadBlocks() as Record<string, any>)["site/x"],
159
170
  });
160
171
  });
161
172
  afterEach(() => {
@@ -166,11 +177,14 @@ describe("handleInvoke — draft binding", () => {
166
177
  });
167
178
 
168
179
  function invokeReq(headers: Record<string, string>): Request {
169
- return new Request("https://preview.example/deco/invoke/site/loaders/x.ts", {
170
- method: "POST",
171
- headers: { "content-type": "application/json", ...headers },
172
- body: "{}",
173
- });
180
+ return new Request(
181
+ "https://preview.example/deco/invoke/site/loaders/x.ts",
182
+ {
183
+ method: "POST",
184
+ headers: { "content-type": "application/json", ...headers },
185
+ body: "{}",
186
+ },
187
+ );
174
188
  }
175
189
 
176
190
  it("resolves loaders against the DRAFT when the request carries an allowed draft cookie", async () => {
@@ -183,7 +197,8 @@ describe("handleInvoke — draft binding", () => {
183
197
  const res = await handleInvoke(
184
198
  invokeReq({
185
199
  "x-forwarded-host": "preview.example",
186
- cookie: "__deco_draft=abc.preview-studio.decocms.com@vX",
200
+ cookie:
201
+ "__deco_draft=studio.decocms.com/api/o/decofile/m/main%3Ftoken%3Dt@vX",
187
202
  }),
188
203
  );
189
204
  expect(await res.json()).toEqual({ value: "draft" });
@@ -197,7 +212,9 @@ describe("handleInvoke — draft binding", () => {
197
212
  return new Response("{}");
198
213
  }) as unknown as typeof fetch;
199
214
 
200
- const res = await handleInvoke(invokeReq({ "x-forwarded-host": "preview.example" }));
215
+ const res = await handleInvoke(
216
+ invokeReq({ "x-forwarded-host": "preview.example" }),
217
+ );
201
218
  expect(await res.json()).toEqual({ value: "published" });
202
219
  expect(called).toBe(false);
203
220
  });
@@ -212,7 +229,8 @@ describe("handleInvoke — draft binding", () => {
212
229
  const res = await handleInvoke(
213
230
  invokeReq({
214
231
  "x-forwarded-host": "prod.example",
215
- cookie: "__deco_draft=abc.preview-studio.decocms.com@vX",
232
+ cookie:
233
+ "__deco_draft=studio.decocms.com/api/o/decofile/m/main%3Ftoken%3Dt@vX",
216
234
  }),
217
235
  );
218
236
  expect(await res.json()).toEqual({ value: "published" });
@@ -13,7 +13,7 @@
13
13
  * copies those headers into the final HTTP Response.
14
14
  */
15
15
 
16
- import { resolveDraftForRequest, withBlocksOverride } from "@decocms/blocks/cms";
16
+ import { resolveDraftForRequest, withDraftBlocks } from "@decocms/blocks/cms";
17
17
  import { RequestContext } from "@decocms/blocks/sdk/requestContext";
18
18
 
19
19
  export type InvokeLoader = (props: any, request: Request) => Promise<any>;
@@ -95,11 +95,13 @@ function forwardCtxHeadersTo(response: Response): void {
95
95
  }
96
96
 
97
97
  const isDev =
98
- typeof globalThis.process !== "undefined" && globalThis.process.env?.NODE_ENV === "development";
98
+ typeof globalThis.process !== "undefined" &&
99
+ globalThis.process.env?.NODE_ENV === "development";
99
100
 
100
101
  function selectFields(data: unknown, select?: string[]): unknown {
101
102
  if (!select?.length || !data || typeof data !== "object") return data;
102
- if (Array.isArray(data)) return data.map((item) => selectFields(item, select));
103
+ if (Array.isArray(data))
104
+ return data.map((item) => selectFields(item, select));
103
105
  const result: Record<string, unknown> = {};
104
106
  for (const key of select) {
105
107
  if (key in (data as Record<string, unknown>)) {
@@ -131,7 +133,9 @@ async function parseBody(request: Request): Promise<any> {
131
133
  for (const [key, value] of formData.entries()) {
132
134
  if (obj[key] !== undefined) {
133
135
  // Multiple values → array
134
- const existing = Array.isArray(obj[key]) ? (obj[key] as unknown[]) : [obj[key]];
136
+ const existing = Array.isArray(obj[key])
137
+ ? (obj[key] as unknown[])
138
+ : [obj[key]];
135
139
  existing.push(value);
136
140
  obj[key] = existing;
137
141
  } else {
@@ -193,10 +197,9 @@ function unknownHandlerMessage(key: string): string {
193
197
  const kind = isAction ? "action" : isLoader ? "loader" : "loader/action";
194
198
  const dir = isAction ? "src/actions/" : "src/loaders/";
195
199
  const cliHint = "npm run generate:loaders";
196
- const setupHint =
197
- isAction
198
- ? "setInvokeActions(() => COMMERCE_ACTIONS)"
199
- : "setInvokeLoaders(() => COMMERCE_LOADERS)";
200
+ const setupHint = isAction
201
+ ? "setInvokeActions(() => COMMERCE_ACTIONS)"
202
+ : "setInvokeLoaders(() => COMMERCE_LOADERS)";
200
203
  return (
201
204
  `Unknown handler: ${key}. ` +
202
205
  `Check that the ${kind} exists in ${dir} (file path must match the key — ` +
@@ -221,16 +224,17 @@ function warnMissingHandlerOnce(key: string): void {
221
224
  * refs; without this, `/deco/invoke` — a separate request from the page render,
222
225
  * so the page's request-scoped draft binding never reaches it — would resolve
223
226
  * against PUBLISHED blocks and hand a reviewer stale content in any
224
- * client-fetched (lazy/on-scroll) or self-fetched section. `withBlocksOverride`
227
+ * client-fetched (lazy/on-scroll) or self-fetched section. `withDraftBlocks`
225
228
  * uses AsyncLocalStorage, so each concurrent invoke gets its own override with
226
229
  * no cross-request bleed (the same isolation the layout-cache race taught us to
227
- * keep). Inert unless the draft gate passes — no draft, no wrap, published path
228
- * untouched.
230
+ * keep) — and composes the draft as a SNAPSHOT, the same semantics the page
231
+ * render applies, so a block deleted in the draft is deleted here too. Inert
232
+ * unless the draft gate passes — no draft, no wrap, published path untouched.
229
233
  */
230
234
  export async function handleInvoke(request: Request): Promise<Response> {
231
235
  const draftBlocks = await resolveDraftForRequest(request);
232
236
  return draftBlocks
233
- ? withBlocksOverride(draftBlocks, () => dispatchInvoke(request))
237
+ ? withDraftBlocks(draftBlocks, () => dispatchInvoke(request))
234
238
  : dispatchInvoke(request);
235
239
  }
236
240
 
@@ -258,7 +262,10 @@ async function dispatchInvoke(request: Request): Promise<Response> {
258
262
  return result;
259
263
  }
260
264
  const filtered = selectFields(result, select);
261
- const response = new Response(JSON.stringify(filtered), { status: 200, headers: JSON_HEADERS });
265
+ const response = new Response(JSON.stringify(filtered), {
266
+ status: 200,
267
+ headers: JSON_HEADERS,
268
+ });
262
269
  forwardCtxHeadersTo(response);
263
270
  return response;
264
271
  } catch (error) {
@@ -267,7 +274,12 @@ async function dispatchInvoke(request: Request): Promise<Response> {
267
274
  }
268
275
 
269
276
  // Batch invoke
270
- if (request.method === "POST" && body && typeof body === "object" && !Array.isArray(body)) {
277
+ if (
278
+ request.method === "POST" &&
279
+ body &&
280
+ typeof body === "object" &&
281
+ !Array.isArray(body)
282
+ ) {
271
283
  const results: Record<string, unknown> = {};
272
284
 
273
285
  const entries = Object.entries(body as Record<string, unknown>);
@@ -286,7 +298,11 @@ async function dispatchInvoke(request: Request): Promise<Response> {
286
298
  // RequestContext.responseHeaders (which forwardCtxHeadersTo()
287
299
  // below propagates onto the batch response).
288
300
  if (result instanceof Response) {
289
- try { result = await result.json(); } catch { result = null; }
301
+ try {
302
+ result = await result.json();
303
+ } catch {
304
+ result = null;
305
+ }
290
306
  }
291
307
  results[key] = selectFields(result, select);
292
308
  } catch (error) {
@@ -299,7 +315,10 @@ async function dispatchInvoke(request: Request): Promise<Response> {
299
315
  }),
300
316
  );
301
317
 
302
- const response = new Response(JSON.stringify(results), { status: 200, headers: JSON_HEADERS });
318
+ const response = new Response(JSON.stringify(results), {
319
+ status: 200,
320
+ headers: JSON_HEADERS,
321
+ });
303
322
  // All batch handlers share the same RequestContext, so any Set-Cookie
304
323
  // they appended (e.g. from VTEX vtexFetchWithCookies) is in
305
324
  // `responseHeaders` by now. Forward it as N distinct Set-Cookie headers.