@decocms/blocks-cli 7.31.4 → 7.31.6

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-cli",
3
- "version": "7.31.4",
3
+ "version": "7.31.6",
4
4
  "type": "module",
5
5
  "description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
6
6
  "repository": {
@@ -31,7 +31,7 @@
31
31
  "lint:unused": "knip"
32
32
  },
33
33
  "dependencies": {
34
- "@decocms/blocks": "7.31.4",
34
+ "@decocms/blocks": "7.31.6",
35
35
  "ts-morph": "^27.0.0",
36
36
  "tsx": "^4.22.5"
37
37
  },
@@ -231,6 +231,7 @@ function generateWranglerConfig(ctx: MigrationContext): string {
231
231
  main: "src/worker-entry.ts",
232
232
  compatibility_date: "2025-05-01",
233
233
  compatibility_flags: ["nodejs_compat", "no_handle_cross_request_promise_resolution"],
234
+ cache: { enabled: true },
234
235
  kv_namespaces: [{ binding: "SITES_KV", id: "dev-sites-kv" }],
235
236
  },
236
237
  null,
@@ -340,7 +341,7 @@ export default debounce;
340
341
  }
341
342
 
342
343
  function generateSignalShim(): string {
343
- return `import { useState, useRef, useEffect, useMemo, useCallback } from "react";
344
+ return `import { useState, useRef, useEffect, useMemo, useCallback, useSyncExternalStore } from "react";
344
345
  export { signal, type ReactiveSignal } from "@decocms/blocks/sdk/signal";
345
346
 
346
347
  /** Run a function immediately. Kept for legacy module-level side effects. */
@@ -386,6 +387,24 @@ export function useComputed<T>(compute: () => T): { readonly value: T } {
386
387
  export function useSignalEffect(cb: () => void | (() => void)): void {
387
388
  useEffect(cb);
388
389
  }
390
+
391
+ /**
392
+ * Subscribe to a ReactiveSignal and re-render when it changes.
393
+ * Required for signals from useUI() (displayCart, displayMenu, etc.) —
394
+ * reading .value directly in render is a dead read in React and freezes the UI.
395
+ *
396
+ * @example
397
+ * const { displayCart } = useUI();
398
+ * const open = useSignalValue(displayCart);
399
+ * return <Drawer open={open} />;
400
+ */
401
+ export function useSignalValue<T>(sig: ReactiveSignal<T>): T {
402
+ return useSyncExternalStore(
403
+ (cb) => sig.subscribe(cb),
404
+ () => sig.value,
405
+ () => sig.value,
406
+ );
407
+ }
389
408
  `;
390
409
  }
391
410
 
@@ -1702,9 +1702,21 @@ const ruleVtexProxyHandlerMissing: Rule = {
1702
1702
  const MODULE_SIGNAL_RE =
1703
1703
  /^(?:export\s+)?const\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(?:\w+\.)?signal\s*\(/m;
1704
1704
 
1705
+ // Well-known signals returned by useUI(). These are never declared with
1706
+ // `const x = signal(...)` at module scope — they come from a hook — so
1707
+ // MODULE_SIGNAL_RE won't catch them. The list is stable: it's the public
1708
+ // API surface of useUI() across all deco storefronts.
1709
+ const WELL_KNOWN_UI_SIGNALS = new Set([
1710
+ "displayCart",
1711
+ "displayMenu",
1712
+ "displayNav",
1713
+ "displaySearchbar",
1714
+ "displaySearchPopup",
1715
+ ]);
1716
+
1705
1717
  const ruleSignalValueReads: Rule = {
1706
1718
  id: "signal-value-reads",
1707
- title: "Module-level signal .value reads without useStore()",
1719
+ title: "Signal .value reads without subscription (freezes UI)",
1708
1720
  run({ siteDir, fs }: RuleContext): Finding[] {
1709
1721
  const tsFiles = fs.glob(siteDir, "src/**/*.{ts,tsx}", SRC_GLOB_EXCLUDES);
1710
1722
 
@@ -1724,7 +1736,13 @@ const ruleSignalValueReads: Rule = {
1724
1736
  }
1725
1737
  }
1726
1738
 
1727
- if (moduleSignals.size === 0) return [];
1739
+ // Also check well-known useUI() signals — they never appear as module-level
1740
+ // declarations, so MODULE_SIGNAL_RE never picks them up.
1741
+ for (const name of WELL_KNOWN_UI_SIGNALS) {
1742
+ if (!moduleSignals.has(name)) {
1743
+ moduleSignals.set(name, "useUI()");
1744
+ }
1745
+ }
1728
1746
 
1729
1747
  // Pre-compile per-signal regexes once — avoids N×M compilations in Pass 2.
1730
1748
  const valueReMap = new Map<string, RegExp>(
@@ -1732,7 +1750,8 @@ const ruleSignalValueReads: Rule = {
1732
1750
  );
1733
1751
 
1734
1752
  // Pass 2: for each .tsx file, check per-signal whether .value is read
1735
- // without a matching useStore(name.store call in the same file.
1753
+ // without a matching subscription in the same file.
1754
+ // Accepts either useStore(name.store or useSignalValue(name as subscriptions.
1736
1755
  const tsxFiles = tsFiles.filter((f) => f.endsWith(".tsx"));
1737
1756
  const findings: Finding[] = [];
1738
1757
 
@@ -1743,18 +1762,17 @@ const ruleSignalValueReads: Rule = {
1743
1762
  for (const [name, declaredIn] of moduleSignals) {
1744
1763
  const valueRe = valueReMap.get(name)!;
1745
1764
  if (!valueRe.test(content)) continue;
1746
- // Per-signal subscription check: useStore(name.store is the canonical
1747
- // subscription pattern. A file that subscribes to a different signal
1748
- // is not considered subscribed to this one.
1749
- const subscribedRe = new RegExp(`\\buseStore\\s*\\(\\s*${name}\\.store\\b`);
1765
+ const subscribedRe = new RegExp(
1766
+ `\\buseStore\\s*\\(\\s*${name}\\.store\\b|\\buseSignalValue\\s*\\(\\s*${name}\\b`,
1767
+ );
1750
1768
  if (subscribedRe.test(content)) continue;
1751
1769
 
1752
1770
  findings.push({
1753
1771
  rule: "signal-value-reads",
1754
1772
  severity: "warning",
1755
1773
  file: rel,
1756
- message: `'${name}.value' read without useStore() — component will not re-render when the signal changes`,
1757
- fix: `Use useStore(${name}.store, (s) => s) to subscribe, or replace with useQuery / useMutation`,
1774
+ message: `'${name}.value' read without subscription — component will not re-render when the signal changes`,
1775
+ fix: `Replace with \`const ${name}Value = useSignalValue(${name})\` (from ~/sdk/signal) or \`useStore(${name}.store)\``,
1758
1776
  meta: { signalName: name, declaredIn },
1759
1777
  });
1760
1778
  }