@decocms/blocks-cli 7.0.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/package.json +44 -0
- package/scripts/analyze-traces.mjs +1117 -0
- package/scripts/audit-observability-config.test.ts +446 -0
- package/scripts/audit-observability-config.ts +511 -0
- package/scripts/deco-migrate-cli.ts +444 -0
- package/scripts/fast-deploy-kv.test.ts +131 -0
- package/scripts/generate-blocks.test.ts +94 -0
- package/scripts/generate-blocks.ts +274 -0
- package/scripts/generate-invoke.test.ts +195 -0
- package/scripts/generate-invoke.ts +469 -0
- package/scripts/generate-loaders.ts +217 -0
- package/scripts/generate-schema.ts +1287 -0
- package/scripts/generate-sections.ts +237 -0
- package/scripts/htmx-analyze.ts +226 -0
- package/scripts/lib/blocks-dedupe.test.ts +179 -0
- package/scripts/lib/blocks-dedupe.ts +142 -0
- package/scripts/lib/cf-kv-rest.ts +78 -0
- package/scripts/lib/jsonc.ts +122 -0
- package/scripts/lib/kv-snapshot.ts +51 -0
- package/scripts/lib/read-decofile.ts +70 -0
- package/scripts/lib/sync-helpers.ts +44 -0
- package/scripts/migrate/analyzers/htmx-analyze.test.ts +372 -0
- package/scripts/migrate/analyzers/htmx-analyze.ts +425 -0
- package/scripts/migrate/analyzers/island-classifier.ts +96 -0
- package/scripts/migrate/analyzers/loader-inventory.ts +63 -0
- package/scripts/migrate/analyzers/section-metadata.ts +147 -0
- package/scripts/migrate/analyzers/theme-extractor.ts +122 -0
- package/scripts/migrate/colors.ts +46 -0
- package/scripts/migrate/config.test.ts +202 -0
- package/scripts/migrate/config.ts +186 -0
- package/scripts/migrate/phase-analyze.test.ts +63 -0
- package/scripts/migrate/phase-analyze.ts +782 -0
- package/scripts/migrate/phase-cleanup-audit.test.ts +137 -0
- package/scripts/migrate/phase-cleanup-audit.ts +105 -0
- package/scripts/migrate/phase-cleanup.test.ts +141 -0
- package/scripts/migrate/phase-cleanup.ts +1588 -0
- package/scripts/migrate/phase-compile.test.ts +193 -0
- package/scripts/migrate/phase-compile.ts +177 -0
- package/scripts/migrate/phase-report.ts +243 -0
- package/scripts/migrate/phase-scaffold.ts +593 -0
- package/scripts/migrate/phase-transform.ts +310 -0
- package/scripts/migrate/phase-verify.test.ts +127 -0
- package/scripts/migrate/phase-verify.ts +572 -0
- package/scripts/migrate/post-cleanup/rules.ts +1708 -0
- package/scripts/migrate/post-cleanup/runner.test.ts +1771 -0
- package/scripts/migrate/post-cleanup/runner.ts +137 -0
- package/scripts/migrate/post-cleanup/shim-classify.test.ts +352 -0
- package/scripts/migrate/post-cleanup/shim-classify.ts +246 -0
- package/scripts/migrate/post-cleanup/types.ts +106 -0
- package/scripts/migrate/source-layout.test.ts +111 -0
- package/scripts/migrate/source-layout.ts +103 -0
- package/scripts/migrate/templates/app-css.ts +366 -0
- package/scripts/migrate/templates/cache-config.ts +26 -0
- package/scripts/migrate/templates/commerce-loaders.ts +230 -0
- package/scripts/migrate/templates/cursor-rules.test.ts +59 -0
- package/scripts/migrate/templates/cursor-rules.ts +70 -0
- package/scripts/migrate/templates/hooks.test.ts +141 -0
- package/scripts/migrate/templates/hooks.ts +152 -0
- package/scripts/migrate/templates/knip-config.ts +27 -0
- package/scripts/migrate/templates/lib-utils.test.ts +139 -0
- package/scripts/migrate/templates/lib-utils.ts +326 -0
- package/scripts/migrate/templates/lockfile-check-yml.test.ts +26 -0
- package/scripts/migrate/templates/lockfile-check-yml.ts +66 -0
- package/scripts/migrate/templates/package-json.ts +177 -0
- package/scripts/migrate/templates/routes.ts +237 -0
- package/scripts/migrate/templates/sdk-gen.ts +59 -0
- package/scripts/migrate/templates/section-loaders.ts +456 -0
- package/scripts/migrate/templates/server-entry.ts +561 -0
- package/scripts/migrate/templates/setup.ts +148 -0
- package/scripts/migrate/templates/tsconfig.ts +21 -0
- package/scripts/migrate/templates/types-gen.ts +174 -0
- package/scripts/migrate/templates/ui-components.ts +144 -0
- package/scripts/migrate/templates/vite-config.ts +101 -0
- package/scripts/migrate/transforms/dead-code.ts +455 -0
- package/scripts/migrate/transforms/deno-isms.ts +85 -0
- package/scripts/migrate/transforms/fresh-apis.ts +223 -0
- package/scripts/migrate/transforms/htmx-on-events.test.ts +305 -0
- package/scripts/migrate/transforms/htmx-on-events.ts +193 -0
- package/scripts/migrate/transforms/imports.ts +385 -0
- package/scripts/migrate/transforms/jsx.ts +317 -0
- package/scripts/migrate/transforms/section-conventions.ts +210 -0
- package/scripts/migrate/transforms/tailwind.ts +739 -0
- package/scripts/migrate/types.ts +244 -0
- package/scripts/migrate-blocks-to-kv.ts +104 -0
- package/scripts/migrate-post-cleanup.ts +191 -0
- package/scripts/migrate-to-cf-observability.test.ts +215 -0
- package/scripts/migrate-to-cf-observability.ts +699 -0
- package/scripts/migrate.ts +282 -0
- package/scripts/smoke-otlp-errorlog.ts +46 -0
- package/scripts/smoke-otlp-meter.ts +48 -0
- package/scripts/sync-blocks-to-kv.ts +153 -0
- package/scripts/tailwind-lint.ts +518 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import type { MigrationContext, SectionMeta } from "../types";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
const ACCOUNT_LOADER_MAP: Record<string, string> = {
|
|
6
|
+
personaldata: "personalData",
|
|
7
|
+
myorders: "orders",
|
|
8
|
+
myordersdata: "orders",
|
|
9
|
+
orders: "orders",
|
|
10
|
+
cards: "cards",
|
|
11
|
+
payments: "cards",
|
|
12
|
+
addresses: "addresses",
|
|
13
|
+
auth: "authentication",
|
|
14
|
+
authentication: "authentication",
|
|
15
|
+
authenticationpage: "authentication",
|
|
16
|
+
login: "authentication",
|
|
17
|
+
myinsurances: "loggedIn",
|
|
18
|
+
privacypolice: "loggedIn",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function getAccountLoaderName(sectionBasename: string): string {
|
|
22
|
+
const key = sectionBasename.toLowerCase().replace(/[^a-z]/g, "");
|
|
23
|
+
return ACCOUNT_LOADER_MAP[key] || "loggedIn";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sectionExists(ctx: MigrationContext, sectionPath: string): boolean {
|
|
27
|
+
const full = path.join(ctx.sourceDir, "src", sectionPath);
|
|
28
|
+
if (fs.existsSync(full)) return true;
|
|
29
|
+
const root = path.join(ctx.sourceDir, sectionPath);
|
|
30
|
+
return fs.existsSync(root);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loaderExists(ctx: MigrationContext, loaderPath: string): boolean {
|
|
34
|
+
const full = path.join(ctx.sourceDir, "src", loaderPath);
|
|
35
|
+
if (fs.existsSync(full)) return true;
|
|
36
|
+
const root = path.join(ctx.sourceDir, loaderPath);
|
|
37
|
+
return fs.existsSync(root);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function generateSectionLoaders(ctx: MigrationContext): string {
|
|
41
|
+
const lines: string[] = [];
|
|
42
|
+
const isVtex = ctx.platform === "vtex";
|
|
43
|
+
const hasAccountSections = isVtex && ctx.sectionMetas.some((m) => m.isAccountSection);
|
|
44
|
+
const hasWishlistSection = sectionExists(ctx, "sections/Wishlist.tsx");
|
|
45
|
+
const hasInstagramSection = sectionExists(ctx, "sections/Social/InstagramPosts.tsx");
|
|
46
|
+
const hasCategoryBanner = sectionExists(ctx, "sections/Category/CategoryBanner.tsx");
|
|
47
|
+
const hasBackgroundWrapper = sectionExists(ctx, "sections/LpContent/BackgroundWrapper.tsx");
|
|
48
|
+
const hasProductReviews = sectionExists(ctx, "sections/Product/ProductReviews.tsx");
|
|
49
|
+
const hasProductDescription = sectionExists(ctx, "sections/Product/ProductDescription.tsx");
|
|
50
|
+
const hasProductFaq = sectionExists(ctx, "sections/Product/ProductFaq.tsx");
|
|
51
|
+
const hasSearchResult = sectionExists(ctx, "sections/Product/SearchResult.tsx");
|
|
52
|
+
const hasPrivacyPolice = sectionExists(ctx, "sections/Account/PrivacyPolice.tsx");
|
|
53
|
+
const hasSEOPDP = sectionExists(ctx, "sections/SEOPDP.tsx");
|
|
54
|
+
const hasCallCenter = sectionExists(ctx, "sections/CallCenter.tsx");
|
|
55
|
+
const hasIsEvents = sectionExists(ctx, "sections/Analytics/IsEvents.tsx");
|
|
56
|
+
const hasWishlistLoaders = loaderExists(ctx, "loaders/Wishlist/get-wishlist-list.ts");
|
|
57
|
+
const hasProductReviewsLoader = loaderExists(ctx, "loaders/reviews/productReviews.ts");
|
|
58
|
+
|
|
59
|
+
lines.push(`/**`);
|
|
60
|
+
lines.push(` * Section Loaders — server-side prop enrichment for CMS sections.`);
|
|
61
|
+
lines.push(` *`);
|
|
62
|
+
lines.push(` * Each entry receives CMS-resolved props + Request, returns enriched props.`);
|
|
63
|
+
lines.push(` * Simple patterns (device, mobile) use framework mixins.`);
|
|
64
|
+
lines.push(` * Complex logic (SearchResult, PDP fallback, Wishlist) is inline.`);
|
|
65
|
+
lines.push(` */`);
|
|
66
|
+
lines.push(`import {`);
|
|
67
|
+
lines.push(` registerSectionLoaders,`);
|
|
68
|
+
if (hasBackgroundWrapper) lines.push(` runSingleSectionLoader,`);
|
|
69
|
+
lines.push(` withDevice,`);
|
|
70
|
+
lines.push(` withMobile,`);
|
|
71
|
+
lines.push(` withSearchParam,`);
|
|
72
|
+
lines.push(` withSectionLoader,`);
|
|
73
|
+
lines.push(` compose,`);
|
|
74
|
+
lines.push(`} from "@decocms/start/cms";`);
|
|
75
|
+
|
|
76
|
+
if (hasSearchResult) {
|
|
77
|
+
lines.push(`import { detectDevice } from "@decocms/start/sdk/useDevice";`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (isVtex) {
|
|
81
|
+
lines.push(`import { getVtexConfig } from "@decocms/apps/vtex";`);
|
|
82
|
+
if (hasWishlistSection && hasWishlistLoaders) {
|
|
83
|
+
lines.push(`import { getUser } from "@decocms/apps/vtex/loaders/user";`);
|
|
84
|
+
lines.push(`import { getVtexCookies } from "@decocms/apps/vtex/utils/cookies";`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (hasAccountSections) {
|
|
89
|
+
lines.push(`import { vtexAccountLoaders } from "@decocms/apps/vtex/utils/accountLoaders";`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (hasProductReviewsLoader && (hasProductReviews || hasSearchResult)) {
|
|
93
|
+
lines.push(`import productReviewsLoader from "../loaders/reviews/productReviews";`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (hasWishlistLoaders && hasWishlistSection) {
|
|
97
|
+
lines.push(`import getWishlistList from "../loaders/Wishlist/get-wishlist-list";`);
|
|
98
|
+
lines.push(`import getWishlistItems from "../loaders/Wishlist/get-wishlist-items";`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for secrets file
|
|
102
|
+
const hasSecrets = loaderExists(ctx, "utils/secrets.ts") || loaderExists(ctx, "src/utils/secrets.ts");
|
|
103
|
+
if (hasSecrets && (hasProductReviews || hasProductReviewsLoader)) {
|
|
104
|
+
lines.push(`import { secrets } from "../utils/secrets";`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Import cachedPDP for PDP fallbacks
|
|
108
|
+
const needsCachedPDP = hasProductDescription || hasProductFaq || hasProductReviews;
|
|
109
|
+
if (isVtex && needsCachedPDP) {
|
|
110
|
+
lines.push(`import { cachedPDP } from "./commerce-loaders";`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
lines.push(``);
|
|
114
|
+
|
|
115
|
+
const entries: string[] = [];
|
|
116
|
+
|
|
117
|
+
// ---------- Headers ----------
|
|
118
|
+
for (const meta of ctx.sectionMetas) {
|
|
119
|
+
if (!meta.isHeader || !meta.hasLoader) continue;
|
|
120
|
+
const sectionKey = `site/${meta.path}`;
|
|
121
|
+
const importPath = `~/` + meta.path.replace(/\.tsx?$/, "");
|
|
122
|
+
entries.push(` // Header: device + search param + section's own loader`);
|
|
123
|
+
entries.push(` "${sectionKey}": async (props, req) => ({`);
|
|
124
|
+
entries.push(` ...(await compose(`);
|
|
125
|
+
entries.push(` withDevice(),`);
|
|
126
|
+
entries.push(` withSearchParam(),`);
|
|
127
|
+
entries.push(` withSectionLoader(() => import("${importPath}")),`);
|
|
128
|
+
entries.push(` )(props, req)),`);
|
|
129
|
+
entries.push(` userName: "",`);
|
|
130
|
+
entries.push(` }),`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------- Device/mobile/url + own-loader composition ----------
|
|
134
|
+
//
|
|
135
|
+
// Rule of thumb: if a section exports its own `loader`, ALWAYS run it.
|
|
136
|
+
// Mixins (withDevice/withMobile/withSearchParam) are composed BEFORE the
|
|
137
|
+
// section loader so they can inject device/search-param props the section
|
|
138
|
+
// loader may read; the section loader has the final say over what is
|
|
139
|
+
// returned.
|
|
140
|
+
//
|
|
141
|
+
// The previous template chose mixins XOR own-loader and silently dropped
|
|
142
|
+
// the section's loader when both were present — see als-tanstack
|
|
143
|
+
// SearchContainerV2 SSR regression.
|
|
144
|
+
for (const meta of ctx.sectionMetas) {
|
|
145
|
+
if (meta.isHeader || meta.isAccountSection || meta.isStatusOnly) continue;
|
|
146
|
+
// Skip sections with no loader AND no device needs
|
|
147
|
+
if (!meta.hasLoader && !meta.loaderUsesDevice) continue;
|
|
148
|
+
const sectionKey = `site/${meta.path}`;
|
|
149
|
+
const basename = meta.path.split("/").pop()?.replace(/\.\w+$/, "") || "";
|
|
150
|
+
const importPath = `~/` + meta.path.replace(/\.tsx?$/, "");
|
|
151
|
+
|
|
152
|
+
// Skip sections handled specially below
|
|
153
|
+
const specialSections = [
|
|
154
|
+
"BackgroundWrapper", "CategoryBanner", "SearchResult",
|
|
155
|
+
"ProductDescription", "ProductFaq", "ProductReviews",
|
|
156
|
+
"SEOPDP", "CallCenter", "IsEvents", "Wishlist", "PrivacyPolice",
|
|
157
|
+
"InstagramPosts",
|
|
158
|
+
];
|
|
159
|
+
if (specialSections.includes(basename)) continue;
|
|
160
|
+
|
|
161
|
+
const mixins: string[] = [];
|
|
162
|
+
if (meta.loaderUsesDevice) {
|
|
163
|
+
mixins.push(meta.usesMobileBoolean ? "withMobile()" : "withDevice()");
|
|
164
|
+
}
|
|
165
|
+
if (meta.loaderUsesUrl) mixins.push("withSearchParam()");
|
|
166
|
+
if (meta.hasLoader) {
|
|
167
|
+
mixins.push(`withSectionLoader(() => import("${importPath}"))`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (mixins.length === 0) continue;
|
|
171
|
+
if (mixins.length === 1) {
|
|
172
|
+
entries.push(` "${sectionKey}": ${mixins[0]},`);
|
|
173
|
+
} else {
|
|
174
|
+
entries.push(` "${sectionKey}": compose(`);
|
|
175
|
+
for (const m of mixins) entries.push(` ${m},`);
|
|
176
|
+
entries.push(` ),`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------- BackgroundWrapper: nested section resolution ----------
|
|
181
|
+
if (hasBackgroundWrapper) {
|
|
182
|
+
entries.push(``);
|
|
183
|
+
entries.push(` // BackgroundWrapper: resolve nested sections`);
|
|
184
|
+
entries.push(` "site/sections/LpContent/BackgroundWrapper.tsx": async (props, req) => {`);
|
|
185
|
+
entries.push(` const sections = (props as any).sections ?? [];`);
|
|
186
|
+
entries.push(` const enrichedSections = await Promise.all(`);
|
|
187
|
+
entries.push(` sections.map(async (s: any) => {`);
|
|
188
|
+
entries.push(` const component = s.Component ?? s.component;`);
|
|
189
|
+
entries.push(` if (!component) return s;`);
|
|
190
|
+
entries.push(` const result = await runSingleSectionLoader({ component, props: s.props ?? {}, key: component, originalIndex: 0 } as any, req);`);
|
|
191
|
+
entries.push(` return { ...s, props: result.props };`);
|
|
192
|
+
entries.push(` }),`);
|
|
193
|
+
entries.push(` );`);
|
|
194
|
+
entries.push(` return { ...withMobile()(props, req), sections: enrichedSections };`);
|
|
195
|
+
entries.push(` },`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ---------- CategoryBanner: URLPattern matcher ----------
|
|
199
|
+
if (hasCategoryBanner) {
|
|
200
|
+
entries.push(``);
|
|
201
|
+
entries.push(` // CategoryBanner: match URL against banner patterns`);
|
|
202
|
+
entries.push(` "site/sections/Category/CategoryBanner.tsx": (props, req) => {`);
|
|
203
|
+
entries.push(` const banners = (props as any).banners ?? [];`);
|
|
204
|
+
entries.push(` const banner = banners.find(({ matcher }: { matcher: string }) => {`);
|
|
205
|
+
entries.push(` try {`);
|
|
206
|
+
entries.push(` return new URLPattern({ pathname: matcher }).test(req.url);`);
|
|
207
|
+
entries.push(` } catch {`);
|
|
208
|
+
entries.push(` return false;`);
|
|
209
|
+
entries.push(` }`);
|
|
210
|
+
entries.push(` });`);
|
|
211
|
+
entries.push(` return { ...props, banner };`);
|
|
212
|
+
entries.push(` },`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ---------- PDP fallbacks ----------
|
|
216
|
+
if (isVtex && needsCachedPDP) {
|
|
217
|
+
entries.push(``);
|
|
218
|
+
entries.push(` // PDP fallbacks — when CMS resolver fails to resolve nested __resolveType chain`);
|
|
219
|
+
|
|
220
|
+
if (hasProductDescription) {
|
|
221
|
+
entries.push(` "site/sections/Product/ProductDescription.tsx": async (props: any, req) => {`);
|
|
222
|
+
entries.push(` if (props.page?.product) return props;`);
|
|
223
|
+
entries.push(` const url = new URL(req.url);`);
|
|
224
|
+
entries.push(` const page = await cachedPDP({ __pagePath: url.pathname });`);
|
|
225
|
+
entries.push(` return { ...props, page };`);
|
|
226
|
+
entries.push(` },`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (hasProductFaq) {
|
|
230
|
+
entries.push(` "site/sections/Product/ProductFaq.tsx": async (props: any, req) => {`);
|
|
231
|
+
entries.push(` if (props.page?.product) return props;`);
|
|
232
|
+
entries.push(` const url = new URL(req.url);`);
|
|
233
|
+
entries.push(` const page = await cachedPDP({ __pagePath: url.pathname });`);
|
|
234
|
+
entries.push(` return { ...props, page };`);
|
|
235
|
+
entries.push(` },`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (hasProductReviews && hasProductReviewsLoader) {
|
|
239
|
+
entries.push(` "site/sections/Product/ProductReviews.tsx": async (props: any, req) => {`);
|
|
240
|
+
entries.push(` if (props.page?.reviews) return props;`);
|
|
241
|
+
entries.push(` const url = new URL(req.url);`);
|
|
242
|
+
entries.push(` const pdpPage = await cachedPDP({ __pagePath: url.pathname });`);
|
|
243
|
+
entries.push(` if (!pdpPage) return props;`);
|
|
244
|
+
entries.push(` const { account } = getVtexConfig();`);
|
|
245
|
+
entries.push(` const result = await productReviewsLoader(`);
|
|
246
|
+
entries.push(` { product: pdpPage },`);
|
|
247
|
+
entries.push(` null as any,`);
|
|
248
|
+
entries.push(` { account${hasSecrets ? ", ...secrets" : ""} } as any,`);
|
|
249
|
+
entries.push(` );`);
|
|
250
|
+
entries.push(` if (!result) return props;`);
|
|
251
|
+
entries.push(` const { getProductReview: _r, reviewLikeAction: _l, reviewVote: _v, getProductsListReviews: _p, ...serializable } = result as any;`);
|
|
252
|
+
entries.push(` return { ...props, page: serializable };`);
|
|
253
|
+
entries.push(` },`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------- SearchResult ----------
|
|
258
|
+
if (hasSearchResult) {
|
|
259
|
+
entries.push(``);
|
|
260
|
+
entries.push(` // SearchResult: URL parsing + device + SEO text + sponsored info`);
|
|
261
|
+
entries.push(` "site/sections/Product/SearchResult.tsx": (props: any, req) => {`);
|
|
262
|
+
entries.push(` const url = new URL(req.url);`);
|
|
263
|
+
entries.push(` const currentSearchTerm = url.searchParams.get("q") || null;`);
|
|
264
|
+
entries.push(` const pathname = url.pathname;`);
|
|
265
|
+
entries.push(` const page = props?.page;`);
|
|
266
|
+
entries.push(` const device = detectDevice(req.headers.get("user-agent") ?? "");`);
|
|
267
|
+
entries.push(``);
|
|
268
|
+
entries.push(` const seoTexts = [...(props.seoTexts || []), ...(page?.seoTexts || [])];`);
|
|
269
|
+
entries.push(` const sortedSeoTexts = seoTexts?.sort(`);
|
|
270
|
+
entries.push(` (a: any, b: any) => (b.route?.split("/")?.length ?? 0) - (a.route?.split("/")?.length ?? 0),`);
|
|
271
|
+
entries.push(` );`);
|
|
272
|
+
entries.push(` const seoText = sortedSeoTexts?.find(`);
|
|
273
|
+
entries.push(` (st: any) =>`);
|
|
274
|
+
entries.push(` pathname === st.route?.split("?")[0] ||`);
|
|
275
|
+
entries.push(` pathname === st.route?.split("?")[0]?.replace(",", "-"),`);
|
|
276
|
+
entries.push(` ) || { title: "", text: "", route: "", bottomText: "" };`);
|
|
277
|
+
entries.push(``);
|
|
278
|
+
entries.push(` let sponsoredInfo: {`);
|
|
279
|
+
entries.push(` pageType: string | undefined;`);
|
|
280
|
+
entries.push(` category: string | null;`);
|
|
281
|
+
entries.push(` query: string | null;`);
|
|
282
|
+
entries.push(` device: string;`);
|
|
283
|
+
entries.push(` } | null = null;`);
|
|
284
|
+
entries.push(` if (props.enableSponsoredBanner) {`);
|
|
285
|
+
entries.push(` try {`);
|
|
286
|
+
entries.push(` const pageType = page?.pageInfo?.pageTypes?.[0];`);
|
|
287
|
+
entries.push(` let category: string | null = null;`);
|
|
288
|
+
entries.push(` let query: string | null = null;`);
|
|
289
|
+
entries.push(` if (pageType === "Department") {`);
|
|
290
|
+
entries.push(` category = page?.breadcrumb?.itemListElement?.[0]?.name || null;`);
|
|
291
|
+
entries.push(` } else if (pageType === "Search") {`);
|
|
292
|
+
entries.push(` query = currentSearchTerm;`);
|
|
293
|
+
entries.push(` }`);
|
|
294
|
+
entries.push(` if (category || query) {`);
|
|
295
|
+
entries.push(` sponsoredInfo = { pageType, category, query, device };`);
|
|
296
|
+
entries.push(` }`);
|
|
297
|
+
entries.push(` } catch {`);
|
|
298
|
+
entries.push(` sponsoredInfo = null;`);
|
|
299
|
+
entries.push(` }`);
|
|
300
|
+
entries.push(` }`);
|
|
301
|
+
entries.push(``);
|
|
302
|
+
entries.push(` return {`);
|
|
303
|
+
entries.push(` ...props,`);
|
|
304
|
+
entries.push(` sponsoredInfo,`);
|
|
305
|
+
entries.push(` seoText,`);
|
|
306
|
+
entries.push(` device,`);
|
|
307
|
+
entries.push(` page: page ? { ...page, products: page.products ?? [] } : page,`);
|
|
308
|
+
entries.push(` currentSearchTerm,`);
|
|
309
|
+
entries.push(` ...(!page?.products?.length && { notFoundPage: props.notFoundPage }),`);
|
|
310
|
+
entries.push(` };`);
|
|
311
|
+
entries.push(` },`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ---------- SEO + analytics delegation ----------
|
|
315
|
+
if (hasSEOPDP) {
|
|
316
|
+
entries.push(``);
|
|
317
|
+
entries.push(` "site/sections/SEOPDP.tsx": async (props: any, _req) => {`);
|
|
318
|
+
entries.push(` const mod = await import("../sections/SEOPDP");`);
|
|
319
|
+
entries.push(` const result = mod.loader(props, _req, { seo: {} } as any);`);
|
|
320
|
+
entries.push(` return result ?? props;`);
|
|
321
|
+
entries.push(` },`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (hasCallCenter) {
|
|
325
|
+
entries.push(` "site/sections/CallCenter.tsx": (props: any, req) => {`);
|
|
326
|
+
entries.push(` const url = new URL(req.url);`);
|
|
327
|
+
entries.push(` return { ...props, pathname: url.pathname };`);
|
|
328
|
+
entries.push(` },`);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (hasIsEvents) {
|
|
332
|
+
entries.push(` "site/sections/Analytics/IsEvents.tsx": async (props: any, req) => {`);
|
|
333
|
+
entries.push(` const mod = await import("../sections/Analytics/IsEvents");`);
|
|
334
|
+
entries.push(` return mod.loader(props, req) as unknown as Record<string, unknown>;`);
|
|
335
|
+
entries.push(` },`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ---------- Account sections ----------
|
|
339
|
+
if (isVtex && hasAccountSections) {
|
|
340
|
+
entries.push(``);
|
|
341
|
+
entries.push(` // Account sections — via @decocms/apps factory`);
|
|
342
|
+
|
|
343
|
+
for (const meta of ctx.sectionMetas) {
|
|
344
|
+
if (!meta.isAccountSection) continue;
|
|
345
|
+
const sectionKey = `site/${meta.path}`;
|
|
346
|
+
const basename = meta.path.split("/").pop()?.replace(/\.\w+$/, "") || "";
|
|
347
|
+
const loaderName = getAccountLoaderName(basename);
|
|
348
|
+
|
|
349
|
+
if (basename === "PersonalData") {
|
|
350
|
+
entries.push(` "${sectionKey}": vtexAccountLoaders.personalData({`);
|
|
351
|
+
entries.push(` extraProfileFields: ["isNewsletterOptIn", "cartAbandoned"],`);
|
|
352
|
+
entries.push(` mapProfile: (p) => ({`);
|
|
353
|
+
entries.push(` "@id": p.userId ?? p.id,`);
|
|
354
|
+
entries.push(` email: p.email,`);
|
|
355
|
+
entries.push(` givenName: p.firstName ?? null,`);
|
|
356
|
+
entries.push(` familyName: p.lastName ?? null,`);
|
|
357
|
+
entries.push(` taxID: p.document,`);
|
|
358
|
+
entries.push(` gender: p.gender,`);
|
|
359
|
+
entries.push(` telephone: p.homePhone,`);
|
|
360
|
+
entries.push(` birthDate: p.birthDate,`);
|
|
361
|
+
entries.push(` corporateName: p.corporateName,`);
|
|
362
|
+
entries.push(` tradeName: p.tradeName,`);
|
|
363
|
+
entries.push(` corporateDocument: p.corporateDocument,`);
|
|
364
|
+
entries.push(` businessPhone: p.businessPhone,`);
|
|
365
|
+
entries.push(` stateRegistration: p.stateRegistration,`);
|
|
366
|
+
entries.push(` isCorporate: p.isCorporate,`);
|
|
367
|
+
entries.push(` customFields: p.customFields,`);
|
|
368
|
+
entries.push(` }),`);
|
|
369
|
+
entries.push(` }),`);
|
|
370
|
+
} else {
|
|
371
|
+
entries.push(` "${sectionKey}": vtexAccountLoaders.${loaderName}(),`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ---------- Wishlist ----------
|
|
377
|
+
if (isVtex && hasWishlistSection && hasWishlistLoaders) {
|
|
378
|
+
entries.push(``);
|
|
379
|
+
entries.push(` // Wishlist`);
|
|
380
|
+
entries.push(` "site/sections/Wishlist.tsx": async (props: any, req) => {`);
|
|
381
|
+
entries.push(` const cookie = getVtexCookies(req);`);
|
|
382
|
+
entries.push(` try {`);
|
|
383
|
+
entries.push(` const userData = await getUser(cookie);`);
|
|
384
|
+
entries.push(` const userId = userData?.email ? (userData.email as string) : "";`);
|
|
385
|
+
entries.push(` if (!userId) return { ...props, wishlist: null };`);
|
|
386
|
+
entries.push(``);
|
|
387
|
+
entries.push(` const listResponse = await getWishlistList({ userId });`);
|
|
388
|
+
entries.push(` if (listResponse && typeof listResponse === "object" && "data" in (listResponse as any)) {`);
|
|
389
|
+
entries.push(` const { data } = listResponse as { data: { id: string; title: string }[] };`);
|
|
390
|
+
entries.push(` if (data?.length > 0) {`);
|
|
391
|
+
entries.push(` const firstList = data[0];`);
|
|
392
|
+
entries.push(` const itemsResponse = await getWishlistItems({ listId: firstList.id, userId });`);
|
|
393
|
+
entries.push(` if (itemsResponse && typeof itemsResponse === "object" && "data" in (itemsResponse as any)) {`);
|
|
394
|
+
entries.push(` const { data: itemsData } = itemsResponse as any;`);
|
|
395
|
+
entries.push(` return {`);
|
|
396
|
+
entries.push(` ...props,`);
|
|
397
|
+
entries.push(` wishlist: {`);
|
|
398
|
+
entries.push(` title: itemsData.title as string,`);
|
|
399
|
+
entries.push(` products: itemsData.products ?? [],`);
|
|
400
|
+
entries.push(` id: firstList.id,`);
|
|
401
|
+
entries.push(` userId,`);
|
|
402
|
+
entries.push(` },`);
|
|
403
|
+
entries.push(` };`);
|
|
404
|
+
entries.push(` }`);
|
|
405
|
+
entries.push(` return { ...props, wishlist: { title: firstList.title, products: [], id: firstList.id, userId } };`);
|
|
406
|
+
entries.push(` }`);
|
|
407
|
+
entries.push(` }`);
|
|
408
|
+
entries.push(` return { ...props, wishlist: null };`);
|
|
409
|
+
entries.push(` } catch (err) {`);
|
|
410
|
+
entries.push(` console.error("[Wishlist SectionLoader] Error:", err);`);
|
|
411
|
+
entries.push(` return { ...props, wishlist: null };`);
|
|
412
|
+
entries.push(` }`);
|
|
413
|
+
entries.push(` },`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ---------- Privacy cookie check ----------
|
|
417
|
+
if (isVtex && hasPrivacyPolice) {
|
|
418
|
+
entries.push(``);
|
|
419
|
+
const vtexAccount = ctx.vtexAccount || "casaevideonewio";
|
|
420
|
+
entries.push(` "site/sections/Account/PrivacyPolice.tsx": (props: any, req) => {`);
|
|
421
|
+
entries.push(` const cookies = req.headers.get("cookie") ?? "";`);
|
|
422
|
+
entries.push(` const logged = cookies.includes("VtexIdclientAutCookie_${vtexAccount}");`);
|
|
423
|
+
entries.push(` return { ...props, logged };`);
|
|
424
|
+
entries.push(` },`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ---------- Instagram ----------
|
|
428
|
+
if (hasInstagramSection) {
|
|
429
|
+
entries.push(``);
|
|
430
|
+
entries.push(` // Social`);
|
|
431
|
+
entries.push(` "site/sections/Social/InstagramPosts.tsx": async (props: any, _req) => {`);
|
|
432
|
+
entries.push(` const { facebookToken, layout, title, description } = props;`);
|
|
433
|
+
entries.push(` if (!facebookToken) return props;`);
|
|
434
|
+
entries.push(` try {`);
|
|
435
|
+
entries.push(` const fields = "media_url,media_type,permalink";`);
|
|
436
|
+
entries.push(" const apiUrl = `https://graph.instagram.com/me/media?access_token=${facebookToken}&fields=${fields}`;");
|
|
437
|
+
entries.push(` const { data } = (await fetch(apiUrl).then((r) => r.json())) as { data: any[] };`);
|
|
438
|
+
entries.push(` return {`);
|
|
439
|
+
entries.push(` data: (data || []).slice(0, layout?.numberOfPosts ?? 12),`);
|
|
440
|
+
entries.push(` title,`);
|
|
441
|
+
entries.push(` description,`);
|
|
442
|
+
entries.push(` layout,`);
|
|
443
|
+
entries.push(` };`);
|
|
444
|
+
entries.push(` } catch (err) {`);
|
|
445
|
+
entries.push(` console.error("[InstagramPosts] loader error:", err);`);
|
|
446
|
+
entries.push(` return props;`);
|
|
447
|
+
entries.push(` }`);
|
|
448
|
+
entries.push(` },`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
lines.push(`registerSectionLoaders({`);
|
|
452
|
+
lines.push(entries.join("\n"));
|
|
453
|
+
lines.push(`});`);
|
|
454
|
+
|
|
455
|
+
return lines.join("\n") + "\n";
|
|
456
|
+
}
|