@decocms/start 2.24.0 → 2.25.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.
@@ -430,6 +430,7 @@ logic, wrapped in something else) are skipped automatically.
430
430
  | `src/sdk/clx.ts` | `@decocms/start/sdk/clx` | yes | Identical implementation; baggagio's extra `clsx` alias has zero callers. |
431
431
  | `src/sdk/useSendEvent.ts` | `@decocms/start/sdk/analytics` | no | Site copy uses `<E extends AnalyticsEvent>` generic; framework export is permissive. Replace 1:1 = type-safety loss. Either widen the framework first or accept the loss. |
432
432
  | `src/matchers/location.ts` | `@decocms/start/matchers/builtins` | no | Framework's `registerBuiltinMatchers()` ships a richer location matcher (`request.cf` first, geo cookies fallback, headers fallback) plus 10 sibling matchers. Adopting changes behaviour — verify country-name lookup parity, swap `setup.ts`'s `customMatchers` entry. |
433
+ | `src/sdk/url.ts` | `@decocms/apps/commerce/sdk/url` | no | Site fork carries a positional `removeIdSku?: boolean` flag with hardcoded VTEX-specific keys. Canonical apps export uses `{ stripSearchParams: string[] }` (`@decocms/apps@1.9+`). Rewrite imports + each `relative(url, true)` call site → `relative(url, { stripSearchParams: ["idsku", "skuId"] })`, then delete the file. Auto-fix is gated because the call-site rewrite needs JSX/TS-aware transformation, not pure import rewrite. |
433
434
 
434
435
  ### Adding a new entry
435
436
 
@@ -1215,9 +1215,76 @@ copy-paste regression on any future site gets caught automatically.
1215
1215
  - **15-B-4** — `Picture` API unification (breaking; needs a
1216
1216
  picking-the-winner pass between casaevideo's and baggagio's
1217
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.
1218
+
1219
+ ### Wave 15-B-5 (canonical `relative()` + audit registry entry — apps + deco-start) 🟡 **IN FLIGHT**
1220
+
1221
+ The smallest 15-B slice: extend `commerce/sdk/url.ts → relative()`
1222
+ with a generic options bag, then point the audit at the canonical
1223
+ so future site forks get caught automatically.
1224
+
1225
+ Verified state (2026-05-01 grep against baggagio-tanstack):
1226
+
1227
+ - `src/sdk/url.ts` carries a positional 2-arg fork (`relative(link,
1228
+ removeIdSku?: boolean)`) with VTEX-specific keys (`idsku`,
1229
+ `skuId`) hardcoded inside.
1230
+ - 9 importers in baggagio. ONE of them — `ProductCard.tsx` — uses
1231
+ the 2-arg form (via prop `removeIdSkuFromUrl`). The other 8 use
1232
+ the 1-arg form, identical to the apps canonical.
1233
+ - casaevideo doesn't carry a fork.
1234
+
1235
+ So the convergence is one apps-side extension + one audit registry
1236
+ entry. The single `ProductCard` call site rewrites by hand or by a
1237
+ future codemod (out of scope here).
1238
+
1239
+ **Shipped (two PRs):**
1240
+
1241
+ 39. [`apps-start#36`](https://github.com/decocms/apps-start/pull/36) — `feat(commerce/sdk): extend relative() with stripSearchParams option` ✅ **MERGED** (will release as `@decocms/apps@1.9.x`).
1242
+ - **`commerce/sdk/url.ts`**: backwards-compatible second
1243
+ `RelativeOptions` argument with `stripSearchParams?:
1244
+ string[]` primitive. 1-arg callers (everyone in apps + 8/9
1245
+ of baggagio's call sites) unaffected. The byte-for-byte
1246
+ "://path-style" passthrough is locked in by an explicit
1247
+ backwards-compat test.
1248
+ - **Why generic, not `removeIdSku?: boolean`**: hardcoded VTEX
1249
+ key names belong at call sites, not in a generic commerce
1250
+ helper. `stripSearchParams: string[]` works for any platform.
1251
+ Sites pass `["idsku", "skuId"]` themselves — honest about
1252
+ where the platform knowledge lives.
1253
+ - **`commerce/__tests__/url.test.ts`** (new): 18 tests covering
1254
+ base behaviour (relative/absolute/undefined/empty/malformed
1255
+ via `toString()` thrower), `stripSearchParams` primitive
1256
+ (single, multi, empty, missing, repeated keys, all-stripped
1257
+ → drop trailing `?`), and three explicit backwards-compat
1258
+ assertions.
1259
+ - 290/290 tests pass, typecheck + biome clean.
1260
+
1261
+ 40. `feat(migrate): add url-relative entry to local-framework-duplicate registry` 🟡 **WAITING ON CI** (deco-start side).
1262
+ - **Registry entry** in `FRAMEWORK_DUPLICATES` for
1263
+ `src/sdk/url.ts` → `@decocms/apps/commerce/sdk/url`. Content
1264
+ signature anchored on the legacy positional `removeIdSku?:
1265
+ boolean` shape so sites that already adopted the canonical
1266
+ options-object aren't flagged.
1267
+ - **`safeToAutoFix: false`** — the call-site rewrite from
1268
+ positional `relative(url, true)` to `relative(url, {
1269
+ stripSearchParams: ["idsku", "skuId"] })` requires JSX/TS-
1270
+ aware transformation, not pure import rewrite. The finding's
1271
+ `fix:` field carries the exact recipe.
1272
+ - **Two new tests**: positive case (legacy fork → flagged with
1273
+ the correct hint), negative case (canonical-shaped local
1274
+ fork that already adopted the options object → NOT flagged,
1275
+ proves the signature-anchoring works).
1276
+ - **Skill doc § 8 table** updated with the 4th entry, including
1277
+ version pin (`@decocms/apps@1.9+`).
1278
+ - 355/355 tests pass, typecheck clean. Smoke against baggagio
1279
+ now fires 3 findings (was 2): clx, useSendEvent, url; smoke
1280
+ against casaevideo unchanged at 1 (location-matcher).
1281
+
1282
+ **Process note**: this is the first time we ran the apps-side and
1283
+ deco-start-side as a pair of PRs. The order matters — apps-start
1284
+ must merge first so the deco-start audit registry can point at
1285
+ the released canonical. The skill doc explicitly version-pins the
1286
+ canonical (`@decocms/apps@1.9+`) so engineers reading the audit
1287
+ output know whether they need to bump apps before adopting.
1221
1288
 
1222
1289
  ### Wave 15+ (htmx cleanup PRs on als + propagation to other sites) — Priority 3 / 4
1223
1290
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "2.24.0",
3
+ "version": "2.25.0",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
@@ -1053,6 +1053,31 @@ export const FRAMEWORK_DUPLICATES: FrameworkDuplicate[] = [
1053
1053
  description:
1054
1054
  "src/matchers/location.ts overlaps with @decocms/start/matchers/builtins → registerBuiltinMatchers()",
1055
1055
  },
1056
+ {
1057
+ id: "url-relative",
1058
+ sitePath: "src/sdk/url.ts",
1059
+ canonicalImport: "@decocms/apps/commerce/sdk/url",
1060
+ // Fingerprint: site fork carries a positional `removeIdSku?: boolean`
1061
+ // flag + hardcoded VTEX-specific keys (`idsku`, `skuId`). Canonical
1062
+ // apps export uses an options object — `{ stripSearchParams: string[] }`
1063
+ // — which is generic and platform-agnostic.
1064
+ contentSignature: [
1065
+ /export\s+const\s+relative\s*=/,
1066
+ /removeIdSku\s*\?\s*:\s*boolean/,
1067
+ /['"](idsku|skuId)['"]/,
1068
+ ],
1069
+ safeToAutoFix: false,
1070
+ reason:
1071
+ "rewrite imports to '@decocms/apps/commerce/sdk/url'. " +
1072
+ "Each call site using the boolean form `relative(url, true)` becomes " +
1073
+ "`relative(url, { stripSearchParams: [\"idsku\", \"skuId\"] })`. " +
1074
+ "1-arg calls are unchanged. Then delete src/sdk/url.ts. " +
1075
+ "Auto-fix is gated because the call-site rewrite needs JSX/TS-aware " +
1076
+ "transformation (positional bool → options object), not pure import " +
1077
+ "rewrite.",
1078
+ description:
1079
+ "src/sdk/url.ts overlaps with @decocms/apps/commerce/sdk/url → relative() (extended in @decocms/apps@1.9+)",
1080
+ },
1056
1081
  ];
1057
1082
 
1058
1083
  const ruleLocalFrameworkDuplicate: Rule = {
@@ -1240,6 +1240,51 @@ describe("rule: local-framework-duplicate", () => {
1240
1240
  expect(r.findings[0].fix).toContain("typed AnalyticsEvent generic");
1241
1241
  });
1242
1242
 
1243
+ it("flags src/sdk/url.ts as warn-only (positional bool → options object call-site rewrite)", () => {
1244
+ const fs = makeFs({
1245
+ "/site/src/sdk/url.ts":
1246
+ "export const relative = (\n" +
1247
+ " link?: string | undefined,\n" +
1248
+ " removeIdSku?: boolean,\n" +
1249
+ ") => {\n" +
1250
+ ' const linkUrl = link ? new URL(link, "http://localhost") : undefined;\n' +
1251
+ " if (linkUrl && removeIdSku) {\n" +
1252
+ ' linkUrl.searchParams.delete("idsku");\n' +
1253
+ ' linkUrl.searchParams.delete("skuId");\n' +
1254
+ " }\n" +
1255
+ " return linkUrl ? `${linkUrl.pathname}` : undefined;\n" +
1256
+ "};\n",
1257
+ });
1258
+ const report = runAudit(SITE, fs);
1259
+ const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
1260
+ expect(r.findings).toHaveLength(1);
1261
+ expect(r.findings[0].file).toBe("src/sdk/url.ts");
1262
+ expect(r.findings[0].meta?.id).toBe("url-relative");
1263
+ expect(r.findings[0].meta?.safeToAutoFix).toBe(false);
1264
+ expect(r.findings[0].meta?.canonicalImport).toBe(
1265
+ "@decocms/apps/commerce/sdk/url",
1266
+ );
1267
+ expect(r.findings[0].fix).toContain("stripSearchParams");
1268
+ });
1269
+
1270
+ it("does NOT flag a forked url.ts that no longer carries the removeIdSku flag", () => {
1271
+ // A site that already adopted an options-object-shaped local helper
1272
+ // should not be flagged — the rule's signature is anchored on the
1273
+ // legacy positional-boolean shape.
1274
+ const fs = makeFs({
1275
+ "/site/src/sdk/url.ts":
1276
+ "export const relative = (link?: string, options?: { stripSearchParams?: string[] }) => {\n" +
1277
+ ' if (!link) return undefined;\n' +
1278
+ ' return new URL(link, "https://localhost").pathname;\n' +
1279
+ "};\n",
1280
+ });
1281
+ const report = runAudit(SITE, fs);
1282
+ const r = report.rules.find((r) => r.rule === "local-framework-duplicate")!;
1283
+ // url-relative entry must NOT fire on the canonical-shaped local fork.
1284
+ const urlFinding = r.findings.find((f) => f.meta?.id === "url-relative");
1285
+ expect(urlFinding).toBeUndefined();
1286
+ });
1287
+
1243
1288
  it("flags src/matchers/location.ts as warn-only (behaviour-superset opportunity)", () => {
1244
1289
  const fs = makeFs({
1245
1290
  "/site/src/matchers/location.ts":