@decocms/blocks-cli 7.31.5 → 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.
|
|
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.
|
|
34
|
+
"@decocms/blocks": "7.31.6",
|
|
35
35
|
"ts-morph": "^27.0.0",
|
|
36
36
|
"tsx": "^4.22.5"
|
|
37
37
|
},
|
|
@@ -341,7 +341,7 @@ export default debounce;
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
function generateSignalShim(): string {
|
|
344
|
-
return `import { useState, useRef, useEffect, useMemo, useCallback } from "react";
|
|
344
|
+
return `import { useState, useRef, useEffect, useMemo, useCallback, useSyncExternalStore } from "react";
|
|
345
345
|
export { signal, type ReactiveSignal } from "@decocms/blocks/sdk/signal";
|
|
346
346
|
|
|
347
347
|
/** Run a function immediately. Kept for legacy module-level side effects. */
|
|
@@ -387,6 +387,24 @@ export function useComputed<T>(compute: () => T): { readonly value: T } {
|
|
|
387
387
|
export function useSignalEffect(cb: () => void | (() => void)): void {
|
|
388
388
|
useEffect(cb);
|
|
389
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
|
+
}
|
|
390
408
|
`;
|
|
391
409
|
}
|
|
392
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: "
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
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
|
|
1757
|
-
fix: `
|
|
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
|
}
|