@decocms/start 2.21.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/htmx-rewrite.md +21 -0
- 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 +293 -6
- package/package.json +1 -1
- package/scripts/migrate/phase-transform.ts +7 -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
- package/scripts/migrate/transforms/htmx-on-events.test.ts +305 -0
- package/scripts/migrate/transforms/htmx-on-events.ts +193 -0
|
@@ -239,6 +239,88 @@ 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
|
+
|
|
294
|
+
### 2026-05-01 — Wave 14-A rescoped from three codemods to one based on real als data
|
|
295
|
+
|
|
296
|
+
- **Pre-data plan vs post-data plan.** The plan called for three
|
|
297
|
+
htmx codemods (`event-handler`, `form-swap`, `click-swap`).
|
|
298
|
+
After running `deco-htmx-analyze` against als-storefront's
|
|
299
|
+
actual code (210 occurrences across 133 files), only the
|
|
300
|
+
`event-handler` bucket (88 occurrences, 42 %) genuinely admits
|
|
301
|
+
a mechanical rewrite — the other buckets need per-call-site
|
|
302
|
+
product decisions a codemod cannot encode. **Decided: ship
|
|
303
|
+
one codemod (W14-A: `htmx-on-event-rename`), defer the other
|
|
304
|
+
two to W15+.** Rationale captured in the Wave 14 — discoveries
|
|
305
|
+
block.
|
|
306
|
+
- **Codemod shape generalises:** rename + preserve body +
|
|
307
|
+
conditional file-level TODO. Three outputs, one mechanical,
|
|
308
|
+
one verbatim, one conditional on body-content heuristics. This
|
|
309
|
+
is the shape any future per-pattern codemod should target.
|
|
310
|
+
- **Smoke against the real source tree validated the design in
|
|
311
|
+
five minutes.** 754 files scanned, 71 changed, 98 renames, 67
|
|
312
|
+
TODO injections (94 % of changed files). Without that smoke
|
|
313
|
+
step we'd have shipped blind on edge cases like multi-line
|
|
314
|
+
values, mixed standard + lifecycle hooks on the same element,
|
|
315
|
+
and the colon-vs-dash variants both showing up in the same
|
|
316
|
+
file.
|
|
317
|
+
- **The codemod + audit pair closes another loop.** Same shape
|
|
318
|
+
as W12 (D3 throwing stubs + audit `--fix` for swap-able
|
|
319
|
+
stubs). The codemod removes the mechanical half of the htmx
|
|
320
|
+
surface; the `htmx-residue` audit catches the surviving half
|
|
321
|
+
in CI. Engineers can never silently ship a half-rewritten
|
|
322
|
+
file.
|
|
323
|
+
|
|
242
324
|
### 2026-05-01 — als-storefront surfaces the htmx track + policy reset
|
|
243
325
|
|
|
244
326
|
- **als-storefront is the third migration target and the first
|
|
@@ -834,12 +916,217 @@ analysis, rewrite recipes, and a "rewrite-complete" gate.
|
|
|
834
916
|
rule means changing one file, getting `--strict` and `--json`
|
|
835
917
|
for free.
|
|
836
918
|
|
|
837
|
-
### Wave 14 (htmx
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
919
|
+
### Wave 14 (htmx codemod — Priority 2 part 2) — ✅ **PARTIAL / RESCOPED**
|
|
920
|
+
|
|
921
|
+
After shipping the W13 htmx foundations and gathering real data
|
|
922
|
+
from als-storefront with `deco-htmx-analyze`, the planned three-codemod
|
|
923
|
+
scope was reduced to **one codemod** + **one inventory artefact**.
|
|
924
|
+
The other two codemods (form-swap, click-swap) were deferred to W15+,
|
|
925
|
+
to be designed *after* als migration data exposes which exact
|
|
926
|
+
attribute clusters dominate. **Rationale logged in W14 discoveries.**
|
|
927
|
+
|
|
928
|
+
**Shipped:**
|
|
929
|
+
|
|
930
|
+
- **W14-A** [`deco-start#132`](https://github.com/decocms/deco-start/pull/132) — `feat(migrate): htmx-on-event-rename codemod` ✅ **MERGED**, released as `@decocms/start@2.22.0`.
|
|
931
|
+
Adds `scripts/migrate/transforms/htmx-on-events.ts` to the migrate
|
|
932
|
+
`transforms/` pipeline. Mechanically rewrites `hx-on:event=` and
|
|
933
|
+
`hx-on-event=` (colon + dash variants) to the React equivalent
|
|
934
|
+
for every standard DOM event in `STANDARD_EVENT_MAP` (40 entries:
|
|
935
|
+
click, submit, change, input, key*, mouse*, focus*, drag*, touch*,
|
|
936
|
+
paste/copy/cut, scroll, wheel, load, contextmenu). Handler bodies
|
|
937
|
+
are preserved verbatim. **Idempotent** — running twice is a no-op.
|
|
938
|
+
Two safety hatches: htmx lifecycle events (`hx-on:htmx-*`) and
|
|
939
|
+
unknown custom events left alone (the `htmx-residue` audit catches
|
|
940
|
+
them); a single top-of-file MIGRATION TODO comment is injected
|
|
941
|
+
when the body references Fresh-only globals (`useScript(…)`,
|
|
942
|
+
`globalThis.window.STOREFRONT`, `STOREFRONT.…`) so engineers
|
|
943
|
+
don't ship a syntactically-clean file with broken runtime calls.
|
|
944
|
+
29 unit tests + als-shaped fixtures (AddToBagButton, SearchInput,
|
|
945
|
+
RecoveryPassword form, Footer.tsx). 339/339 pass; typecheck clean.
|
|
946
|
+
htmx-rewrite skill § Pattern 1 cross-references the codemod.
|
|
947
|
+
- **W14-B** [`deco-start#132`](https://github.com/decocms/deco-start/pull/132) — captured the **als-storefront htmx inventory** in this plan (this section) as a fixture for future W15+ codemod design.
|
|
948
|
+
|
|
949
|
+
**Deferred (intentionally) — see Wave 14 discoveries:**
|
|
950
|
+
|
|
951
|
+
- ~~**W14-C** codemod `transforms/htmx-form-post-swap.ts`~~ — moved to W15+. The form-swap rewrite is genuinely non-mechanical (per-call-site decisions about optimistic vs pessimistic UI, where to surface loading state, which response handler shape). A speculative codemod would produce React skeletons that still need ~80 % manual work.
|
|
952
|
+
- ~~**W14-D** codemod `transforms/htmx-click-fetch-swap.ts`~~ — moved to W15+. Same logic; on top of that, choosing between local state machine vs sub-route is a routing-architecture decision that varies per page.
|
|
953
|
+
|
|
954
|
+
#### W14-A smoke + als inventory (captured 2026-05-01)
|
|
955
|
+
|
|
956
|
+
The W13-A `deco-htmx-analyze` CLI run against als-storefront's
|
|
957
|
+
production Fresh tree:
|
|
958
|
+
|
|
959
|
+
| Category | Count | % | Notes |
|
|
960
|
+
|---|---:|---:|---|
|
|
961
|
+
| `event-handler` | 88 | 42 % | **Codemoded by W14-A** — mechanical rename |
|
|
962
|
+
| `click-swap` | 64 | 30 % | Manual (W15+) — needs state vs sub-route decision |
|
|
963
|
+
| `form-swap` | 20 | 10 % | Manual (W15+) — needs `useMutation` shape decision |
|
|
964
|
+
| `auto-fetch` | 9 | 4 % | Manual — debounced state + `useQuery` |
|
|
965
|
+
| `oob-swap` | 8 | 4 % | Manual — no 1:1 React equivalent |
|
|
966
|
+
| `unmatched` | 21 | 10 % | Mostly typed-generic noise (`<string>` from `Map<string,X>`) |
|
|
967
|
+
| **Total** | **210** | | across 133 files |
|
|
968
|
+
|
|
969
|
+
W14-A codemod sweep against the same tree (754 ts/tsx files):
|
|
970
|
+
|
|
971
|
+
| Metric | Value |
|
|
972
|
+
|---|---:|
|
|
973
|
+
| Files scanned | 754 |
|
|
974
|
+
| Files changed | 71 |
|
|
975
|
+
| Total `hx-on:*` attributes renamed | 98 |
|
|
976
|
+
| Files getting the MIGRATION TODO | 67 (94 % of changed) |
|
|
977
|
+
|
|
978
|
+
The 98 vs 88 discrepancy is expected: the analyzer counts attribute
|
|
979
|
+
*clusters* per element (an `<input hx-post hx-target hx-on:change>`
|
|
980
|
+
classifies as one `auto-fetch`); the codemod counts individual
|
|
981
|
+
`hx-on:*` attribute renames (the same element gets one rename
|
|
982
|
+
plus the `auto-fetch` cluster left intact for the engineer to
|
|
983
|
+
finish). Net effect: ~98 mechanical wins, leaving ~112 cluster
|
|
984
|
+
rewrites (click-swap + form-swap + auto-fetch + oob-swap +
|
|
985
|
+
unmatched) for the engineer — matching the manual rewrite
|
|
986
|
+
recipes in `references/htmx-rewrite.md`.
|
|
987
|
+
|
|
988
|
+
### Wave 14 — discoveries
|
|
989
|
+
|
|
990
|
+
- **Speculative codemods are over-engineering; data-driven scope
|
|
991
|
+
is better.** The pre-data plan said three codemods (event-handler,
|
|
992
|
+
form-swap, click-swap). After running `deco-htmx-analyze` against
|
|
993
|
+
als-storefront's actual code, only the event-handler bucket
|
|
994
|
+
(88 occurrences, 42 % of the surface) genuinely admits a
|
|
995
|
+
mechanical rewrite. The other two buckets need per-call-site
|
|
996
|
+
product decisions (state machine vs sub-route, optimistic vs
|
|
997
|
+
pessimistic UI, response-handler shape) that a codemod cannot
|
|
998
|
+
encode without producing React skeletons that still need ~80 %
|
|
999
|
+
manual work — net negative versus the recipe in
|
|
1000
|
+
`references/htmx-rewrite.md`. **New rule: codemods come *after*
|
|
1001
|
+
the analyzer data, not before.**
|
|
1002
|
+
- **The smoke-against-real-site step is the design feedback loop.**
|
|
1003
|
+
Running the codemod against als's full 754-file tree (98
|
|
1004
|
+
renames, 71 files changed, 67 with TODO injection) validated
|
|
1005
|
+
three things in five minutes: (a) the rename surface matches
|
|
1006
|
+
the inventory (98 vs 88 ratio explained), (b) the TODO
|
|
1007
|
+
injection rate is high (94 %) — the marker is essential, not
|
|
1008
|
+
defensive, (c) the codemod is idempotent at scale (re-running
|
|
1009
|
+
produces zero diffs). Without this step we'd ship blind.
|
|
1010
|
+
- **The three-output codemod shape (rename + preserve body +
|
|
1011
|
+
conditional TODO) generalises.** Same shape any future
|
|
1012
|
+
per-pattern codemod should target: do the mechanical part,
|
|
1013
|
+
preserve the human-decision-required part, leave a single
|
|
1014
|
+
file-level marker the engineer can grep for. Over-eager
|
|
1015
|
+
body rewriting is what produces the ~80 % manual cleanup load
|
|
1016
|
+
that justifies leaving form-swap / click-swap codemods out for
|
|
1017
|
+
now.
|
|
1018
|
+
- **`htmx-residue` audit + W14-A codemod close another loop.**
|
|
1019
|
+
Same pattern as W12 (D3 throwing stubs + audit `--fix` for
|
|
1020
|
+
swap-able stubs). The codemod removes the easy half of the
|
|
1021
|
+
htmx surface; the audit catches the surviving half. Engineers
|
|
1022
|
+
can never accidentally ship a half-rewritten file: the
|
|
1023
|
+
attribute is either gone (codemod ran, body might still need
|
|
1024
|
+
work — TODO), or it's still there (audit fires in CI).
|
|
1025
|
+
- **als-storefront's profile probably generalises to other htmx
|
|
1026
|
+
sites.** 42 % event-handler is a strong skew toward
|
|
1027
|
+
trivially-mechanical rewrites; even if other sites differ,
|
|
1028
|
+
this codemod alone removes the largest single bucket. If a
|
|
1029
|
+
future site shows 80 % click-swap, *that* would be the cue to
|
|
1030
|
+
build the click-swap codemod — not pre-emptively now.
|
|
1031
|
+
- **Pipeline order matters.** Codemod runs after `transformJsx`
|
|
1032
|
+
(which renames `class` → `className` and `onInput` → `onChange`)
|
|
1033
|
+
and before `transformFreshApis` (which removes `useScript`
|
|
1034
|
+
imports). If `transformFreshApis` ran first, the codemod's
|
|
1035
|
+
TODO marker would still fire (we look for `useScript(` calls,
|
|
1036
|
+
not the import), but the import-removal would create dead
|
|
1037
|
+
references. Order is correct.
|
|
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.
|
|
843
1130
|
|
|
844
1131
|
### Wave 15+ (htmx cleanup PRs on als + propagation to other sites) — Priority 3 / 4
|
|
845
1132
|
|
package/package.json
CHANGED
|
@@ -9,6 +9,7 @@ import { transformFreshApis } from "./transforms/fresh-apis";
|
|
|
9
9
|
import { transformDenoIsms } from "./transforms/deno-isms";
|
|
10
10
|
import { transformTailwind } from "./transforms/tailwind";
|
|
11
11
|
import { transformDeadCode } from "./transforms/dead-code";
|
|
12
|
+
import { transformHtmxOnEvents } from "./transforms/htmx-on-events";
|
|
12
13
|
import { createSectionConventionsTransform } from "./transforms/section-conventions";
|
|
13
14
|
|
|
14
15
|
/** Map of section path → metadata, populated per-run */
|
|
@@ -54,10 +55,15 @@ function applyTransforms(content: string, filePath: string, ctx?: MigrationConte
|
|
|
54
55
|
return { content, changed: false, notes: [] };
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
// Pipeline: imports → jsx → fresh-apis → dead-code → deno-isms → tailwind
|
|
58
|
+
// Pipeline: imports → jsx → htmx-on-events → fresh-apis → dead-code → deno-isms → tailwind
|
|
59
|
+
// htmx-on-events runs after jsx (which renames class/onChange) and
|
|
60
|
+
// before fresh-apis (which removes useScript imports the htmx
|
|
61
|
+
// codemod's TODO might still reference). The codemod is a no-op on
|
|
62
|
+
// files without hx-on, so it never adds latency to non-htmx sites.
|
|
58
63
|
const pipeline: Array<{ name: string; fn: (content: string) => TransformResult }> = [
|
|
59
64
|
{ name: "imports", fn: (c) => transformImports(c, ctx?.islandWrapperTargets) },
|
|
60
65
|
{ name: "jsx", fn: transformJsx },
|
|
66
|
+
{ name: "htmx-on-events", fn: transformHtmxOnEvents },
|
|
61
67
|
{ name: "fresh-apis", fn: transformFreshApis },
|
|
62
68
|
{ name: "dead-code", fn: (c) => transformDeadCode(c, ctx?.platform) },
|
|
63
69
|
{ name: "deno-isms", fn: transformDenoIsms },
|
|
@@ -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
|
|