@decocms/start 2.22.0 → 2.24.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/.agents/skills/deco-to-tanstack-migration/references/post-migration-cleanup.md +64 -9
- package/MIGRATION_TOOLING_PLAN.md +235 -0
- package/package.json +1 -1
- package/scripts/migrate/post-cleanup/rules.ts +239 -7
- package/scripts/migrate/post-cleanup/runner.test.ts +268 -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-post-cleanup.ts +4 -2
|
@@ -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,189 @@ 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
|
+
|
|
1131
|
+
### Wave 15-B-1 (cross-site convergence — `local-framework-duplicate` audit rule) — 🟡 **IN FLIGHT**
|
|
1132
|
+
|
|
1133
|
+
First slice of the cross-site-convergence backlog deferred from
|
|
1134
|
+
Wave 15-A. Concrete data first (per the user-rule "verify before
|
|
1135
|
+
designing"): the `useSendEvent`/`clx`/location-matcher promotion
|
|
1136
|
+
turned out to be *not* a "promote site code → framework" exercise.
|
|
1137
|
+
The framework already has each helper. The work is **enforcing the
|
|
1138
|
+
existing canonical** when sites carry their own copy.
|
|
1139
|
+
|
|
1140
|
+
Verified state (2026-05-01 grep against both sites):
|
|
1141
|
+
|
|
1142
|
+
| Item | casaevideo | baggagio | Action |
|
|
1143
|
+
|---|---|---|---|
|
|
1144
|
+
| `src/sdk/clx.ts` | absent (already canonical) | present, identical body + dead `clsx` alias (zero callers) | pure dup → **auto-fix** |
|
|
1145
|
+
| `src/sdk/useSendEvent.ts` | absent | present, **stricter** typing (`<E extends AnalyticsEvent>` generic) vs framework's permissive shape | **warn-only** (replacing 1:1 weakens types) |
|
|
1146
|
+
| `src/matchers/location.ts` | present, cookie-only subset of framework | absent | **warn-only** (framework's `registerBuiltinMatchers()` is a behavior superset; needs per-site verification of country-name lookup parity) |
|
|
1147
|
+
|
|
1148
|
+
So this is exactly *one* mechanically-applicable fix (`clx` in
|
|
1149
|
+
baggagio) plus two judgement calls. Hand-applying would be cheap;
|
|
1150
|
+
the value is making the audit *enforce* the convergence so the next
|
|
1151
|
+
copy-paste regression on any future site gets caught automatically.
|
|
1152
|
+
|
|
1153
|
+
**Shipped (one PR against `decocms/deco-start`):**
|
|
1154
|
+
|
|
1155
|
+
38. `feat(migrate): local-framework-duplicate audit rule with registry-driven enforcement` 🟡 **WAITING ON CI**
|
|
1156
|
+
- **New rule `local-framework-duplicate`** in
|
|
1157
|
+
`scripts/migrate/post-cleanup/rules.ts` driven by an exported
|
|
1158
|
+
`FRAMEWORK_DUPLICATES` registry. Each entry is `{ id,
|
|
1159
|
+
sitePath, canonicalImport, contentSignature: RegExp[],
|
|
1160
|
+
safeToAutoFix, reason?, description }`. The rule fires only
|
|
1161
|
+
when **every** content-signature regex matches the site file
|
|
1162
|
+
— conservative on purpose so genuinely-forked helpers are
|
|
1163
|
+
skipped.
|
|
1164
|
+
- **Auto-fix path** (when `safeToAutoFix: true`): rewrite all
|
|
1165
|
+
`from "~/<derived>"` importers to `from
|
|
1166
|
+
"<canonicalImport>"` via the existing `rewriteImportSpec`
|
|
1167
|
+
helper, then delete the file. Already-canonical importers
|
|
1168
|
+
are left untouched.
|
|
1169
|
+
- **Warn-only path** (when `safeToAutoFix: false`): rule still
|
|
1170
|
+
fires + populates the finding's `fix:` field with the
|
|
1171
|
+
`reason` so engineers see *why* auto-fix is gated and what
|
|
1172
|
+
they need to verify before manual cleanup.
|
|
1173
|
+
- **Three initial entries** in the registry, mapped 1:1 to the
|
|
1174
|
+
cross-site audit findings:
|
|
1175
|
+
| id | site path | canonical | auto-fix? |
|
|
1176
|
+
|---|---|---|---|
|
|
1177
|
+
| `clx` | `src/sdk/clx.ts` | `@decocms/start/sdk/clx` | **yes** |
|
|
1178
|
+
| `use-send-event` | `src/sdk/useSendEvent.ts` | `@decocms/start/sdk/analytics` | no (typing regression) |
|
|
1179
|
+
| `location-matcher` | `src/matchers/location.ts` | `@decocms/start/matchers/builtins` | no (behavior superset, parity check needed) |
|
|
1180
|
+
- **11 new tests** covering: pure-dup detection, fork detection
|
|
1181
|
+
(signature mismatch → no flag), warn-only entries, severity
|
|
1182
|
+
uniformity (warning for both kinds, so `--strict` gates
|
|
1183
|
+
everything), auto-fix happy-path (delete + rewrite both
|
|
1184
|
+
importers, leave canonical importers alone), warn-only
|
|
1185
|
+
auto-fix is a no-op (does NOT delete partial-overlap files),
|
|
1186
|
+
mixed coexistence (auto-fixable `clx` and warn-only
|
|
1187
|
+
`useSendEvent` in the same tree → only `clx` gets auto-fixed),
|
|
1188
|
+
`supportsAutoFix` flag is true (since rule has `applyFix`).
|
|
1189
|
+
- **CLI help text + `post-migration-cleanup.md` § 8** updated
|
|
1190
|
+
with the new rule's table and the "adding a new entry"
|
|
1191
|
+
section. Old § 8 (orphan TODO comments) renumbered to § 9.
|
|
1192
|
+
- 345 → 353 tests pass, typecheck clean, end-to-end disk smoke
|
|
1193
|
+
against a temp fixture confirmed: 2 importers rewritten + 1
|
|
1194
|
+
file deleted in one `--fix` run.
|
|
1195
|
+
- **Real-site smoke**:
|
|
1196
|
+
- **baggagio**: rule fires twice — `clx.ts` (auto-fixable),
|
|
1197
|
+
`useSendEvent.ts` (warn-only with the typed-generic reason).
|
|
1198
|
+
- **casaevideo**: rule fires once — `location.ts` (warn-only
|
|
1199
|
+
with the `registerBuiltinMatchers()` adoption hint).
|
|
1200
|
+
- **Net**: every future site that copy-pastes any of these three
|
|
1201
|
+
files gets a tight audit finding + auto-fix on the safe one.
|
|
1202
|
+
The registry pattern means adding a 4th cross-site duplicate
|
|
1203
|
+
is a single object literal — no new rule, no new tests
|
|
1204
|
+
scaffolding, no new doc section.
|
|
1205
|
+
|
|
1206
|
+
**Still in the cross-site backlog (sequenced behind 15-B-1):**
|
|
1207
|
+
|
|
1208
|
+
- **15-B-2** — `useSuggestions` framework helper (new export in
|
|
1209
|
+
`@decocms/start/sdk` typed by `Resolved<T>`, optional Sentry
|
|
1210
|
+
hook). Sites adopt incrementally; once 2+ adopt, add a registry
|
|
1211
|
+
entry pointing the legacy hand-rolled implementations at the
|
|
1212
|
+
canonical via `local-framework-duplicate`.
|
|
1213
|
+
- **15-B-3** — `useOffer` factory (D4 candidate; needs design pass
|
|
1214
|
+
for PIX/installment plugin slots).
|
|
1215
|
+
- **15-B-4** — `Picture` API unification (breaking; needs a
|
|
1216
|
+
picking-the-winner pass between casaevideo's and baggagio's
|
|
1217
|
+
shapes, plus a codemod for call sites).
|
|
1218
|
+
- **15-B-5** — `relative()` SKU-stripping option in
|
|
1219
|
+
`@decocms/apps/commerce/sdk/url`. Apps-side change; sites delete
|
|
1220
|
+
their wrapper.
|
|
1221
|
+
|
|
987
1222
|
### Wave 15+ (htmx cleanup PRs on als + propagation to other sites) — Priority 3 / 4
|
|
988
1223
|
|
|
989
1224
|
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 [
|
|
@@ -888,6 +943,181 @@ const ruleFrameworkTodos: Rule = {
|
|
|
888
943
|
},
|
|
889
944
|
};
|
|
890
945
|
|
|
946
|
+
/* ------------------------------------------------------------------ */
|
|
947
|
+
/* Rule — `local-framework-duplicate` — site-local copy of fwk code */
|
|
948
|
+
/* ------------------------------------------------------------------ */
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Registry of files we expect sites to NOT carry locally because the
|
|
952
|
+
* canonical implementation already lives in `@decocms/start` (or a
|
|
953
|
+
* sibling apps package).
|
|
954
|
+
*
|
|
955
|
+
* Two flavours:
|
|
956
|
+
* - `safeToAutoFix: true` — site file is a behaviour-equivalent dup
|
|
957
|
+
* of the framework export. `--fix` rewrites every `from "~/<path>"`
|
|
958
|
+
* importer to `from "<canonicalImport>"` and deletes the file.
|
|
959
|
+
* - `safeToAutoFix: false` — site file *overlaps* with framework code
|
|
960
|
+
* but isn't a clean drop-in (different typing, partial coverage,
|
|
961
|
+
* stricter behaviour, etc.). The rule still flags it so the entry
|
|
962
|
+
* surfaces in audits, but never deletes — the `reason` explains why
|
|
963
|
+
* a human has to make the call.
|
|
964
|
+
*
|
|
965
|
+
* `contentSignature` regexes ALL must match the site file's contents
|
|
966
|
+
* before the rule fires. They are deliberately specific enough to
|
|
967
|
+
* avoid catching forks that happen to share a filename but have
|
|
968
|
+
* diverged.
|
|
969
|
+
*/
|
|
970
|
+
interface FrameworkDuplicate {
|
|
971
|
+
/** Stable id surfaced in finding meta and CLI/JSON output. */
|
|
972
|
+
id: string;
|
|
973
|
+
/** Site-relative path of the duplicated file (e.g. "src/sdk/clx.ts"). */
|
|
974
|
+
sitePath: string;
|
|
975
|
+
/** Canonical import to rewrite to. */
|
|
976
|
+
canonicalImport: string;
|
|
977
|
+
/**
|
|
978
|
+
* Heuristic content fingerprint. The site file must match every
|
|
979
|
+
* regex for the rule to consider it the framework dup.
|
|
980
|
+
*/
|
|
981
|
+
contentSignature: RegExp[];
|
|
982
|
+
/**
|
|
983
|
+
* When true, the rule's `applyFix` will rewrite all importers and
|
|
984
|
+
* delete the file. When false, the rule emits a warning only —
|
|
985
|
+
* `reason` explains the manual judgement required.
|
|
986
|
+
*/
|
|
987
|
+
safeToAutoFix: boolean;
|
|
988
|
+
/**
|
|
989
|
+
* Required when `safeToAutoFix: false`. Surfaces in the finding's
|
|
990
|
+
* `fix:` field so users see *why* the auto-fix is gated.
|
|
991
|
+
*/
|
|
992
|
+
reason?: string;
|
|
993
|
+
/**
|
|
994
|
+
* Human-readable one-liner shown in the finding message and used
|
|
995
|
+
* to compose the `fix:` hint when auto-fixable.
|
|
996
|
+
*/
|
|
997
|
+
description: string;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Add an entry here when:
|
|
1002
|
+
* - 1+ migrated sites carry their own copy of code that already
|
|
1003
|
+
* exists in `@decocms/start` (or a sibling apps package), AND
|
|
1004
|
+
* - the canonical version is at least feature-equivalent.
|
|
1005
|
+
*
|
|
1006
|
+
* Per D4 in the migration tooling policy, the framework promotion
|
|
1007
|
+
* itself happens at 3+ sites — but once promoted, this registry is
|
|
1008
|
+
* how we *enforce* convergence on the remaining sites.
|
|
1009
|
+
*/
|
|
1010
|
+
export const FRAMEWORK_DUPLICATES: FrameworkDuplicate[] = [
|
|
1011
|
+
{
|
|
1012
|
+
id: "clx",
|
|
1013
|
+
sitePath: "src/sdk/clx.ts",
|
|
1014
|
+
canonicalImport: "@decocms/start/sdk/clx",
|
|
1015
|
+
contentSignature: [
|
|
1016
|
+
/export\s+const\s+clx\s*=/,
|
|
1017
|
+
/args\.filter\(Boolean\)\.join/,
|
|
1018
|
+
],
|
|
1019
|
+
safeToAutoFix: true,
|
|
1020
|
+
description: "src/sdk/clx.ts duplicates @decocms/start/sdk/clx",
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
id: "use-send-event",
|
|
1024
|
+
sitePath: "src/sdk/useSendEvent.ts",
|
|
1025
|
+
canonicalImport: "@decocms/start/sdk/analytics",
|
|
1026
|
+
contentSignature: [
|
|
1027
|
+
/export\s+(?:const|function)\s+useSendEvent/,
|
|
1028
|
+
/data-event/,
|
|
1029
|
+
/encodeURIComponent/,
|
|
1030
|
+
],
|
|
1031
|
+
safeToAutoFix: false,
|
|
1032
|
+
reason:
|
|
1033
|
+
"site copy uses a typed AnalyticsEvent generic; the framework export is permissive. " +
|
|
1034
|
+
"Replacing 1:1 weakens type-safety. Either widen the framework export (preferred), or " +
|
|
1035
|
+
"rewrite call sites to drop the generic. Manual review required.",
|
|
1036
|
+
description:
|
|
1037
|
+
"src/sdk/useSendEvent.ts overlaps with @decocms/start/sdk/analytics → useSendEvent",
|
|
1038
|
+
},
|
|
1039
|
+
{
|
|
1040
|
+
id: "location-matcher",
|
|
1041
|
+
sitePath: "src/matchers/location.ts",
|
|
1042
|
+
canonicalImport: "@decocms/start/matchers/builtins",
|
|
1043
|
+
contentSignature: [
|
|
1044
|
+
/registerMatcher\(\s*['"]website\/matchers\/location\.ts['"]/,
|
|
1045
|
+
/__cf_geo/,
|
|
1046
|
+
],
|
|
1047
|
+
safeToAutoFix: false,
|
|
1048
|
+
reason:
|
|
1049
|
+
"framework's registerBuiltinMatchers() ships a richer location matcher (request.cf + " +
|
|
1050
|
+
"geo cookies + headers + 10 sibling matchers). Adopting it changes behaviour: " +
|
|
1051
|
+
"verify country-name lookup parity (resolveCountryCode vs site's inline table) and " +
|
|
1052
|
+
"swap setup.ts's customMatchers entry to call registerBuiltinMatchers().",
|
|
1053
|
+
description:
|
|
1054
|
+
"src/matchers/location.ts overlaps with @decocms/start/matchers/builtins → registerBuiltinMatchers()",
|
|
1055
|
+
},
|
|
1056
|
+
];
|
|
1057
|
+
|
|
1058
|
+
const ruleLocalFrameworkDuplicate: Rule = {
|
|
1059
|
+
id: "local-framework-duplicate",
|
|
1060
|
+
title: "Site-local copy of framework code",
|
|
1061
|
+
run({ siteDir, fs }: RuleContext): Finding[] {
|
|
1062
|
+
const findings: Finding[] = [];
|
|
1063
|
+
for (const dup of FRAMEWORK_DUPLICATES) {
|
|
1064
|
+
const abs = `${siteDir}/${dup.sitePath}`;
|
|
1065
|
+
if (!fs.exists(abs)) continue;
|
|
1066
|
+
const content = fs.readText(abs);
|
|
1067
|
+
const matchesAll = dup.contentSignature.every((re) => re.test(content));
|
|
1068
|
+
if (!matchesAll) continue;
|
|
1069
|
+
|
|
1070
|
+
const fixMessage = dup.safeToAutoFix
|
|
1071
|
+
? `Auto-fixable: rewrite \`from "~/${stripExt(dup.sitePath.replace(/^src\//, ""))}"\` → \`from "${dup.canonicalImport}"\` and delete ${dup.sitePath}.`
|
|
1072
|
+
: dup.reason ?? "Manual review required.";
|
|
1073
|
+
|
|
1074
|
+
findings.push({
|
|
1075
|
+
rule: "local-framework-duplicate",
|
|
1076
|
+
severity: "warning",
|
|
1077
|
+
file: dup.sitePath,
|
|
1078
|
+
message: `${dup.description}${dup.safeToAutoFix ? " (pure dup)" : " (partial overlap)"}`,
|
|
1079
|
+
fix: fixMessage,
|
|
1080
|
+
meta: {
|
|
1081
|
+
id: dup.id,
|
|
1082
|
+
canonicalImport: dup.canonicalImport,
|
|
1083
|
+
safeToAutoFix: dup.safeToAutoFix,
|
|
1084
|
+
...(dup.reason ? { reason: dup.reason } : {}),
|
|
1085
|
+
},
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
return findings;
|
|
1089
|
+
},
|
|
1090
|
+
applyFix(ctx, findings, writer): FixAction[] {
|
|
1091
|
+
const actions: FixAction[] = [];
|
|
1092
|
+
for (const f of findings) {
|
|
1093
|
+
const id = f.meta?.id as string | undefined;
|
|
1094
|
+
const safe = f.meta?.safeToAutoFix === true;
|
|
1095
|
+
if (!safe || !id) continue;
|
|
1096
|
+
const dup = FRAMEWORK_DUPLICATES.find((d) => d.id === id);
|
|
1097
|
+
if (!dup) continue;
|
|
1098
|
+
|
|
1099
|
+
const siteImportSpec = `~/${stripExt(dup.sitePath.replace(/^src\//, ""))}`;
|
|
1100
|
+
const updated = rewriteImportSpec(
|
|
1101
|
+
ctx,
|
|
1102
|
+
writer,
|
|
1103
|
+
siteImportSpec,
|
|
1104
|
+
dup.canonicalImport,
|
|
1105
|
+
);
|
|
1106
|
+
writer.deleteFile(`${ctx.siteDir}/${dup.sitePath}`);
|
|
1107
|
+
actions.push({
|
|
1108
|
+
file: dup.sitePath,
|
|
1109
|
+
kind: "rewrite-imports+delete",
|
|
1110
|
+
detail: `rewrote ${updated.length} import(s) "${siteImportSpec}" → "${dup.canonicalImport}" and deleted ${dup.sitePath}`,
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
return actions;
|
|
1114
|
+
},
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
function stripExt(path: string): string {
|
|
1118
|
+
return path.replace(/\.(ts|tsx|js|jsx|mjs)$/, "");
|
|
1119
|
+
}
|
|
1120
|
+
|
|
891
1121
|
/* ------------------------------------------------------------------ */
|
|
892
1122
|
/* Rule 8 — `htmx-residue` — leftover hx-* attrs in migrated src/ */
|
|
893
1123
|
/* ------------------------------------------------------------------ */
|
|
@@ -966,6 +1196,7 @@ export const ALL_RULES: Rule[] = [
|
|
|
966
1196
|
ruleVtexShimRegression,
|
|
967
1197
|
ruleLocalWidgetsTypes,
|
|
968
1198
|
ruleFrameworkTodos,
|
|
1199
|
+
ruleLocalFrameworkDuplicate,
|
|
969
1200
|
ruleHtmxResidue,
|
|
970
1201
|
];
|
|
971
1202
|
|
|
@@ -982,5 +1213,6 @@ export const _internals = {
|
|
|
982
1213
|
ruleHtmxResidue,
|
|
983
1214
|
ruleLocalWidgetsTypes,
|
|
984
1215
|
ruleFrameworkTodos,
|
|
1216
|
+
ruleLocalFrameworkDuplicate,
|
|
985
1217
|
},
|
|
986
1218
|
};
|