@decocms/start 2.14.0 → 2.15.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.
|
@@ -168,16 +168,116 @@ one imported symbol is a real silent stub (returns `null` / `{}` / `[]`
|
|
|
168
168
|
alongside stubs (e.g. a `parseCookie` cookie parser, a `fetchSafe`
|
|
169
169
|
wrapper) no longer create noise.
|
|
170
170
|
|
|
171
|
-
The audit's finding names the exact stub symbols
|
|
171
|
+
The audit's finding names the exact stub symbols **and emits per-symbol
|
|
172
|
+
fix guidance**, e.g.
|
|
172
173
|
|
|
173
174
|
```
|
|
174
175
|
[WARNING] src/loaders/search/x.ts — Imports stub-only symbols from
|
|
175
176
|
vtex-transform (toProduct); vtex-segment (getSegmentFromBag) —
|
|
176
177
|
runtime is silently stubbed
|
|
177
|
-
fix:
|
|
178
|
-
|
|
178
|
+
fix: toProduct → @decocms/apps/vtex/utils/transform (1:1 import swap)
|
|
179
|
+
— canonical signature is `toProduct(product, sku, level, options)`;
|
|
180
|
+
1-arg call sites need to expand args first | getSegmentFromBag →
|
|
181
|
+
call-site refactor: read cookies via `request.headers.get('cookie')`
|
|
182
|
+
then call `buildSegmentFromCookies()` from
|
|
183
|
+
'@decocms/apps/vtex/utils/segment'.
|
|
179
184
|
```
|
|
180
185
|
|
|
186
|
+
JSON consumers can read structured guidance from `meta.fixHints`:
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"rule": "vtex-shim-regression",
|
|
191
|
+
"meta": {
|
|
192
|
+
"stubsBySim": { "vtex-transform": ["toProduct"], "vtex-segment": ["getSegmentFromBag"] },
|
|
193
|
+
"fixHints": {
|
|
194
|
+
"toProduct": { "kind": "swap", "canonical": "@decocms/apps/vtex/utils/transform", "note": "..." },
|
|
195
|
+
"getSegmentFromBag": { "kind": "refactor", "note": "..." }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Canonical replacement table
|
|
202
|
+
|
|
203
|
+
| Stub symbol | Kind | Canonical / fix |
|
|
204
|
+
|---|---|---|
|
|
205
|
+
| `toProduct` | swap | `@decocms/apps/vtex/utils/transform.toProduct` — note canonical signature is `(product, sku, level, options)`; 1-arg call sites need to expand args |
|
|
206
|
+
| `withSegmentCookie` | swap | `@decocms/apps/vtex/utils/segment.withSegmentCookie` — note canonical signature is `(segment, headers?)` |
|
|
207
|
+
| `getSegmentFromBag` | refactor | read cookies via `request.headers.get('cookie')`, then `buildSegmentFromCookies()` from `@decocms/apps/vtex/utils/segment` |
|
|
208
|
+
| `getISCookiesFromBag` | refactor | extract IS cookies from `request.headers.get('cookie')` directly — no canonical helper, the bag-based mechanism doesn't exist on TanStack Start |
|
|
209
|
+
|
|
210
|
+
Symbols not in the table get the generic guidance ("repoint to
|
|
211
|
+
`@decocms/apps/vtex/...` or `apps/commerce/utils/...`") — when you find
|
|
212
|
+
a new one worth pinning down, add it to `STUB_FIX_HINTS` in
|
|
213
|
+
[`scripts/migrate/post-cleanup/rules.ts`](https://github.com/decocms/deco-start/blob/main/scripts/migrate/post-cleanup/rules.ts).
|
|
214
|
+
|
|
215
|
+
### Recipe: expanding 1-arg `toProduct(p)` call sites
|
|
216
|
+
|
|
217
|
+
Two real-world patterns surface, requiring different fixes:
|
|
218
|
+
|
|
219
|
+
**Pattern A — call site already passes 4 args under `as any`** (e.g.
|
|
220
|
+
`smartShelfForYou.ts` on casaevideo): the dev wrote the call for
|
|
221
|
+
canonical, the import pointed at the stub. Fix is **import-only**:
|
|
222
|
+
|
|
223
|
+
```diff
|
|
224
|
+
-import { toProduct } from "~/lib/vtex-transform";
|
|
225
|
+
+import { toProduct } from "@decocms/apps/vtex/utils/transform";
|
|
226
|
+
|
|
227
|
+
const normalizedProducts = rawProducts.data.map((p: VTEXProduct) =>
|
|
228
|
+
(toProduct as any)(p, p.items?.[0], 0, {
|
|
229
|
+
baseUrl: baseURL,
|
|
230
|
+
priceCurrency: "BRL",
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The `as any` cast may stay if local `~/types/vtex.Product` and
|
|
236
|
+
canonical `LegacyProductVTEX | ProductVTEX` differ structurally — that's
|
|
237
|
+
a separate refactor.
|
|
238
|
+
|
|
239
|
+
**Pattern B — call site uses true 1-arg form** (e.g.
|
|
240
|
+
`intelligenseSearch.ts` on casaevideo): the dev relied on the stub's
|
|
241
|
+
identity-cast behaviour. Fix is to **expand the call** mirroring the
|
|
242
|
+
canonical pattern in
|
|
243
|
+
[`apps-start/vtex/loaders/autocomplete.ts`](https://github.com/decocms/apps-start/blob/main/vtex/loaders/autocomplete.ts):
|
|
244
|
+
|
|
245
|
+
```diff
|
|
246
|
+
-import { toProduct } from "~/lib/vtex-transform";
|
|
247
|
+
+import { pickSku, toProduct } from "@decocms/apps/vtex/utils/transform";
|
|
248
|
+
|
|
249
|
+
const baseURL = new URL(req.url).origin;
|
|
250
|
+
return {
|
|
251
|
+
searches,
|
|
252
|
+
- products: (products ?? []).map((p) => toProduct(p)).slice(0, count),
|
|
253
|
+
+ products: (products ?? []).slice(0, count).map((p: any) => {
|
|
254
|
+
+ const sku = pickSku(p);
|
|
255
|
+
+ return toProduct(p, sku, 0, { baseUrl: baseURL, priceCurrency: "BRL" });
|
|
256
|
+
+ }),
|
|
257
|
+
};
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`pickSku` handles the IS-shape SKU selection; without it, downstream
|
|
261
|
+
fields like `productID`, `gtin`, `additionalProperty[]` come back
|
|
262
|
+
empty.
|
|
263
|
+
|
|
264
|
+
**Pattern C — keep the stub deliberately**: rare, but valid when the
|
|
265
|
+
upstream API already returns canonical `Product[]` shape and the call
|
|
266
|
+
is purely a type-narrowing cast. Replace with a typed cast at the
|
|
267
|
+
boundary instead of importing a stub:
|
|
268
|
+
|
|
269
|
+
```diff
|
|
270
|
+
-import { toProduct } from "~/lib/vtex-transform";
|
|
271
|
+
+import type { Product } from "@decocms/apps/commerce/types";
|
|
272
|
+
|
|
273
|
+
-products: (products ?? []).map((p) => toProduct(p)).slice(0, count),
|
|
274
|
+
+products: ((products ?? []) as Product[]).slice(0, count),
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
This silences the audit (the stub import is gone) without changing
|
|
278
|
+
behaviour. Only do this if you've **verified** the upstream payload is
|
|
279
|
+
already schema.org-shaped.
|
|
280
|
+
|
|
181
281
|
Manual sweep (still useful if you don't have the audit handy):
|
|
182
282
|
|
|
183
283
|
```bash
|
package/package.json
CHANGED
|
@@ -239,6 +239,123 @@ const ruleSiteLocalGlobals: Rule = {
|
|
|
239
239
|
/* Rule 5 — `~/lib/vtex-*` shim regression */
|
|
240
240
|
/* ------------------------------------------------------------------ */
|
|
241
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Per-symbol guidance for the canonical replacement of each known
|
|
244
|
+
* shim stub. Used by the `vtex-shim-regression` rule to compose
|
|
245
|
+
* actionable `fix:` messages instead of the generic "Repoint imports"
|
|
246
|
+
* fallback.
|
|
247
|
+
*
|
|
248
|
+
* Kept as data (not code) so the JSON output of the audit can carry
|
|
249
|
+
* structured fix metadata for downstream tooling (CI dashboards,
|
|
250
|
+
* follow-up auto-fix rules, etc.).
|
|
251
|
+
*
|
|
252
|
+
* Categories:
|
|
253
|
+
* - `swap`: 1:1 import swap is safe — caller imports the symbol from
|
|
254
|
+
* `canonical` instead of the local shim. Note may flag a signature
|
|
255
|
+
* gotcha that the caller has to address at the call site.
|
|
256
|
+
* - `refactor`: a call-site rewrite is required (typically because the
|
|
257
|
+
* stub's "bag-based" API has no analog on TanStack Start; the request
|
|
258
|
+
* headers are the new source of truth). The note explains the pattern.
|
|
259
|
+
*
|
|
260
|
+
* Symbols absent from this table fall back to the generic guidance.
|
|
261
|
+
* The rule still flags them — only the `fix:` prose changes.
|
|
262
|
+
*/
|
|
263
|
+
export type FixHint =
|
|
264
|
+
| { kind: "swap"; canonical: string; note?: string }
|
|
265
|
+
| { kind: "refactor"; note: string };
|
|
266
|
+
|
|
267
|
+
export const STUB_FIX_HINTS: Record<string, FixHint> = {
|
|
268
|
+
// src/lib/vtex-transform
|
|
269
|
+
toProduct: {
|
|
270
|
+
kind: "swap",
|
|
271
|
+
canonical: "@decocms/apps/vtex/utils/transform",
|
|
272
|
+
note:
|
|
273
|
+
"canonical signature is `toProduct(product, sku, level, options)`; " +
|
|
274
|
+
"1-arg call sites need to expand args first — see skill § 5",
|
|
275
|
+
},
|
|
276
|
+
// src/lib/vtex-segment
|
|
277
|
+
getSegmentFromBag: {
|
|
278
|
+
kind: "refactor",
|
|
279
|
+
note:
|
|
280
|
+
"read cookies via `request.headers.get('cookie')` then call " +
|
|
281
|
+
"`buildSegmentFromCookies()` from '@decocms/apps/vtex/utils/segment'. " +
|
|
282
|
+
"The bag-based lookup mechanism does not exist on TanStack Start.",
|
|
283
|
+
},
|
|
284
|
+
withSegmentCookie: {
|
|
285
|
+
kind: "swap",
|
|
286
|
+
canonical: "@decocms/apps/vtex/utils/segment",
|
|
287
|
+
note:
|
|
288
|
+
"canonical signature is `withSegmentCookie(segment, headers?)`; " +
|
|
289
|
+
"if you currently pass only headers, also pass a segment object",
|
|
290
|
+
},
|
|
291
|
+
// src/lib/vtex-intelligent-search
|
|
292
|
+
getISCookiesFromBag: {
|
|
293
|
+
kind: "refactor",
|
|
294
|
+
note:
|
|
295
|
+
"extract IS cookies from `request.headers.get('cookie')` directly. " +
|
|
296
|
+
"The bag-based lookup mechanism does not exist on TanStack Start.",
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Format a single symbol's fix guidance as a one-liner suitable for
|
|
302
|
+
* the audit's `fix:` field. Returns undefined when the symbol has no
|
|
303
|
+
* specific entry in `STUB_FIX_HINTS`.
|
|
304
|
+
*/
|
|
305
|
+
export function formatFixHint(symbol: string): string | undefined {
|
|
306
|
+
const hint = STUB_FIX_HINTS[symbol];
|
|
307
|
+
if (!hint) return undefined;
|
|
308
|
+
if (hint.kind === "swap") {
|
|
309
|
+
const head = `${symbol} → ${hint.canonical} (1:1 import swap)`;
|
|
310
|
+
return hint.note ? `${head} — ${hint.note}` : head;
|
|
311
|
+
}
|
|
312
|
+
return `${symbol} → call-site refactor: ${hint.note}`;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Compose the `fix:` message for a finding from the per-shim stub map.
|
|
317
|
+
* Splits symbols into "have specific guidance" vs "fall back to generic".
|
|
318
|
+
* Output joins each piece with ` | ` so the message stays one logical
|
|
319
|
+
* line even when there are several stubs.
|
|
320
|
+
*/
|
|
321
|
+
export function buildVtexShimFixMessage(stubsBySim: Map<string, string[]>): string {
|
|
322
|
+
const known: string[] = [];
|
|
323
|
+
const unknown: string[] = [];
|
|
324
|
+
for (const syms of stubsBySim.values()) {
|
|
325
|
+
for (const s of syms) {
|
|
326
|
+
const hint = formatFixHint(s);
|
|
327
|
+
if (hint) known.push(hint);
|
|
328
|
+
else unknown.push(s);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const parts: string[] = [...known];
|
|
332
|
+
if (unknown.length > 0) {
|
|
333
|
+
parts.push(
|
|
334
|
+
`${unknown.join(", ")} → repoint to '@decocms/apps/vtex/...' or 'apps/commerce/utils/...'`,
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
return parts.length > 0
|
|
338
|
+
? parts.join(" | ")
|
|
339
|
+
: "Repoint imports to '@decocms/apps/vtex/...' or 'apps/commerce/utils/...'";
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Build the structured `fixHints` payload for `meta` so JSON consumers
|
|
344
|
+
* (CI dashboards, follow-up tooling) can render their own UI. Each
|
|
345
|
+
* entry is keyed by symbol; symbols without specific guidance are
|
|
346
|
+
* omitted (the prose fallback covers them).
|
|
347
|
+
*/
|
|
348
|
+
function fixHintsToMeta(stubsBySim: Map<string, string[]>): Record<string, FixHint> {
|
|
349
|
+
const out: Record<string, FixHint> = {};
|
|
350
|
+
for (const syms of stubsBySim.values()) {
|
|
351
|
+
for (const s of syms) {
|
|
352
|
+
const hint = STUB_FIX_HINTS[s];
|
|
353
|
+
if (hint) out[s] = hint;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
|
|
242
359
|
/**
|
|
243
360
|
* Parse one or more ES `import { a, b as c, type d } from "spec"` blocks
|
|
244
361
|
* targeting a specific source spec out of a file. Returns the list of
|
|
@@ -319,16 +436,17 @@ const ruleVtexShimRegression: Rule = {
|
|
|
319
436
|
const rel = abs.slice(siteDir.length + 1);
|
|
320
437
|
const detail = [...stubsBySim.entries()]
|
|
321
438
|
.map(([s, syms]) => `${s} (${syms.join(", ")})`)
|
|
322
|
-
.join("; ")
|
|
323
|
-
;
|
|
439
|
+
.join("; ");
|
|
440
|
+
const fixHintsMeta = fixHintsToMeta(stubsBySim);
|
|
324
441
|
findings.push({
|
|
325
442
|
rule: "vtex-shim-regression",
|
|
326
443
|
severity: "warning",
|
|
327
444
|
file: rel,
|
|
328
445
|
message: `Imports stub-only symbols from ${detail} — runtime is silently stubbed`,
|
|
329
|
-
fix:
|
|
446
|
+
fix: buildVtexShimFixMessage(stubsBySim),
|
|
330
447
|
meta: {
|
|
331
448
|
stubsBySim: Object.fromEntries(stubsBySim),
|
|
449
|
+
...(Object.keys(fixHintsMeta).length > 0 ? { fixHints: fixHintsMeta } : {}),
|
|
332
450
|
},
|
|
333
451
|
});
|
|
334
452
|
}
|
|
@@ -404,6 +404,108 @@ describe("rule: vtex-shim-regression", () => {
|
|
|
404
404
|
});
|
|
405
405
|
});
|
|
406
406
|
|
|
407
|
+
describe("rule: vtex-shim-regression — per-symbol fix hints", () => {
|
|
408
|
+
it("emits 1:1 swap hint for `toProduct`", () => {
|
|
409
|
+
const fs = makeFs({
|
|
410
|
+
"/site/src/lib/vtex-transform.ts":
|
|
411
|
+
"export function toProduct(p: any): unknown { return p as unknown; }\n",
|
|
412
|
+
"/site/src/loaders/x.ts":
|
|
413
|
+
'import { toProduct } from "~/lib/vtex-transform";\n',
|
|
414
|
+
});
|
|
415
|
+
const report = runAudit(SITE, fs);
|
|
416
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
417
|
+
expect(r.findings).toHaveLength(1);
|
|
418
|
+
const f = r.findings[0];
|
|
419
|
+
expect(f.fix).toContain("toProduct → @decocms/apps/vtex/utils/transform");
|
|
420
|
+
expect(f.fix).toContain("1:1 import swap");
|
|
421
|
+
expect(f.meta?.fixHints).toEqual({
|
|
422
|
+
toProduct: {
|
|
423
|
+
kind: "swap",
|
|
424
|
+
canonical: "@decocms/apps/vtex/utils/transform",
|
|
425
|
+
note: expect.stringContaining("canonical signature"),
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("emits refactor hint for `getSegmentFromBag`", () => {
|
|
431
|
+
const fs = makeFs({
|
|
432
|
+
"/site/src/lib/vtex-segment.ts":
|
|
433
|
+
"export function getSegmentFromBag(): null { return null; }\n",
|
|
434
|
+
"/site/src/loaders/x.ts":
|
|
435
|
+
'import { getSegmentFromBag } from "~/lib/vtex-segment";\n',
|
|
436
|
+
});
|
|
437
|
+
const report = runAudit(SITE, fs);
|
|
438
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
439
|
+
const f = r.findings[0];
|
|
440
|
+
expect(f.fix).toContain("getSegmentFromBag → call-site refactor");
|
|
441
|
+
expect(f.fix).toContain("buildSegmentFromCookies");
|
|
442
|
+
expect(f.meta?.fixHints).toEqual({
|
|
443
|
+
getSegmentFromBag: {
|
|
444
|
+
kind: "refactor",
|
|
445
|
+
note: expect.stringContaining("buildSegmentFromCookies"),
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("composes hints for files with multiple stubs", () => {
|
|
451
|
+
const fs = makeFs({
|
|
452
|
+
"/site/src/lib/vtex-segment.ts":
|
|
453
|
+
"export function getSegmentFromBag(): null { return null; }\n",
|
|
454
|
+
"/site/src/lib/vtex-transform.ts":
|
|
455
|
+
"export function toProduct(p: any): unknown { return p as unknown; }\n",
|
|
456
|
+
"/site/src/loaders/x.ts":
|
|
457
|
+
'import { getSegmentFromBag } from "~/lib/vtex-segment";\n' +
|
|
458
|
+
'import { toProduct } from "~/lib/vtex-transform";\n',
|
|
459
|
+
});
|
|
460
|
+
const report = runAudit(SITE, fs);
|
|
461
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
462
|
+
const f = r.findings[0];
|
|
463
|
+
expect(f.fix).toContain("getSegmentFromBag → call-site refactor");
|
|
464
|
+
expect(f.fix).toContain("toProduct → @decocms/apps/vtex/utils/transform");
|
|
465
|
+
// Joined with " | " for visual separation.
|
|
466
|
+
expect(f.fix).toContain(" | ");
|
|
467
|
+
expect(Object.keys(f.meta?.fixHints as object)).toEqual(
|
|
468
|
+
expect.arrayContaining(["toProduct", "getSegmentFromBag"]),
|
|
469
|
+
);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("falls back to generic hint for symbols without entries", () => {
|
|
473
|
+
const fs = makeFs({
|
|
474
|
+
"/site/src/lib/vtex-mystery.ts":
|
|
475
|
+
"export function unknownStub(): null { return null; }\n",
|
|
476
|
+
"/site/src/loaders/x.ts":
|
|
477
|
+
'import { unknownStub } from "~/lib/vtex-mystery";\n',
|
|
478
|
+
});
|
|
479
|
+
const report = runAudit(SITE, fs);
|
|
480
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
481
|
+
const f = r.findings[0];
|
|
482
|
+
expect(f.fix).toContain("unknownStub → repoint to '@decocms/apps/vtex/...");
|
|
483
|
+
// No fixHints in meta when no symbols match the table.
|
|
484
|
+
expect(f.meta?.fixHints).toBeUndefined();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it("mixes specific hints and generic fallback in one message", () => {
|
|
488
|
+
const fs = makeFs({
|
|
489
|
+
"/site/src/lib/vtex-transform.ts":
|
|
490
|
+
"export function toProduct(p: any): unknown { return p as unknown; }\n",
|
|
491
|
+
"/site/src/lib/vtex-mystery.ts":
|
|
492
|
+
"export function unknownStub(): null { return null; }\n",
|
|
493
|
+
"/site/src/loaders/x.ts":
|
|
494
|
+
'import { toProduct } from "~/lib/vtex-transform";\n' +
|
|
495
|
+
'import { unknownStub } from "~/lib/vtex-mystery";\n',
|
|
496
|
+
});
|
|
497
|
+
const report = runAudit(SITE, fs);
|
|
498
|
+
const r = report.rules.find((r) => r.rule === "vtex-shim-regression")!;
|
|
499
|
+
const f = r.findings[0];
|
|
500
|
+
expect(f.fix).toContain("toProduct → @decocms/apps/vtex/utils/transform");
|
|
501
|
+
expect(f.fix).toContain("unknownStub → repoint");
|
|
502
|
+
// Only the known symbol shows up in fixHints.
|
|
503
|
+
expect(f.meta?.fixHints).toEqual({
|
|
504
|
+
toProduct: expect.objectContaining({ kind: "swap" }),
|
|
505
|
+
});
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
407
509
|
describe("rule: local-widgets-types", () => {
|
|
408
510
|
it("flags presence of src/types/widgets.ts and counts imports", () => {
|
|
409
511
|
const fs = makeFs({
|