@decocms/start 1.2.6 → 1.2.8
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 +1 -1
- package/scripts/deco-migrate-cli.ts +444 -0
- package/scripts/migrate/analyzers/island-classifier.ts +73 -0
- package/scripts/migrate/analyzers/loader-inventory.ts +63 -0
- package/scripts/migrate/analyzers/section-metadata.ts +91 -0
- package/scripts/migrate/analyzers/theme-extractor.ts +122 -0
- package/scripts/migrate/phase-analyze.ts +147 -17
- package/scripts/migrate/phase-cleanup.ts +124 -2
- package/scripts/migrate/phase-report.ts +44 -16
- package/scripts/migrate/phase-scaffold.ts +38 -132
- package/scripts/migrate/phase-transform.ts +28 -3
- package/scripts/migrate/phase-verify.ts +127 -5
- package/scripts/migrate/templates/app-css.ts +204 -0
- package/scripts/migrate/templates/cache-config.ts +26 -0
- package/scripts/migrate/templates/commerce-loaders.ts +124 -0
- package/scripts/migrate/templates/hooks.ts +358 -0
- package/scripts/migrate/templates/package-json.ts +29 -6
- package/scripts/migrate/templates/routes.ts +41 -136
- package/scripts/migrate/templates/sdk-gen.ts +59 -0
- package/scripts/migrate/templates/section-loaders.ts +108 -0
- package/scripts/migrate/templates/server-entry.ts +174 -67
- package/scripts/migrate/templates/setup.ts +64 -55
- package/scripts/migrate/templates/types-gen.ts +119 -0
- package/scripts/migrate/templates/ui-components.ts +113 -0
- package/scripts/migrate/templates/vite-config.ts +18 -1
- package/scripts/migrate/templates/wrangler.ts +4 -1
- package/scripts/migrate/transforms/dead-code.ts +23 -2
- package/scripts/migrate/transforms/imports.ts +40 -10
- package/scripts/migrate/transforms/jsx.ts +9 -0
- package/scripts/migrate/transforms/section-conventions.ts +83 -0
- package/scripts/migrate/types.ts +74 -0
- package/src/routes/cmsRoute.ts +26 -0
|
@@ -17,99 +17,11 @@ export function generateRoutes(
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function generateRoot(ctx: MigrationContext, siteTitle: string): string {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// Google Tag Manager
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (typeof window === "undefined") return;
|
|
25
|
-
const script = document.createElement("script");
|
|
26
|
-
script.async = true;
|
|
27
|
-
script.src = "https://www.googletagmanager.com/gtm.js?id=${ctx.gtmId}";
|
|
28
|
-
document.head.appendChild(script);
|
|
29
|
-
window.dataLayer = window.dataLayer || [];
|
|
30
|
-
window.dataLayer.push({ "gtm.start": Date.now(), event: "gtm.js" });
|
|
31
|
-
}, []);`
|
|
32
|
-
: "";
|
|
33
|
-
|
|
34
|
-
return `import { useState, useEffect, useRef } from "react";
|
|
35
|
-
import {
|
|
36
|
-
createRootRoute,
|
|
37
|
-
HeadContent,
|
|
38
|
-
Outlet,
|
|
39
|
-
Scripts,
|
|
40
|
-
useRouterState,
|
|
41
|
-
} from "@tanstack/react-router";
|
|
42
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
43
|
-
import { LiveControls } from "@decocms/start/hooks";
|
|
44
|
-
import { ANALYTICS_SCRIPT } from "@decocms/start/sdk/analytics";
|
|
20
|
+
return `import { createRootRoute } from "@tanstack/react-router";
|
|
21
|
+
import { DecoRootLayout } from "@decocms/start/hooks";
|
|
45
22
|
// @ts-ignore Vite ?url import
|
|
46
23
|
import appCss from "../styles/app.css?url";
|
|
47
24
|
|
|
48
|
-
declare global {
|
|
49
|
-
interface Window {
|
|
50
|
-
__deco_ready?: boolean;
|
|
51
|
-
dataLayer: unknown[];
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const PROGRESS_CSS = \`
|
|
56
|
-
@keyframes progressSlide { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
|
|
57
|
-
.nav-progress-bar { animation: progressSlide 1s ease-in-out infinite; }
|
|
58
|
-
\`;
|
|
59
|
-
|
|
60
|
-
function NavigationProgress() {
|
|
61
|
-
const isLoading = useRouterState({ select: (s) => s.isLoading });
|
|
62
|
-
if (!isLoading) return null;
|
|
63
|
-
return (
|
|
64
|
-
<div className="fixed top-0 left-0 right-0 z-[9999] h-1 bg-primary/20 overflow-hidden">
|
|
65
|
-
<style dangerouslySetInnerHTML={{ __html: PROGRESS_CSS }} />
|
|
66
|
-
<div className="nav-progress-bar h-full w-1/3 bg-primary rounded-full" />
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function StableOutlet() {
|
|
72
|
-
const isLoading = useRouterState({ select: (s) => s.isLoading });
|
|
73
|
-
const ref = useRef<HTMLDivElement>(null);
|
|
74
|
-
const savedHeight = useRef<number | undefined>(undefined);
|
|
75
|
-
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (isLoading && ref.current) {
|
|
78
|
-
savedHeight.current = ref.current.offsetHeight;
|
|
79
|
-
}
|
|
80
|
-
if (!isLoading) {
|
|
81
|
-
savedHeight.current = undefined;
|
|
82
|
-
}
|
|
83
|
-
}, [isLoading]);
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div ref={ref} style={savedHeight.current ? { minHeight: savedHeight.current } : undefined}>
|
|
87
|
-
<Outlet />
|
|
88
|
-
</div>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const DECO_EVENTS_BOOTSTRAP = \`
|
|
93
|
-
window.DECO = window.DECO || {};
|
|
94
|
-
window.DECO.events = window.DECO.events || {
|
|
95
|
-
_q: [],
|
|
96
|
-
_subs: [],
|
|
97
|
-
dispatch: function(e) {
|
|
98
|
-
this._q.push(e);
|
|
99
|
-
for (var i = 0; i < this._subs.length; i++) {
|
|
100
|
-
try { this._subs[i](e); } catch(err) { console.error('[DECO.events]', err); }
|
|
101
|
-
}
|
|
102
|
-
},
|
|
103
|
-
subscribe: function(fn) {
|
|
104
|
-
this._subs.push(fn);
|
|
105
|
-
for (var i = 0; i < this._q.length; i++) {
|
|
106
|
-
try { fn(this._q[i]); } catch(err) {}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
window.dataLayer = window.dataLayer || [];
|
|
111
|
-
\`;
|
|
112
|
-
|
|
113
25
|
export const Route = createRootRoute({
|
|
114
26
|
head: () => ({
|
|
115
27
|
meta: [
|
|
@@ -126,45 +38,11 @@ export const Route = createRootRoute({
|
|
|
126
38
|
});
|
|
127
39
|
|
|
128
40
|
function RootLayout() {
|
|
129
|
-
const [queryClient] = useState(
|
|
130
|
-
() =>
|
|
131
|
-
new QueryClient({
|
|
132
|
-
defaultOptions: {
|
|
133
|
-
queries: { staleTime: 30_000 },
|
|
134
|
-
},
|
|
135
|
-
}),
|
|
136
|
-
);
|
|
137
|
-
${gtmScript}
|
|
138
|
-
|
|
139
|
-
useEffect(() => {
|
|
140
|
-
const id = setTimeout(() => {
|
|
141
|
-
window.__deco_ready = true;
|
|
142
|
-
document.dispatchEvent(new Event("deco:ready"));
|
|
143
|
-
}, 500);
|
|
144
|
-
return () => clearTimeout(id);
|
|
145
|
-
}, []);
|
|
146
|
-
|
|
147
41
|
return (
|
|
148
|
-
<
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
153
|
-
<link href="https://fonts.googleapis.com/css2?family=${encodeURIComponent(ctx.fontFamily)}:wght@400;500;600;700&display=swap" rel="stylesheet" />` : ""}
|
|
154
|
-
</head>
|
|
155
|
-
<body className="bg-base-200 text-base-content" suppressHydrationWarning>
|
|
156
|
-
<script dangerouslySetInnerHTML={{ __html: DECO_EVENTS_BOOTSTRAP }} />
|
|
157
|
-
<QueryClientProvider client={queryClient}>
|
|
158
|
-
<NavigationProgress />
|
|
159
|
-
<main>
|
|
160
|
-
<StableOutlet />
|
|
161
|
-
</main>
|
|
162
|
-
</QueryClientProvider>
|
|
163
|
-
<LiveControls site="${ctx.siteName}" />
|
|
164
|
-
<script type="module" dangerouslySetInnerHTML={{ __html: ANALYTICS_SCRIPT }} />
|
|
165
|
-
<Scripts />
|
|
166
|
-
</body>
|
|
167
|
-
</html>
|
|
42
|
+
<DecoRootLayout
|
|
43
|
+
lang="pt-BR"
|
|
44
|
+
siteName="${ctx.siteName}"
|
|
45
|
+
/>
|
|
168
46
|
);
|
|
169
47
|
}
|
|
170
48
|
`;
|
|
@@ -172,7 +50,8 @@ ${gtmScript}
|
|
|
172
50
|
|
|
173
51
|
function generateIndex(siteTitle: string): string {
|
|
174
52
|
return `import { createFileRoute } from "@tanstack/react-router";
|
|
175
|
-
import { cmsHomeRouteConfig,
|
|
53
|
+
import { cmsHomeRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
54
|
+
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
176
55
|
|
|
177
56
|
export const Route = createFileRoute("/")({
|
|
178
57
|
...cmsHomeRouteConfig({
|
|
@@ -184,15 +63,26 @@ export const Route = createFileRoute("/")({
|
|
|
184
63
|
|
|
185
64
|
function HomePage() {
|
|
186
65
|
const data = Route.useLoaderData() as Record<string, any> | null;
|
|
187
|
-
|
|
66
|
+
|
|
67
|
+
if (!data) {
|
|
68
|
+
return (
|
|
69
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
70
|
+
<div className="text-center">
|
|
71
|
+
<h1 className="text-4xl font-bold mb-4">${siteTitle}</h1>
|
|
72
|
+
<p className="text-sm text-base-content/40 mt-2">No CMS page found for /</p>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
188
77
|
|
|
189
78
|
return (
|
|
190
|
-
<
|
|
79
|
+
<DecoPageRenderer
|
|
191
80
|
sections={data.resolvedSections ?? []}
|
|
192
81
|
deferredSections={data.deferredSections ?? []}
|
|
193
82
|
deferredPromises={data.deferredPromises}
|
|
194
83
|
pagePath={data.pagePath}
|
|
195
84
|
pageUrl={data.pageUrl}
|
|
85
|
+
loadDeferredSectionFn={deferredSectionLoader}
|
|
196
86
|
/>
|
|
197
87
|
);
|
|
198
88
|
}
|
|
@@ -201,34 +91,49 @@ function HomePage() {
|
|
|
201
91
|
|
|
202
92
|
function generateCatchAll(siteTitle: string): string {
|
|
203
93
|
return `import { createFileRoute } from "@tanstack/react-router";
|
|
204
|
-
import { cmsRouteConfig,
|
|
94
|
+
import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
95
|
+
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
205
96
|
|
|
206
97
|
const routeConfig = cmsRouteConfig({
|
|
207
98
|
siteName: "${siteTitle}",
|
|
208
99
|
defaultTitle: "${siteTitle}",
|
|
100
|
+
ignoreSearchParams: ["skuId"],
|
|
209
101
|
});
|
|
210
102
|
|
|
211
103
|
export const Route = createFileRoute("/$")({
|
|
212
104
|
...routeConfig,
|
|
213
|
-
component:
|
|
105
|
+
component: CmsPage,
|
|
214
106
|
notFoundComponent: NotFoundPage,
|
|
215
|
-
staleTime: 30_000,
|
|
216
107
|
});
|
|
217
108
|
|
|
218
|
-
function
|
|
109
|
+
function CmsPage() {
|
|
219
110
|
const data = Route.useLoaderData() as Record<string, any> | null;
|
|
220
111
|
if (!data) return <NotFoundPage />;
|
|
221
112
|
|
|
222
113
|
return (
|
|
223
|
-
<
|
|
114
|
+
<DecoPageRenderer
|
|
224
115
|
sections={data.resolvedSections ?? []}
|
|
225
116
|
deferredSections={data.deferredSections ?? []}
|
|
226
117
|
deferredPromises={data.deferredPromises}
|
|
227
118
|
pagePath={data.pagePath}
|
|
228
119
|
pageUrl={data.pageUrl}
|
|
120
|
+
loadDeferredSectionFn={deferredSectionLoader}
|
|
229
121
|
/>
|
|
230
122
|
);
|
|
231
123
|
}
|
|
124
|
+
|
|
125
|
+
function NotFoundPage() {
|
|
126
|
+
return (
|
|
127
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
128
|
+
<div className="text-center">
|
|
129
|
+
<h1 className="text-6xl font-bold text-base-content/20 mb-4">404</h1>
|
|
130
|
+
<h2 className="text-2xl font-bold mb-2">Page Not Found</h2>
|
|
131
|
+
<p className="text-base-content/60 mb-6">No CMS page block matches this URL.</p>
|
|
132
|
+
<a href="/" className="btn btn-primary">Go Home</a>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
232
137
|
`;
|
|
233
138
|
}
|
|
234
139
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { MigrationContext } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export function generateSdkFiles(ctx: MigrationContext): Record<string, string> {
|
|
4
|
+
const files: Record<string, string> = {};
|
|
5
|
+
|
|
6
|
+
files["src/sdk/deviceServer.ts"] = generateDeviceServer();
|
|
7
|
+
files["src/sdk/logger.ts"] = generateLogger();
|
|
8
|
+
|
|
9
|
+
return files;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function generateDeviceServer(): string {
|
|
13
|
+
return `import { createServerFn } from "@tanstack/react-start";
|
|
14
|
+
import { getRequestHeader } from "@tanstack/react-start/server";
|
|
15
|
+
import { detectDevice } from "@decocms/start/sdk/useDevice";
|
|
16
|
+
|
|
17
|
+
export const getDeviceFromServer = createServerFn({ method: "GET" }).handler(
|
|
18
|
+
async () => {
|
|
19
|
+
const ua = getRequestHeader("user-agent") ?? "";
|
|
20
|
+
return detectDevice(ua);
|
|
21
|
+
},
|
|
22
|
+
);
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function generateLogger(): string {
|
|
27
|
+
return `type LogLevel = "debug" | "info" | "warn" | "error";
|
|
28
|
+
|
|
29
|
+
const LEVELS: Record<LogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
30
|
+
let minLevel: LogLevel = "info";
|
|
31
|
+
|
|
32
|
+
export function setLogLevel(level: LogLevel) {
|
|
33
|
+
minLevel = level;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function shouldLog(level: LogLevel): boolean {
|
|
37
|
+
return LEVELS[level] >= LEVELS[minLevel];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function debug(...args: unknown[]) {
|
|
41
|
+
if (shouldLog("debug")) console.debug(...args);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function info(...args: unknown[]) {
|
|
45
|
+
if (shouldLog("info")) console.info(...args);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function warn(...args: unknown[]) {
|
|
49
|
+
if (shouldLog("warn")) console.warn(...args);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function error(...args: unknown[]) {
|
|
53
|
+
if (shouldLog("error")) console.error(...args);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const logger = { debug, info, warn, error, setLogLevel };
|
|
57
|
+
export default logger;
|
|
58
|
+
`;
|
|
59
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { MigrationContext, SectionMeta } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
const ACCOUNT_LOADER_MAP: Record<string, string> = {
|
|
4
|
+
personaldata: "personalData",
|
|
5
|
+
myorders: "orders",
|
|
6
|
+
orders: "orders",
|
|
7
|
+
cards: "cards",
|
|
8
|
+
payments: "cards",
|
|
9
|
+
addresses: "addresses",
|
|
10
|
+
auth: "authentication",
|
|
11
|
+
authentication: "authentication",
|
|
12
|
+
login: "authentication",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function getAccountLoaderName(sectionBasename: string): string {
|
|
16
|
+
const key = sectionBasename.toLowerCase().replace(/[^a-z]/g, "");
|
|
17
|
+
return ACCOUNT_LOADER_MAP[key] || "loggedIn";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function generateSectionLoaders(ctx: MigrationContext): string {
|
|
21
|
+
const lines: string[] = [];
|
|
22
|
+
const isVtex = ctx.platform === "vtex";
|
|
23
|
+
const hasAccountSections = isVtex && ctx.sectionMetas.some((m) => m.isAccountSection && m.hasLoader);
|
|
24
|
+
|
|
25
|
+
lines.push(`/**`);
|
|
26
|
+
lines.push(` * Section Loaders — server-side prop enrichment for CMS sections.`);
|
|
27
|
+
lines.push(` *`);
|
|
28
|
+
lines.push(` * Simple patterns (device, mobile) use framework mixins.`);
|
|
29
|
+
lines.push(` * Complex loaders delegate to the section's own loader export.`);
|
|
30
|
+
lines.push(` * Account sections use vtexAccountLoaders from @decocms/apps.`);
|
|
31
|
+
lines.push(` */`);
|
|
32
|
+
lines.push(`import {`);
|
|
33
|
+
lines.push(` registerSectionLoaders,`);
|
|
34
|
+
lines.push(` withDevice,`);
|
|
35
|
+
lines.push(` withMobile,`);
|
|
36
|
+
lines.push(` withSearchParam,`);
|
|
37
|
+
lines.push(` compose,`);
|
|
38
|
+
lines.push(`} from "@decocms/start/cms";`);
|
|
39
|
+
|
|
40
|
+
if (hasAccountSections) {
|
|
41
|
+
lines.push(`import { vtexAccountLoaders } from "@decocms/apps/vtex/utils/accountLoaders";`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
lines.push(``);
|
|
45
|
+
|
|
46
|
+
const entries: string[] = [];
|
|
47
|
+
|
|
48
|
+
for (const meta of ctx.sectionMetas) {
|
|
49
|
+
if (!meta.hasLoader) continue;
|
|
50
|
+
|
|
51
|
+
const sectionKey = `site/${meta.path}`;
|
|
52
|
+
const basename = meta.path.split("/").pop()?.replace(/\.\w+$/, "") || "";
|
|
53
|
+
|
|
54
|
+
// Skip status-only loaders (they just set ctx.response.status — handled at route level)
|
|
55
|
+
if (meta.isStatusOnly) {
|
|
56
|
+
entries.push(` // ${meta.path}: status-only loader — handled at route/worker level, no section loader needed`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Account sections -> vtexAccountLoaders
|
|
61
|
+
if (isVtex && meta.isAccountSection) {
|
|
62
|
+
const loaderName = getAccountLoaderName(basename);
|
|
63
|
+
entries.push(` // Account: ${basename}`);
|
|
64
|
+
entries.push(` "${sectionKey}": vtexAccountLoaders.${loaderName}(),`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Header: compose device + search param
|
|
69
|
+
if (meta.isHeader) {
|
|
70
|
+
entries.push(` // Header: device + search param`);
|
|
71
|
+
entries.push(` "${sectionKey}": compose(withDevice(), withSearchParam()),`);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Simple mixins
|
|
76
|
+
if (meta.loaderUsesDevice && meta.loaderUsesUrl) {
|
|
77
|
+
const deviceMixin = meta.usesMobileBoolean ? "withMobile()" : "withDevice()";
|
|
78
|
+
entries.push(` // ${meta.path}: ${meta.usesMobileBoolean ? "mobile" : "device"} + URL`);
|
|
79
|
+
entries.push(` "${sectionKey}": compose(${deviceMixin}, withSearchParam()),`);
|
|
80
|
+
} else if (meta.loaderUsesDevice) {
|
|
81
|
+
if (meta.usesMobileBoolean) {
|
|
82
|
+
entries.push(` // ${meta.path}: mobile detection`);
|
|
83
|
+
entries.push(` "${sectionKey}": withMobile(),`);
|
|
84
|
+
} else {
|
|
85
|
+
entries.push(` // ${meta.path}: device detection`);
|
|
86
|
+
entries.push(` "${sectionKey}": withDevice(),`);
|
|
87
|
+
}
|
|
88
|
+
} else if (meta.loaderUsesUrl) {
|
|
89
|
+
entries.push(` // ${meta.path}: URL/search params`);
|
|
90
|
+
entries.push(` "${sectionKey}": withSearchParam(),`);
|
|
91
|
+
} else {
|
|
92
|
+
// Complex loader — delegate to the section's own loader export
|
|
93
|
+
const importPath = `~/` + meta.path.replace(/\.tsx?$/, "");
|
|
94
|
+
entries.push(` // ${meta.path}: complex loader — delegated to section's loader export`);
|
|
95
|
+
entries.push(` "${sectionKey}": async (props: any, req: Request) => {`);
|
|
96
|
+
entries.push(` const mod = await import("${importPath}");`);
|
|
97
|
+
entries.push(` if (typeof mod.loader === "function") return mod.loader(props, req);`);
|
|
98
|
+
entries.push(` return props;`);
|
|
99
|
+
entries.push(` },`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
lines.push(`registerSectionLoaders({`);
|
|
104
|
+
lines.push(entries.join("\n"));
|
|
105
|
+
lines.push(`});`);
|
|
106
|
+
|
|
107
|
+
return lines.join("\n") + "\n";
|
|
108
|
+
}
|