@decocms/start 2.22.0 → 2.23.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/.agents/skills/deco-to-tanstack-migration/SKILL.md +12 -7
- package/.agents/skills/deco-to-tanstack-migration/references/platform-hooks/README.md +57 -47
- package/.agents/skills/deco-to-tanstack-migration/references/platform-hooks-factories.md +186 -0
- package/MIGRATION_TOOLING_PLAN.md +144 -0
- package/package.json +1 -1
- package/scripts/migrate/post-cleanup/rules.ts +62 -7
- package/scripts/migrate/post-cleanup/runner.test.ts +77 -1
- package/scripts/migrate/templates/commerce-loaders.ts +2 -1
- package/scripts/migrate/templates/routes.ts +15 -10
- package/scripts/migrate/templates/server-entry.ts +13 -55
- package/scripts/migrate/templates/vite-config.ts +0 -35
|
@@ -43,7 +43,7 @@ Each phase has entry/exit criteria. Follow in order. Automation % indicates how
|
|
|
43
43
|
| [2](#phase-2--signals--state) | Signals & State | ~50% | `references/signals/` |
|
|
44
44
|
| [3](#phase-3--deco-framework) | Deco Framework Elimination | ~80% | `references/deco-framework/` |
|
|
45
45
|
| [4](#phase-4--commerce--types) | Commerce Types & UI | ~70% | `references/commerce/` |
|
|
46
|
-
| [5](#phase-5--platform-hooks) | Platform Hooks |
|
|
46
|
+
| [5](#phase-5--platform-hooks) | Platform Hooks (factories, W12+) | template | `references/platform-hooks-factories.md` |
|
|
47
47
|
| [6](#phase-6--islands-elimination) | Islands Elimination | ~60% | `references/islands.md` |
|
|
48
48
|
| [7](#phase-7--section-registry) | Section Registry & Setup | 0% | `references/async-rendering.md` |
|
|
49
49
|
| [8](#phase-8--routes--cms) | Routes & CMS | template | `references/navigation.md` |
|
|
@@ -152,14 +152,18 @@ See: `references/commerce/README.md`, `references/vtex-commerce.md`
|
|
|
152
152
|
|
|
153
153
|
**Entry**: Phase 4 complete
|
|
154
154
|
|
|
155
|
-
**Actions
|
|
156
|
-
1.
|
|
157
|
-
2.
|
|
158
|
-
3.
|
|
155
|
+
**Actions (Wave 12+ factory-based — current)**:
|
|
156
|
+
1. `src/hooks/useCart.ts` — 5-line shim around `createUseCart` from `@decocms/apps/vtex/hooks/createUseCart`
|
|
157
|
+
2. `src/hooks/useUser.ts` — 5-line shim around `createUseUser`
|
|
158
|
+
3. `src/hooks/useWishlist.ts` — 5-line shim around `createUseWishlist`
|
|
159
|
+
4. The migration template (`scripts/migrate/templates/hooks.ts`) emits all three for VTEX sites automatically.
|
|
160
|
+
|
|
161
|
+
For non-VTEX platforms, scaffold no-op stubs using `@decocms/start/sdk/signal` (see factories doc § "Non-VTEX platforms").
|
|
159
162
|
|
|
160
163
|
**Exit**: Cart add/remove works, no `apps/{platform}/hooks` imports
|
|
161
164
|
|
|
162
|
-
See: `references/platform-hooks
|
|
165
|
+
See: `references/platform-hooks-factories.md` (canonical, Wave 12+).
|
|
166
|
+
Pre-W12 manual approach is preserved at `references/platform-hooks/README.md` for sites that haven't migrated to factories yet.
|
|
163
167
|
|
|
164
168
|
---
|
|
165
169
|
|
|
@@ -356,7 +360,8 @@ For sites with 100+ sections:
|
|
|
356
360
|
| Signals → TanStack Store | `references/signals/` |
|
|
357
361
|
| Deco framework elimination | `references/deco-framework/` |
|
|
358
362
|
| Commerce & widget types | `references/commerce/` |
|
|
359
|
-
| Platform hooks (VTEX) | `references/platform-hooks
|
|
363
|
+
| Platform hooks (VTEX, factories — Wave 12+) | `references/platform-hooks-factories.md` |
|
|
364
|
+
| Platform hooks (manual, legacy pre-W12) | `references/platform-hooks/README.md` |
|
|
360
365
|
| Vite configuration | `references/vite-config/` |
|
|
361
366
|
| Automation commands | `references/codemod-commands.md` |
|
|
362
367
|
| Islands elimination | `references/islands.md` |
|
|
@@ -1,16 +1,29 @@
|
|
|
1
|
-
# Platform Hooks Migration
|
|
1
|
+
# Platform Hooks Migration (legacy reference)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **This document describes the pre-Wave-12 manual approach.** New
|
|
4
|
+
> migrations should follow
|
|
5
|
+
> [`platform-hooks-factories.md`](../platform-hooks-factories.md), which
|
|
6
|
+
> covers the `createUseCart` / `createUseUser` / `createUseWishlist`
|
|
7
|
+
> factories from `@decocms/apps/vtex/hooks`. The factories collapse
|
|
8
|
+
> everything below into a 5-line site shim per hook.
|
|
9
|
+
>
|
|
10
|
+
> This file is kept for sites that scaffolded before the factories
|
|
11
|
+
> existed — typically sites with `src/lib/vtex-cart-server.ts` or
|
|
12
|
+
> hand-rolled `createServerFn` calls to VTEX endpoints inside
|
|
13
|
+
> `src/hooks/useCart.ts`. See the **"Migrating off the manual approach"**
|
|
14
|
+
> section in the new doc for the cleanup playbook.
|
|
4
15
|
|
|
5
|
-
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Strategy (legacy)
|
|
6
19
|
|
|
7
20
|
All hooks are **site-local**. No Vite alias tricks. No compat layers.
|
|
8
21
|
|
|
9
|
-
- Active platform hooks (VTEX for this store)
|
|
10
|
-
- Inactive platform hooks (Wake, Shopify, etc.)
|
|
11
|
-
- Auth hooks
|
|
22
|
+
- Active platform hooks (VTEX for this store) → `~/hooks/useCart.ts` with real implementation
|
|
23
|
+
- Inactive platform hooks (Wake, Shopify, etc.) → `~/hooks/platform/{name}.ts` with no-op stubs
|
|
24
|
+
- Auth hooks → `~/hooks/useUser.ts`, `~/hooks/useWishlist.ts`
|
|
12
25
|
|
|
13
|
-
## VTEX useCart (
|
|
26
|
+
## VTEX useCart (Manual Implementation)
|
|
14
27
|
|
|
15
28
|
### Why Server Functions Are Required
|
|
16
29
|
|
|
@@ -18,7 +31,7 @@ The storefront domain (e.g., `my-store.deco.site`) differs from the VTEX checkou
|
|
|
18
31
|
|
|
19
32
|
Use TanStack Start `createServerFn` to create server-side proxy functions that the client hook calls transparently.
|
|
20
33
|
|
|
21
|
-
### Server Functions (
|
|
34
|
+
### Server Functions (`~/lib/vtex-cart-server.ts`)
|
|
22
35
|
|
|
23
36
|
```typescript
|
|
24
37
|
import { createServerFn } from "@tanstack/react-start";
|
|
@@ -40,50 +53,46 @@ export const getOrCreateCart = createServerFn({ method: "GET" })
|
|
|
40
53
|
"X-VTEX-API-AppKey": API_KEY,
|
|
41
54
|
"X-VTEX-API-AppToken": API_TOKEN,
|
|
42
55
|
},
|
|
43
|
-
body: JSON.stringify({
|
|
56
|
+
body: JSON.stringify({
|
|
57
|
+
expectedOrderFormSections: [
|
|
58
|
+
"items",
|
|
59
|
+
"totalizers",
|
|
60
|
+
"shippingData",
|
|
61
|
+
"clientPreferencesData",
|
|
62
|
+
"storePreferencesData",
|
|
63
|
+
"marketingData",
|
|
64
|
+
],
|
|
65
|
+
}),
|
|
44
66
|
});
|
|
45
67
|
return res.json();
|
|
46
68
|
});
|
|
47
|
-
|
|
48
|
-
export const addItemsToCart = createServerFn({ method: "POST" })
|
|
49
|
-
.validator((data: { orderFormId: string; items: any[] }) => data)
|
|
50
|
-
.handler(async ({ data }) => {
|
|
51
|
-
const res = await fetch(
|
|
52
|
-
`https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${data.orderFormId}/items`,
|
|
53
|
-
{
|
|
54
|
-
method: "POST",
|
|
55
|
-
headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
|
|
56
|
-
body: JSON.stringify({ orderItems: data.items }),
|
|
57
|
-
},
|
|
58
|
-
);
|
|
59
|
-
return res.json();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
export const updateCartItems = createServerFn({ method: "POST" })
|
|
63
|
-
.validator((data: { orderFormId: string; items: any[] }) => data)
|
|
64
|
-
.handler(async ({ data }) => {
|
|
65
|
-
const res = await fetch(
|
|
66
|
-
`https://${ACCOUNT}.vtexcommercestable.com.br/api/checkout/pub/orderForm/${data.orderFormId}/items/update`,
|
|
67
|
-
{
|
|
68
|
-
method: "POST",
|
|
69
|
-
headers: { "Content-Type": "application/json", "X-VTEX-API-AppKey": API_KEY, "X-VTEX-API-AppToken": API_TOKEN },
|
|
70
|
-
body: JSON.stringify({ orderItems: data.items }),
|
|
71
|
-
},
|
|
72
|
-
);
|
|
73
|
-
return res.json();
|
|
74
|
-
});
|
|
75
69
|
```
|
|
76
70
|
|
|
77
|
-
|
|
71
|
+
> **Don't write code like this in new sites.** The factories already wrap
|
|
72
|
+
> all canonical VTEX action endpoints (cart, session, masterdata,
|
|
73
|
+
> newsletter, checkout) in `@decocms/apps/vtex/actions/*`. The migration
|
|
74
|
+
> template scaffolds `src/server/invoke.gen.ts` which exposes them as
|
|
75
|
+
> typed server functions; `~/server/invoke.ts` then re-exports them
|
|
76
|
+
> under `invoke.vtex.actions.*`. The factory consumes that surface and
|
|
77
|
+
> returns the legacy hook shape.
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
### Hook (`~/hooks/useCart.ts`)
|
|
80
|
+
|
|
81
|
+
Key design decisions of the legacy manual hook:
|
|
80
82
|
- **Module-level singleton state** shared across all component instances
|
|
81
83
|
- **Pub/sub pattern** (`_listeners` Set) for notifying React components of changes
|
|
82
|
-
- **Cookie-based session**: reads/writes `checkout.vtex.com__orderFormId` on the **client** side
|
|
84
|
+
- **Cookie-based session**: reads/writes `checkout.vtex.com__orderFormId` on the **client** side
|
|
83
85
|
- Returns `cart` and `loading` with `.value` getter/setter for backward compat with Preact-era components
|
|
84
86
|
- Lazy initialization: cart is fetched on first component mount, not on module load
|
|
85
87
|
- Exports `itemToAnalyticsItem` for cart-specific analytics mapping
|
|
86
88
|
|
|
89
|
+
The factory in `@decocms/apps/vtex/hooks/createUseCart` ships *exactly*
|
|
90
|
+
these semantics — that's the implementation behind the new shim. If your
|
|
91
|
+
site needs to extend behaviour (e.g. extra analytics events, custom
|
|
92
|
+
post-add hooks), prefer wrapping the factory's exports rather than
|
|
93
|
+
forking back to a manual hook; the factory leaves space for that
|
|
94
|
+
without giving up the upgrade path.
|
|
95
|
+
|
|
87
96
|
### Cross-Domain Checkout
|
|
88
97
|
|
|
89
98
|
The minicart's "Finalizar Compra" button must link to the VTEX checkout domain with the `orderFormId` as a query parameter — the VTEX domain can't read the storefront's cookies:
|
|
@@ -92,10 +101,8 @@ The minicart's "Finalizar Compra" button must link to the VTEX checkout domain w
|
|
|
92
101
|
const checkoutUrl = `https://secure.${STORE_DOMAIN}/checkout/?orderFormId=${orderFormId}`;
|
|
93
102
|
```
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
Site-local types for VTEX-specific structures:
|
|
98
|
-
- `OrderFormItem`, `SimulationOrderForm`, `Sla`, `SKU`, `VtexProduct`
|
|
104
|
+
This pattern is unchanged by the factory — it's a UI concern, not a hook
|
|
105
|
+
concern. Implement it in your minicart component as before.
|
|
99
106
|
|
|
100
107
|
## Inactive Platform Stubs
|
|
101
108
|
|
|
@@ -130,9 +137,13 @@ export function useWishlist() {
|
|
|
130
137
|
}
|
|
131
138
|
```
|
|
132
139
|
|
|
133
|
-
Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`.
|
|
140
|
+
Create similar stubs for: `shopify.ts`, `linx.ts`, `vnda.ts`, `nuvemshop.ts`. Match the return shape to what each platform's AddToCartButton expects (some use `addItem`, others `addItems`).
|
|
134
141
|
|
|
135
|
-
|
|
142
|
+
The factory equivalent for non-VTEX sites is documented in
|
|
143
|
+
[`platform-hooks-factories.md` § "Non-VTEX platforms"](../platform-hooks-factories.md#non-vtex-platforms).
|
|
144
|
+
Until each platform has its own factory in `@decocms/apps`, the stub
|
|
145
|
+
shape above is still correct — but use `@decocms/start/sdk/signal`
|
|
146
|
+
instead of hand-rolled `{ value: ... }` objects.
|
|
136
147
|
|
|
137
148
|
## Import Rewrites
|
|
138
149
|
|
|
@@ -143,7 +154,6 @@ sed -i '' 's|from "apps/vtex/hooks/useWishlist.ts"|from "~/hooks/useWishlist"|g'
|
|
|
143
154
|
sed -i '' 's|from "apps/vtex/utils/types.ts"|from "~/types/vtex"|g'
|
|
144
155
|
sed -i '' 's|from "apps/shopify/hooks/useCart.ts"|from "~/hooks/platform/shopify"|g'
|
|
145
156
|
sed -i '' 's|from "apps/wake/hooks/useCart.ts"|from "~/hooks/platform/wake"|g'
|
|
146
|
-
# etc. for all platforms
|
|
147
157
|
```
|
|
148
158
|
|
|
149
159
|
## Verification
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Platform Hooks — Factory Pattern
|
|
2
|
+
|
|
3
|
+
> **Canonical reference for `createUseCart` / `createUseUser` /
|
|
4
|
+
> `createUseWishlist` from `@decocms/apps/vtex/hooks`.** These factories
|
|
5
|
+
> ship the legacy invoke-based hook semantics that migrated Fresh sites
|
|
6
|
+
> depend on — module-level singleton state, listener-based re-render,
|
|
7
|
+
> awaitable async actions, signal-shaped accessors. Sites consume them
|
|
8
|
+
> as 5-line shims.
|
|
9
|
+
|
|
10
|
+
This doc replaces the pre-W12 "manual `createServerFn` per VTEX endpoint"
|
|
11
|
+
approach in
|
|
12
|
+
[`platform-hooks/README.md`](./platform-hooks/README.md). If you scaffolded
|
|
13
|
+
a site before Wave 12 (≤ `@decocms/apps@2.x` / `@decocms/start@2.18`), see
|
|
14
|
+
"Migrating off the manual approach" at the bottom.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What the factories own
|
|
19
|
+
|
|
20
|
+
| Concern | Where it lives |
|
|
21
|
+
|---|---|
|
|
22
|
+
| Module-level singleton state (`cart`, `user`, `wishlist`) | Inside the factory closure |
|
|
23
|
+
| `useEffect` + `forceRender(c => c + 1)` re-render pattern | Factory |
|
|
24
|
+
| Signal-shaped accessors (`cart.value`, `user.value`) | Factory |
|
|
25
|
+
| Awaitable mutations (`await addItem(...)`) | Factory |
|
|
26
|
+
| `itemToAnalyticsItem` helper (cart) | Factory |
|
|
27
|
+
| Wishlist arg swap (`productId` ↔ `productGroupId`) | Factory |
|
|
28
|
+
| **VTEX HTTP calls** | NOT in the factory — provided by the `invoke` proxy you pass in |
|
|
29
|
+
|
|
30
|
+
The factory only wires state + listeners. The site provides an `invoke`
|
|
31
|
+
object whose shape is structurally typed against
|
|
32
|
+
`CreateUseCartInvoke` / `CreateUseUserInvoke` / `CreateUseWishlistInvoke`
|
|
33
|
+
(exported next to each factory). The migration template generates an
|
|
34
|
+
`invoke` proxy in `src/server/invoke.ts` that meets all three shapes
|
|
35
|
+
without any extra wiring.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Site-local shim (the entire file)
|
|
40
|
+
|
|
41
|
+
### `src/hooks/useCart.ts`
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { createUseCart } from "@decocms/apps/vtex/hooks/createUseCart";
|
|
45
|
+
import { invoke } from "~/server/invoke";
|
|
46
|
+
|
|
47
|
+
export type { OrderForm, OrderFormItem } from "@decocms/apps/vtex/types";
|
|
48
|
+
|
|
49
|
+
export const { useCart, resetCart, itemToAnalyticsItem } = createUseCart({
|
|
50
|
+
invoke,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `src/hooks/useUser.ts`
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createUseUser } from "@decocms/apps/vtex/hooks/createUseUser";
|
|
58
|
+
import { invoke } from "~/server/invoke";
|
|
59
|
+
|
|
60
|
+
export type { Person } from "@decocms/apps/vtex/loaders/user";
|
|
61
|
+
|
|
62
|
+
export const { useUser, resetUser } = createUseUser({ invoke });
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `src/hooks/useWishlist.ts`
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { createUseWishlist } from "@decocms/apps/vtex/hooks/createUseWishlist";
|
|
69
|
+
import { invoke } from "~/server/invoke";
|
|
70
|
+
|
|
71
|
+
export type { WishlistItem } from "@decocms/apps/vtex/loaders/wishlist";
|
|
72
|
+
|
|
73
|
+
export const { useWishlist, resetWishlist } = createUseWishlist({ invoke });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That's the whole hook — no `createServerFn`, no VTEX URLs, no `AppKey`
|
|
77
|
+
plumbing. `npm run migrate` scaffolds these three files automatically
|
|
78
|
+
when `--platform vtex` is set; if you regenerate, the migration template
|
|
79
|
+
in `scripts/migrate/templates/hooks.ts` is the source of truth.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Why a factory and not a single hook?
|
|
84
|
+
|
|
85
|
+
> Two reasons that come up repeatedly when reviewing migration PRs.
|
|
86
|
+
|
|
87
|
+
1. **`useCart` already exists in apps.** The canonical `vtex/hooks/useCart.ts`
|
|
88
|
+
is built on TanStack Query and exposes the `Minicart` shape — that is
|
|
89
|
+
the shape new code should target. The factory exists strictly so
|
|
90
|
+
already-migrated UIs keep working without a rewrite. Both can coexist
|
|
91
|
+
in one site.
|
|
92
|
+
2. **Singletons can't live in a shared package without leaking across
|
|
93
|
+
sites.** The factory call instantiates a fresh module-level state per
|
|
94
|
+
site. Importing `useCart` directly from apps would share state across
|
|
95
|
+
any sites that ran in the same Worker (matters less in practice, but
|
|
96
|
+
it's the architectural reason the factory exists).
|
|
97
|
+
|
|
98
|
+
The factory boundary is also the seam where we'd later wire
|
|
99
|
+
`@tanstack/store` if we wanted to — the API shape is signal-compatible
|
|
100
|
+
already.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Non-VTEX platforms
|
|
105
|
+
|
|
106
|
+
Sites that target Wake / Shopify / VNDA / Linx / Nuvemshop still need a
|
|
107
|
+
hook surface that AddToCartButtons can consume. Until each platform has
|
|
108
|
+
its own factory in `@decocms/apps`, scaffold a no-op shim:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// src/hooks/useCart.ts (custom platform)
|
|
112
|
+
import { signal } from "@decocms/start/sdk/signal";
|
|
113
|
+
|
|
114
|
+
const cart = signal<unknown>(null);
|
|
115
|
+
const loading = signal(false);
|
|
116
|
+
|
|
117
|
+
export function useCart() {
|
|
118
|
+
return {
|
|
119
|
+
cart,
|
|
120
|
+
loading,
|
|
121
|
+
async getCart() {
|
|
122
|
+
// TODO: call your platform's cart API via ~/server/invoke
|
|
123
|
+
return null;
|
|
124
|
+
},
|
|
125
|
+
async addItems(_items: unknown[]) {
|
|
126
|
+
// TODO
|
|
127
|
+
},
|
|
128
|
+
async updateItems(_items: unknown[]) {
|
|
129
|
+
// TODO
|
|
130
|
+
},
|
|
131
|
+
setCart(next: unknown) {
|
|
132
|
+
cart.value = next;
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default useCart;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`@decocms/start/sdk/signal` (re-exported via `~/sdk/signal` after
|
|
141
|
+
migration) gives you the same `.value` getter/setter the factory uses,
|
|
142
|
+
so AddToCart UI components don't need to know which platform is wired.
|
|
143
|
+
|
|
144
|
+
The migration template's `generateGenericUseCart()` emits this stub when
|
|
145
|
+
`--platform` is `custom` (or any non-VTEX value).
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Migrating off the manual approach (pre-W12 sites)
|
|
150
|
+
|
|
151
|
+
If your site contains files like `src/lib/vtex-cart-server.ts` or hand-rolled `createServerFn` blocks for VTEX endpoints in `src/hooks/useCart.ts`, the post-cleanup audit will not auto-fix them — the manual code accumulated 6+ months of site-specific edits and the per-site judgment call about "what's still needed?" is real. The mechanical part:
|
|
152
|
+
|
|
153
|
+
1. Replace the entire body of `src/hooks/useCart.ts` with the 5-line factory shim above.
|
|
154
|
+
2. Delete `src/lib/vtex-cart-server.ts` (the migration template's `src/server/invoke.gen.ts` provides equivalent server functions wrapping `@decocms/apps/vtex/actions/checkout`).
|
|
155
|
+
3. Verify `src/server/invoke.ts` exports the proxy shape the factory needs (cart actions under `invoke.vtex.actions`). The migration template scaffolds this; older sites may need to add the missing entries by hand.
|
|
156
|
+
4. Run `npm run typecheck` — TypeScript will surface any callsites that referenced removed helpers (e.g. local `getOrCreateCart` shims).
|
|
157
|
+
5. Repeat for `useUser` / `useWishlist`.
|
|
158
|
+
|
|
159
|
+
For `useUser`, the analog of step 2 is removing any local `currentUser` /
|
|
160
|
+
`getUser` server functions in favor of `@decocms/apps/vtex/loaders/user`
|
|
161
|
+
exposed via `invoke.vtex.loaders.user()`. For `useWishlist`, the
|
|
162
|
+
canonical surface is `@decocms/apps/vtex/{actions,loaders}/wishlist`.
|
|
163
|
+
|
|
164
|
+
If you find yourself wanting to add behaviour to a factory (extra cart
|
|
165
|
+
actions, custom analytics events) rather than ripping out the factory and going back to a manual hook:
|
|
166
|
+
|
|
167
|
+
- **Extra read paths** → expose a new loader from
|
|
168
|
+
`@decocms/apps/vtex/loaders/*`, register it in `~/server/invoke.ts`,
|
|
169
|
+
call from your component (the factory doesn't need to know).
|
|
170
|
+
- **Extra write paths** → ditto for `@decocms/apps/vtex/actions/*`.
|
|
171
|
+
- **Cross-cutting business logic** (e.g. PIX-specific offer pricing) →
|
|
172
|
+
this is the kind of seam that justifies a parallel `useOffer` factory.
|
|
173
|
+
Talk to the apps maintainers; opening up a factory's plugin slots is a
|
|
174
|
+
one-PR change in apps, not a per-site rewrite.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Related
|
|
179
|
+
|
|
180
|
+
- `scripts/migrate/templates/hooks.ts` — the template that emits the
|
|
181
|
+
shims above.
|
|
182
|
+
- `apps-start/vtex/hooks/createUseCart.ts` /
|
|
183
|
+
`createUseUser.ts` / `createUseWishlist.ts` — the factories
|
|
184
|
+
themselves; each docstring is the authoritative API reference.
|
|
185
|
+
- `references/platform-hooks/README.md` — historical reference for the
|
|
186
|
+
pre-W12 manual approach (kept for sites that haven't migrated yet).
|
|
@@ -239,6 +239,58 @@ Each item carries a status: ⬜ pending, 🟡 in progress, ✅ done, 🚫 blocke
|
|
|
239
239
|
|
|
240
240
|
> Append-only. Each entry: date, what we found, where it impacts the plan.
|
|
241
241
|
|
|
242
|
+
### 2026-05-01 — Wave 15-A double-check exposed a self-perpetuating template→audit loop
|
|
243
|
+
|
|
244
|
+
- **Q1 (sites→packages promotion completeness):** Two subagents
|
|
245
|
+
swept `casaevideo-storefront` + `baggagio-tanstack`. The big
|
|
246
|
+
A-list icebergs are caught — but **5 cross-site duplications
|
|
247
|
+
satisfying D4** slipped through (`useSuggestions`, `useOffer`
|
|
248
|
+
forks, `useVariantPossibilities` forks, site-local copies of
|
|
249
|
+
framework `clx` / `useSendEvent`, three competing `Picture`
|
|
250
|
+
APIs). Plus 4 migration debts where the framework already has
|
|
251
|
+
the answer (`useCart` factory not adopted in casaevideo,
|
|
252
|
+
`runtime.ts` inline proxy still scaffolded, location matcher
|
|
253
|
+
duplication, inline cookie helpers).
|
|
254
|
+
- **Q2 (script/skill coverage of what we shipped):** Worse. The
|
|
255
|
+
migration script's templates were **scaffolding code that the
|
|
256
|
+
audit's `--fix` then removed** — the textbook
|
|
257
|
+
self-perpetuating loop. `templates/vite-config.ts` was emitting
|
|
258
|
+
`site-manual-chunks` + `deco-stub-meta-gen` (both already
|
|
259
|
+
inside `decoVitePlugin()`). `templates/server-entry.ts` was
|
|
260
|
+
emitting a 47-line `createNestedInvokeProxy` body (already in
|
|
261
|
+
`@decocms/start/sdk`). The factories shipped in W12 (`createUseUser`
|
|
262
|
+
/ `createUseWishlist`) had **zero skill mentions** — the
|
|
263
|
+
pre-W12 manual approach was still the canonical doc. Cookie
|
|
264
|
+
passthrough helpers in `cookiePassthrough.ts` were **half-shipped**:
|
|
265
|
+
the deco-start side compiled, the apps-start providers it
|
|
266
|
+
references in its own docstring don't exist, and the migration
|
|
267
|
+
script never wired either.
|
|
268
|
+
- **Decided: ship Wave 15-A as a single PR.** Drop the obsolete
|
|
269
|
+
emissions (G1/G2/G4/G5), expand `dead-runtime-shim` to catch
|
|
270
|
+
both the legacy inline shape (with `Runtime` export) and the
|
|
271
|
+
Wave-15-A canonical re-export shape (skip — desired form),
|
|
272
|
+
publish a `platform-hooks-factories.md` skill that supersedes
|
|
273
|
+
the stale README, and update plan + journal.
|
|
274
|
+
- **Defer to Wave 15-B / 16:** (G3) promotion of the
|
|
275
|
+
`invoke.gen.ts` 170-LOC server-fn block to apps-start needs
|
|
276
|
+
research on TanStack Start's compiler scanning behaviour
|
|
277
|
+
(whether `createServerFn` can be transformed when it lives in
|
|
278
|
+
a node_module). (H1) full cookie-passthrough provider wiring
|
|
279
|
+
(`setRequestCookieProvider` / `setResponseCookieForwarder` in
|
|
280
|
+
apps-start, auto-wire in `setup.ts`) needs design. The 5
|
|
281
|
+
cross-site convergence promotions (`useSuggestions`,
|
|
282
|
+
`useOffer` factory, `Picture` unification, `clx`/`useSendEvent`
|
|
283
|
+
redirects, `relative()` extension) are now in the priority-2
|
|
284
|
+
backlog, sequenced after Wave 15-A merges.
|
|
285
|
+
- **The double-check itself is a useful primitive.** "Did we
|
|
286
|
+
ship script/skill/audit coverage for everything we built?" run
|
|
287
|
+
against the framework + apps inventory consistently surfaces
|
|
288
|
+
these loops. Codify it: when promoting a new factory or
|
|
289
|
+
helper, the PR checklist must include "matching template
|
|
290
|
+
emit", "matching audit-rule expansion", "matching skill doc
|
|
291
|
+
entry". This is the kind of self-check that prevents the next
|
|
292
|
+
16-month-old stale skill.
|
|
293
|
+
|
|
242
294
|
### 2026-05-01 — Wave 14-A rescoped from three codemods to one based on real als data
|
|
243
295
|
|
|
244
296
|
- **Pre-data plan vs post-data plan.** The plan called for three
|
|
@@ -984,6 +1036,98 @@ recipes in `references/htmx-rewrite.md`.
|
|
|
984
1036
|
not the import), but the import-removal would create dead
|
|
985
1037
|
references. Order is correct.
|
|
986
1038
|
|
|
1039
|
+
### Wave 15-A (close template→audit loops + factories skill — Priority 2 follow-on) — 🟡 **IN FLIGHT**
|
|
1040
|
+
|
|
1041
|
+
Triggered by the double-check audit on 2026-05-01: subagent sweeps over
|
|
1042
|
+
casaevideo-storefront + baggagio-tanstack revealed (a) the migration
|
|
1043
|
+
template was scaffolding code that the audit's `--fix` then removed,
|
|
1044
|
+
and (b) the W12 factory hooks (`createUseUser`, `createUseWishlist`)
|
|
1045
|
+
had no skill coverage. Wave 15-A closes both loops in one PR.
|
|
1046
|
+
|
|
1047
|
+
**Shipped (one PR against `decocms/deco-start`):**
|
|
1048
|
+
|
|
1049
|
+
37. `feat(migrate): close template→audit loops for vite plugins, runtime, cookies, branding + factories skill` 🟡 **WAITING ON CI**.
|
|
1050
|
+
- **`templates/vite-config.ts`** — drop `site-manual-chunks` and
|
|
1051
|
+
`deco-stub-meta-gen` plugin emissions. Both already live in
|
|
1052
|
+
`decoVitePlugin()` (`src/vite/plugin.js`). The audit's
|
|
1053
|
+
`obsolete-vite-plugins --fix` was undoing the template's own
|
|
1054
|
+
output; now the template emits clean, the audit catches
|
|
1055
|
+
regressions in legacy sites.
|
|
1056
|
+
- **`templates/server-entry.ts` `generateRuntime()`** — replace
|
|
1057
|
+
the 47-line inline `createNestedInvokeProxy` body with a 6-line
|
|
1058
|
+
re-export from `@decocms/start/sdk`. Sites keep
|
|
1059
|
+
`import { invoke } from "~/runtime"` and `Runtime.invoke`
|
|
1060
|
+
shapes. A2 of the original investigation finally lands at the
|
|
1061
|
+
template layer (was previously only patched post-migration by
|
|
1062
|
+
the audit).
|
|
1063
|
+
- **`templates/server-entry.ts` `generateInvoke()` (VTEX path)**
|
|
1064
|
+
— replace inline `mergeSetCookies` helper with
|
|
1065
|
+
`forwardResponseCookies` from
|
|
1066
|
+
`@decocms/start/sdk/cookiePassthrough`. The framework helper
|
|
1067
|
+
already shipped with try/catch for build-time safety.
|
|
1068
|
+
`getVtexCookies` stays inline (it's auth-specific filtering, not
|
|
1069
|
+
generic passthrough — see Wave 15-B/16 for full provider wiring
|
|
1070
|
+
under H1).
|
|
1071
|
+
- **`templates/routes.ts` + `templates/commerce-loaders.ts`** —
|
|
1072
|
+
replace casaevideo-specific branding leaks ("Tudo para sua
|
|
1073
|
+
casa…" tagline, "O melhor site de compras online…"
|
|
1074
|
+
`productListPageCollection` SEO description) with
|
|
1075
|
+
`${siteTitle}`-derived defaults plus `MIGRATION TODO` markers
|
|
1076
|
+
pointing at the per-site customization spot. CMS `Site.seo`
|
|
1077
|
+
overrides the defaults at runtime so leaving them visible in
|
|
1078
|
+
pre-resolution states is the safe behaviour.
|
|
1079
|
+
- **Audit rule expansion: `dead-runtime-shim`** — previously only
|
|
1080
|
+
flagged when exports were exactly `{ invoke }` or
|
|
1081
|
+
`{ invoke, createNestedInvokeProxy }`. Updated to detect (a)
|
|
1082
|
+
inline `createNestedInvokeProxy` body via regex (catches the
|
|
1083
|
+
legacy 47-line shape **with** `Runtime` export — which the old
|
|
1084
|
+
heuristic missed entirely; this is the shape every existing
|
|
1085
|
+
VTEX site has) and (b) skip the new Wave-15-A canonical
|
|
1086
|
+
re-export shape (where `import invoke from
|
|
1087
|
+
@decocms/start/sdk` is present and no inline proxy body
|
|
1088
|
+
exists). Auto-fix is gated by `safeToAutoFix` metadata: legacy
|
|
1089
|
+
shim shapes get the rewrite + delete; sites that mix the proxy
|
|
1090
|
+
with custom helpers get a warning only. Three new tests.
|
|
1091
|
+
**Verified against casaevideo-storefront**: now flags
|
|
1092
|
+
`[invoke, Runtime] inline createNestedInvokeProxy body` (was
|
|
1093
|
+
missed entirely before).
|
|
1094
|
+
- **Skill: `references/platform-hooks-factories.md`** — new
|
|
1095
|
+
canonical doc covering `createUseCart` / `createUseUser` /
|
|
1096
|
+
`createUseWishlist`. Replaces the pre-W12 manual approach of
|
|
1097
|
+
hand-rolling 200+ LOC of `createServerFn` wrappers per site.
|
|
1098
|
+
Documents the 5-line shim shape, why factories instead of
|
|
1099
|
+
direct hook imports (state isolation per site), non-VTEX
|
|
1100
|
+
stubs using `@decocms/start/sdk/signal`, and the migration
|
|
1101
|
+
path off the manual approach.
|
|
1102
|
+
- **Skill update: `references/platform-hooks/README.md`** —
|
|
1103
|
+
retained as legacy reference but now opens with a
|
|
1104
|
+
"deprecated, see canonical" header pointing at the new doc.
|
|
1105
|
+
The pre-W12 `createServerFn` examples are kept for sites that
|
|
1106
|
+
haven't migrated to factories yet.
|
|
1107
|
+
- **`SKILL.md` index update** — Phase 5 entry now points to the
|
|
1108
|
+
factories doc; reference table lists both new + legacy paths.
|
|
1109
|
+
- 342 → 345 tests pass, typecheck clean, smoke against casaevideo
|
|
1110
|
+
+ baggagio confirms expanded rule fires correctly on legacy
|
|
1111
|
+
shapes and stays silent on baggagio (no `runtime.ts` file there).
|
|
1112
|
+
|
|
1113
|
+
**Deferred to Wave 15-B / 16 (intentionally — see discoveries journal):**
|
|
1114
|
+
|
|
1115
|
+
- **G3** — promote the 170-LOC `invoke.gen.ts` VTEX `createServerFn`
|
|
1116
|
+
wrappers into `@decocms/apps/vtex/server-fns`. Needs research on
|
|
1117
|
+
whether TanStack Start's compiler can transform `createServerFn`
|
|
1118
|
+
call sites that live inside a node_module. Not safe to ship blind.
|
|
1119
|
+
- **H1** — full cookie-passthrough provider wiring
|
|
1120
|
+
(`setRequestCookieProvider` / `setResponseCookieForwarder` in
|
|
1121
|
+
apps-start, auto-wire in `templates/setup.ts`). The
|
|
1122
|
+
`cookiePassthrough.ts` docstring already references this design but
|
|
1123
|
+
the apps-start side doesn't exist. Needs a design pass to scope
|
|
1124
|
+
what calls inside `vtex/utils/fetch.ts` need the provider hook
|
|
1125
|
+
(currently each call site forwards cookies manually).
|
|
1126
|
+
- **Cross-site convergence promotions** (5 items) — `useSuggestions`,
|
|
1127
|
+
`useOffer` factory, `Picture` API unification, redirect
|
|
1128
|
+
`useSendEvent`/`clx`/location-matcher imports, `relative()`
|
|
1129
|
+
SKU-stripping extension. Sequenced after 15-A merges.
|
|
1130
|
+
|
|
987
1131
|
### Wave 15+ (htmx cleanup PRs on als + propagation to other sites) — Priority 3 / 4
|
|
988
1132
|
|
|
989
1133
|
Each htmx pattern that survives the codemod becomes a per-pattern PR
|
package/package.json
CHANGED
|
@@ -462,6 +462,31 @@ function findAttachedLeadingComments(content: string, openIdx: number): number {
|
|
|
462
462
|
/* Rule 3 — dead `src/runtime.ts` invoke shim */
|
|
463
463
|
/* ------------------------------------------------------------------ */
|
|
464
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Detection covers two shapes of `src/runtime.ts`:
|
|
467
|
+
*
|
|
468
|
+
* 1. Legacy inline proxy (pre-Wave 15-A migration template) — defines
|
|
469
|
+
* `createNestedInvokeProxy` plus `invoke` and `Runtime` constants.
|
|
470
|
+
* The whole 40-50 LOC body duplicates `@decocms/start/sdk`'s `invoke`.
|
|
471
|
+
*
|
|
472
|
+
* 2. Simple re-export shim — the file only re-exports `invoke` /
|
|
473
|
+
* `createNestedInvokeProxy` (no inline proxy body, but also not yet
|
|
474
|
+
* pointing at `@decocms/start/sdk`).
|
|
475
|
+
*
|
|
476
|
+
* Both should be replaced with `import { invoke } from "@decocms/start/sdk"`
|
|
477
|
+
* at every callsite, and the file deleted. The Wave 15-A migration template
|
|
478
|
+
* scaffolds a thin re-export form that's also acceptable (re-exports
|
|
479
|
+
* `invoke` from `@decocms/start/sdk` and rebuilds `Runtime = { invoke }`);
|
|
480
|
+
* we explicitly skip it via the "imports invoke from @decocms/start/sdk
|
|
481
|
+
* AND no inline proxy" check below.
|
|
482
|
+
*/
|
|
483
|
+
const INLINE_PROXY_RE =
|
|
484
|
+
/(?:function|const)\s+createNestedInvokeProxy\b|new\s+Proxy\s*\(\s*Object\.assign\s*\(\s*async\s*\(\s*props/;
|
|
485
|
+
const FRAMEWORK_INVOKE_IMPORT_RE =
|
|
486
|
+
/import\s+\{[^}]*\binvoke\b[^}]*\}\s+from\s+['"]@decocms\/start(?:\/sdk)?['"]/;
|
|
487
|
+
|
|
488
|
+
const ALLOWED_RUNTIME_EXPORTS = new Set(["invoke", "createNestedInvokeProxy", "Runtime"]);
|
|
489
|
+
|
|
465
490
|
const ruleDeadRuntimeShim: Rule = {
|
|
466
491
|
id: "dead-runtime-shim",
|
|
467
492
|
title: "Dead src/runtime.ts invoke shim",
|
|
@@ -469,24 +494,54 @@ const ruleDeadRuntimeShim: Rule = {
|
|
|
469
494
|
const abs = `${siteDir}/src/runtime.ts`;
|
|
470
495
|
if (!fs.exists(abs)) return [];
|
|
471
496
|
const content = fs.readText(abs);
|
|
472
|
-
|
|
473
|
-
|
|
497
|
+
|
|
498
|
+
const hasInlineProxy = INLINE_PROXY_RE.test(content);
|
|
499
|
+
const reExportsFromFramework = FRAMEWORK_INVOKE_IMPORT_RE.test(content);
|
|
474
500
|
const exports = extractExports(content);
|
|
475
|
-
const
|
|
476
|
-
exports.length > 0 && exports.every((e) =>
|
|
477
|
-
|
|
501
|
+
const onlyKnownInvokeExports =
|
|
502
|
+
exports.length > 0 && exports.every((e) => ALLOWED_RUNTIME_EXPORTS.has(e));
|
|
503
|
+
|
|
504
|
+
// Wave 15-A canonical template: re-exports invoke from @decocms/start/sdk
|
|
505
|
+
// and exposes `Runtime = { invoke }` for legacy callers. No inline proxy
|
|
506
|
+
// body. This is the desired shape — skip.
|
|
507
|
+
if (reExportsFromFramework && !hasInlineProxy) return [];
|
|
508
|
+
|
|
509
|
+
// Site-specific helpers alongside invoke: don't flag — the file has its
|
|
510
|
+
// own purpose beyond shimming. (Old behavior preserved.)
|
|
511
|
+
if (!hasInlineProxy && !onlyKnownInvokeExports) return [];
|
|
512
|
+
|
|
513
|
+
const exportSummary = exports.length > 0 ? exports.join(", ") : "(re-exports only)";
|
|
514
|
+
const flavor = hasInlineProxy ? "inline createNestedInvokeProxy body" : "shim re-exports";
|
|
515
|
+
// Only safe to auto-delete when exports are pure invoke surface; if a
|
|
516
|
+
// legacy file mixes the inline proxy with custom helpers, we still flag
|
|
517
|
+
// it but skip the destructive fix.
|
|
518
|
+
const safeToAutoFix = onlyKnownInvokeExports;
|
|
519
|
+
|
|
478
520
|
return [
|
|
479
521
|
{
|
|
480
522
|
rule: "dead-runtime-shim",
|
|
481
523
|
severity: "info",
|
|
482
524
|
file: "src/runtime.ts",
|
|
483
|
-
message:
|
|
484
|
-
|
|
525
|
+
message: safeToAutoFix
|
|
526
|
+
? `${flavor} [${exportSummary}] — replace with @decocms/start/sdk`
|
|
527
|
+
: `${flavor} [${exportSummary}] — manual review: file mixes the runtime proxy with site-specific exports`,
|
|
528
|
+
fix: safeToAutoFix
|
|
529
|
+
? 'rg -l "from \\"~/runtime\\"" src/ | xargs sed -i \'\' \'s|from "~/runtime"|from "@decocms/start/sdk"|g\' && rm src/runtime.ts'
|
|
530
|
+
: "Move the inline `createNestedInvokeProxy` body to call @decocms/start/sdk's `invoke`; relocate site-specific helpers to a dedicated module before deleting src/runtime.ts",
|
|
531
|
+
meta: {
|
|
532
|
+
hasInlineProxy,
|
|
533
|
+
exports,
|
|
534
|
+
safeToAutoFix,
|
|
535
|
+
},
|
|
485
536
|
},
|
|
486
537
|
];
|
|
487
538
|
},
|
|
488
539
|
applyFix(ctx, findings, writer): FixAction[] {
|
|
489
540
|
if (findings.length === 0) return [];
|
|
541
|
+
// Honor the per-finding safety gate emitted by run() — never auto-delete
|
|
542
|
+
// a runtime.ts that mixes the proxy with site-specific helpers.
|
|
543
|
+
const safe = findings.every((f) => f.meta?.safeToAutoFix !== false);
|
|
544
|
+
if (!safe) return [];
|
|
490
545
|
const updated = rewriteImportSpec(ctx, writer, "~/runtime", "@decocms/start/sdk");
|
|
491
546
|
writer.deleteFile(`${ctx.siteDir}/src/runtime.ts`);
|
|
492
547
|
return [
|
|
@@ -227,9 +227,11 @@ describe("rule: dead-runtime-shim", () => {
|
|
|
227
227
|
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
228
228
|
expect(r.findings).toHaveLength(1);
|
|
229
229
|
expect(r.findings[0].file).toBe("src/runtime.ts");
|
|
230
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
|
|
231
|
+
expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
|
|
230
232
|
});
|
|
231
233
|
|
|
232
|
-
it("does not flag a runtime.ts that exports site-specific helpers", () => {
|
|
234
|
+
it("does not flag a runtime.ts that exports site-specific helpers (no inline proxy)", () => {
|
|
233
235
|
const fs = makeFs({
|
|
234
236
|
"/site/src/runtime.ts": "export const invoke = {};\nexport const customHelper = () => 1;\n",
|
|
235
237
|
});
|
|
@@ -237,6 +239,80 @@ describe("rule: dead-runtime-shim", () => {
|
|
|
237
239
|
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
238
240
|
expect(r.findings).toEqual([]);
|
|
239
241
|
});
|
|
242
|
+
|
|
243
|
+
it("does NOT flag the Wave 15-A canonical re-export shape", () => {
|
|
244
|
+
// The migration template now scaffolds a thin re-export from
|
|
245
|
+
// @decocms/start/sdk plus a Runtime alias. No inline proxy body.
|
|
246
|
+
const fs = makeFs({
|
|
247
|
+
"/site/src/runtime.ts":
|
|
248
|
+
'import { invoke } from "@decocms/start/sdk";\nexport { invoke };\nexport const Runtime = { invoke };\n',
|
|
249
|
+
});
|
|
250
|
+
const report = runAudit(SITE, fs);
|
|
251
|
+
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
252
|
+
expect(r.findings).toEqual([]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("flags the legacy 47-line inline createNestedInvokeProxy body (with Runtime export)", () => {
|
|
256
|
+
// The pre-Wave-15-A migration template emitted a full Proxy body
|
|
257
|
+
// alongside `Runtime = { invoke }`. The earlier rule heuristic
|
|
258
|
+
// missed this shape because `Runtime` was not in its allowlist.
|
|
259
|
+
const fs = makeFs({
|
|
260
|
+
"/site/src/runtime.ts": `
|
|
261
|
+
function createNestedInvokeProxy(path: string[] = []): any {
|
|
262
|
+
return new Proxy(
|
|
263
|
+
Object.assign(async (props: any) => {
|
|
264
|
+
const key = path.join("/");
|
|
265
|
+
const response = await fetch(\`/deco/invoke/\${key}\`, {
|
|
266
|
+
method: "POST",
|
|
267
|
+
headers: { "Content-Type": "application/json" },
|
|
268
|
+
body: JSON.stringify(props ?? {}),
|
|
269
|
+
});
|
|
270
|
+
return response.json();
|
|
271
|
+
}, {}),
|
|
272
|
+
{
|
|
273
|
+
get(_target: any, prop: string) {
|
|
274
|
+
if (prop === "then") return undefined;
|
|
275
|
+
return createNestedInvokeProxy([...path, prop]);
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export const invoke = createNestedInvokeProxy() as any;
|
|
282
|
+
export const Runtime = { invoke };
|
|
283
|
+
`,
|
|
284
|
+
});
|
|
285
|
+
const report = runAudit(SITE, fs);
|
|
286
|
+
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
287
|
+
expect(r.findings).toHaveLength(1);
|
|
288
|
+
expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
|
|
289
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(true);
|
|
290
|
+
expect(r.findings[0].message).toContain("inline createNestedInvokeProxy body");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("flags but does NOT auto-fix when inline proxy coexists with site-specific helpers", () => {
|
|
294
|
+
// Defensive: if a site has hand-tuned the runtime file with extra
|
|
295
|
+
// exports beyond invoke/Runtime, deletion would lose data. Surface
|
|
296
|
+
// the issue but skip the destructive fix.
|
|
297
|
+
const fs = makeFs({
|
|
298
|
+
"/site/src/runtime.ts": `
|
|
299
|
+
function createNestedInvokeProxy(path: string[] = []): any {
|
|
300
|
+
return new Proxy(Object.assign(async (props: any) => {}, {}), {});
|
|
301
|
+
}
|
|
302
|
+
export const invoke = createNestedInvokeProxy();
|
|
303
|
+
export const trackPageView = () => console.log("custom tracker");
|
|
304
|
+
`,
|
|
305
|
+
});
|
|
306
|
+
const report = runAudit(SITE, fs);
|
|
307
|
+
const r = report.rules.find((r) => r.rule === "dead-runtime-shim")!;
|
|
308
|
+
expect(r.findings).toHaveLength(1);
|
|
309
|
+
expect(r.findings[0].meta?.hasInlineProxy).toBe(true);
|
|
310
|
+
expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
|
|
311
|
+
expect(r.findings[0].message).toContain("manual review");
|
|
312
|
+
// applyFix should be a no-op when safeToAutoFix is false — verified
|
|
313
|
+
// implicitly by the runner test for --fix below; here we only
|
|
314
|
+
// assert the metadata gate.
|
|
315
|
+
});
|
|
240
316
|
});
|
|
241
317
|
|
|
242
318
|
describe("rule: site-local-with-globals", () => {
|
|
@@ -204,7 +204,8 @@ export function generateCommerceLoaders(ctx: MigrationContext): string {
|
|
|
204
204
|
lines.push(` breadcrumb: createBreadcrumbFromPath(url.pathname, url, collection.name) ?? {},`);
|
|
205
205
|
lines.push(` seo: {`);
|
|
206
206
|
lines.push(` title: collection.name,`);
|
|
207
|
-
lines.push(`
|
|
207
|
+
lines.push(` // MIGRATION TODO: replace with site-specific category description`);
|
|
208
|
+
lines.push(` description: collection.name,`);
|
|
208
209
|
lines.push(` noIndexing: false,`);
|
|
209
210
|
lines.push(` canonical: url.toString(),`);
|
|
210
211
|
lines.push(` },`);
|
|
@@ -67,8 +67,10 @@ import { DecoRootLayout } from "@decocms/start/hooks";
|
|
|
67
67
|
// @ts-ignore Vite ?url import
|
|
68
68
|
import appCss from "../styles/app.css?url";
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
// MIGRATION TODO: customize description, OG image, and locale for ${siteTitle}.
|
|
71
|
+
// The migration scaffold leaves a generic default so it never falls through;
|
|
72
|
+
// CMS \`Site.seo\` overrides this once block resolution kicks in.
|
|
73
|
+
const DEFAULT_DESCRIPTION = "${siteTitle}";
|
|
72
74
|
|
|
73
75
|
export const Route = createRootRoute({
|
|
74
76
|
head: () => ({
|
|
@@ -108,11 +110,14 @@ function generateIndex(ctx: MigrationContext, siteTitle: string): string {
|
|
|
108
110
|
import { cmsHomeRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
109
111
|
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
110
112
|
|
|
113
|
+
// MIGRATION TODO: customize defaultTitle / defaultDescription / fallback
|
|
114
|
+
// copy below for ${siteTitle}. CMS \`Site.seo\` overrides these once block
|
|
115
|
+
// resolution kicks in, so leaving the migration scaffold defaults is safe
|
|
116
|
+
// but visible in pre-block-resolution states.
|
|
111
117
|
export const Route = createFileRoute("/")({
|
|
112
118
|
...cmsHomeRouteConfig({
|
|
113
|
-
defaultTitle: "${siteTitle}
|
|
114
|
-
defaultDescription:
|
|
115
|
-
"${siteTitle} - Tudo para sua casa com os melhores preços.",
|
|
119
|
+
defaultTitle: "${siteTitle}",
|
|
120
|
+
defaultDescription: "${siteTitle}",
|
|
116
121
|
siteName: "${siteTitle}",
|
|
117
122
|
}),
|
|
118
123
|
component: HomePage,
|
|
@@ -126,8 +131,7 @@ function HomePage() {
|
|
|
126
131
|
<div className="min-h-screen flex items-center justify-center">
|
|
127
132
|
<div className="text-center">
|
|
128
133
|
<h1 className="text-4xl font-bold mb-4">${siteTitle}</h1>
|
|
129
|
-
<p className="text-
|
|
130
|
-
<p className="text-sm text-base-content/40 mt-2">Nenhuma página CMS encontrada para /</p>
|
|
134
|
+
<p className="text-sm text-base-content/40 mt-2">No CMS page registered for /</p>
|
|
131
135
|
</div>
|
|
132
136
|
</div>
|
|
133
137
|
);
|
|
@@ -152,11 +156,12 @@ function generateCatchAll(ctx: MigrationContext, siteTitle: string): string {
|
|
|
152
156
|
import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
153
157
|
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
154
158
|
|
|
159
|
+
// MIGRATION TODO: customize defaultTitle / defaultDescription for ${siteTitle}
|
|
160
|
+
// (CMS \`Site.seo\` overrides these per-page once block resolution kicks in).
|
|
155
161
|
const routeConfig = cmsRouteConfig({
|
|
156
162
|
siteName: "${siteTitle}",
|
|
157
|
-
defaultTitle: "${siteTitle}
|
|
158
|
-
defaultDescription:
|
|
159
|
-
"${siteTitle} - Tudo para sua casa com os melhores preços.",
|
|
163
|
+
defaultTitle: "${siteTitle}",
|
|
164
|
+
defaultDescription: "${siteTitle}",
|
|
160
165
|
ignoreSearchParams: ["skuId"],
|
|
161
166
|
});
|
|
162
167
|
|
|
@@ -216,50 +216,20 @@ declare module "@tanstack/react-router" {
|
|
|
216
216
|
|
|
217
217
|
function generateRuntime(): string {
|
|
218
218
|
return `/**
|
|
219
|
-
* Runtime invoke proxy.
|
|
219
|
+
* Runtime invoke proxy — re-exports the framework canonical from @decocms/start/sdk.
|
|
220
220
|
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
221
|
+
* The implementation (typed RPC over /deco/invoke, dotted-path proxy, .ts
|
|
222
|
+
* suffix fallback) lives in @decocms/start/sdk/invoke. This file exists so
|
|
223
|
+
* existing site code can keep \`import { invoke } from "~/runtime"\` and
|
|
224
|
+
* \`Runtime.invoke\` shapes without churn.
|
|
225
225
|
*
|
|
226
|
-
*
|
|
227
|
-
* (registered loaders may have ".ts" extensions in their keys).
|
|
226
|
+
* Don't reimplement here — extend @decocms/start/sdk/invoke instead.
|
|
228
227
|
*/
|
|
229
|
-
|
|
230
|
-
return new Proxy(
|
|
231
|
-
Object.assign(async (props: any) => {
|
|
232
|
-
const key = path.join("/");
|
|
233
|
-
for (const k of [key, \`\${key}.ts\`]) {
|
|
234
|
-
const response = await fetch(\`/deco/invoke/\${k}\`, {
|
|
235
|
-
method: "POST",
|
|
236
|
-
headers: { "Content-Type": "application/json" },
|
|
237
|
-
body: JSON.stringify(props ?? {}),
|
|
238
|
-
});
|
|
239
|
-
if (response.status === 404) continue;
|
|
240
|
-
if (!response.ok) {
|
|
241
|
-
throw new Error(\`invoke(\${k}) failed: \${response.status}\`);
|
|
242
|
-
}
|
|
243
|
-
return response.json();
|
|
244
|
-
}
|
|
245
|
-
throw new Error(\`invoke(\${key}) failed: handler not found\`);
|
|
246
|
-
}, {}),
|
|
247
|
-
{
|
|
248
|
-
get(_target: any, prop: string) {
|
|
249
|
-
if (prop === "then" || prop === "catch" || prop === "finally") {
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
|
-
return createNestedInvokeProxy([...path, prop]);
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
);
|
|
256
|
-
}
|
|
228
|
+
import { invoke } from "@decocms/start/sdk";
|
|
257
229
|
|
|
258
|
-
export
|
|
230
|
+
export { invoke };
|
|
259
231
|
|
|
260
|
-
export const Runtime = {
|
|
261
|
-
invoke,
|
|
262
|
-
};
|
|
232
|
+
export const Runtime = { invoke };
|
|
263
233
|
`;
|
|
264
234
|
}
|
|
265
235
|
|
|
@@ -299,11 +269,8 @@ export const invoke = {} as const;
|
|
|
299
269
|
* auto-generated in invoke.gen.ts. Run \`npm run generate:invoke\` to update.
|
|
300
270
|
*/
|
|
301
271
|
import { createServerFn } from "@tanstack/react-start";
|
|
302
|
-
import {
|
|
303
|
-
|
|
304
|
-
getResponseHeaders,
|
|
305
|
-
setResponseHeader,
|
|
306
|
-
} from "@tanstack/react-start/server";
|
|
272
|
+
import { getRequestHeader } from "@tanstack/react-start/server";
|
|
273
|
+
import { forwardResponseCookies } from "@decocms/start/sdk/cookiePassthrough";
|
|
307
274
|
import { vtexActions } from "./invoke.gen";
|
|
308
275
|
${hasVtexAuthLoader ? `import vtexAuthLoader from "../loaders/vtex-auth-loader";\n` : ""}import {
|
|
309
276
|
extractVtexCookiesFromHeader,
|
|
@@ -314,15 +281,6 @@ ${hasVtexAuthLoader ? `import vtexAuthLoader from "../loaders/vtex-auth-loader";
|
|
|
314
281
|
|
|
315
282
|
export type { OrderForm } from "./invoke.gen";
|
|
316
283
|
|
|
317
|
-
function mergeSetCookies(newCookies: string[]): void {
|
|
318
|
-
if (newCookies.length === 0) return;
|
|
319
|
-
const existing: string[] =
|
|
320
|
-
typeof getResponseHeaders().getSetCookie === "function"
|
|
321
|
-
? getResponseHeaders().getSetCookie()
|
|
322
|
-
: [];
|
|
323
|
-
setResponseHeader("set-cookie", [...existing, ...newCookies]);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
284
|
function getVtexCookies(): string {
|
|
327
285
|
return extractVtexCookiesFromHeader(getRequestHeader("cookie") ?? "");
|
|
328
286
|
}
|
|
@@ -336,7 +294,7 @@ ${hasVtexAuthLoader ? `const _vtexAuth = createServerFn({ method: "POST" })
|
|
|
336
294
|
} as any);
|
|
337
295
|
if (result instanceof Response) {
|
|
338
296
|
const setCookies = result.headers.getSetCookie?.() ?? [];
|
|
339
|
-
|
|
297
|
+
forwardResponseCookies(stripCookieDomain(setCookies));
|
|
340
298
|
return result.json();
|
|
341
299
|
}
|
|
342
300
|
return result;
|
|
@@ -344,7 +302,7 @@ ${hasVtexAuthLoader ? `const _vtexAuth = createServerFn({ method: "POST" })
|
|
|
344
302
|
` : ""}const _logout = createServerFn({ method: "POST" }).handler(
|
|
345
303
|
async (): Promise<{ success: boolean }> => {
|
|
346
304
|
const { setCookies } = await performVtexLogout(getVtexCookies());
|
|
347
|
-
|
|
305
|
+
forwardResponseCookies(setCookies);
|
|
348
306
|
return { success: true };
|
|
349
307
|
},
|
|
350
308
|
);
|
|
@@ -53,41 +53,6 @@ export default defineConfig({
|
|
|
53
53
|
}),
|
|
54
54
|
tailwindcss(),
|
|
55
55
|
decoVitePlugin(),
|
|
56
|
-
{
|
|
57
|
-
name: "site-manual-chunks",
|
|
58
|
-
config(_cfg, { command }) {
|
|
59
|
-
if (command !== "build") return;
|
|
60
|
-
return {
|
|
61
|
-
build: {
|
|
62
|
-
rollupOptions: {
|
|
63
|
-
output: {
|
|
64
|
-
manualChunks(id: string) {
|
|
65
|
-
if (id.includes("node_modules/react-dom") || id.includes("node_modules/react/"))
|
|
66
|
-
return "vendor-react";
|
|
67
|
-
if (id.includes("@tanstack/react-router") || id.includes("@tanstack/start"))
|
|
68
|
-
return "vendor-router";
|
|
69
|
-
if (id.includes("@tanstack/react-query")) return "vendor-query";
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: "deco-stub-meta-gen",
|
|
79
|
-
enforce: "pre" as const,
|
|
80
|
-
resolveId(id, importer, options) {
|
|
81
|
-
if (!options?.ssr && importer && id.includes("meta.gen")) {
|
|
82
|
-
return "\\0stub:meta-gen";
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
load(id) {
|
|
86
|
-
if (id === "\\0stub:meta-gen") {
|
|
87
|
-
return "export default {};";
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
56
|
],
|
|
92
57
|
build: {
|
|
93
58
|
sourcemap: "hidden",
|