@decocms/start 0.31.1 → 0.32.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.
@@ -0,0 +1,59 @@
1
+ import type { MigrationContext } from "../types.ts";
2
+
3
+ export function generatePackageJson(ctx: MigrationContext): string {
4
+ const pkg = {
5
+ name: ctx.siteName,
6
+ version: "0.1.0",
7
+ type: "module",
8
+ description: `${ctx.siteName} storefront powered by TanStack Start`,
9
+ scripts: {
10
+ dev: "vite dev",
11
+ "generate:blocks":
12
+ "tsx node_modules/@decocms/start/scripts/generate-blocks.ts",
13
+ "generate:routes": "tsr generate",
14
+ "generate:schema": `tsx node_modules/@decocms/start/scripts/generate-schema.ts --site ${ctx.siteName}`,
15
+ build:
16
+ "npm run generate:blocks && npm run generate:schema && tsr generate && vite build",
17
+ preview: "vite preview",
18
+ deploy: "npm run build && wrangler deploy",
19
+ types: "wrangler types",
20
+ typecheck: "tsc --noEmit",
21
+ format: 'prettier --write "src/**/*.{ts,tsx}"',
22
+ "format:check": 'prettier --check "src/**/*.{ts,tsx}"',
23
+ knip: "knip",
24
+ },
25
+ author: "deco.cx",
26
+ license: "MIT",
27
+ dependencies: {
28
+ "@decocms/apps": "^0.25.2",
29
+ "@decocms/start": "^0.31.1",
30
+ "@tanstack/react-query": "5.90.21",
31
+ "@tanstack/react-router": "1.166.7",
32
+ "@tanstack/react-start": "1.166.8",
33
+ "@tanstack/react-store": "0.9.2",
34
+ "@tanstack/store": "0.9.2",
35
+ "colorjs.io": "^0.6.1",
36
+ react: "^19.2.4",
37
+ "react-dom": "^19.2.4",
38
+ },
39
+ devDependencies: {
40
+ "@cloudflare/vite-plugin": "^1.27.0",
41
+ "@tailwindcss/vite": "^4.2.1",
42
+ "@tanstack/router-cli": "1.166.7",
43
+ "@types/react": "^19.2.14",
44
+ "@types/react-dom": "^19.2.3",
45
+ "@vitejs/plugin-react": "^5.1.4",
46
+ "babel-plugin-react-compiler": "^1.0.0",
47
+ "daisyui": "^5.5.19",
48
+ knip: "^5.61.2",
49
+ prettier: "^3.5.3",
50
+ tailwindcss: "^4.2.1",
51
+ tsx: "^4.19.4",
52
+ typescript: "^5.9.3",
53
+ vite: "^7.3.1",
54
+ wrangler: "^4.72.0",
55
+ },
56
+ };
57
+
58
+ return JSON.stringify(pkg, null, 2) + "\n";
59
+ }
@@ -0,0 +1,280 @@
1
+ import type { MigrationContext } from "../types.ts";
2
+
3
+ export function generateRoutes(
4
+ ctx: MigrationContext,
5
+ ): Record<string, string> {
6
+ const siteName = ctx.siteName;
7
+ const siteTitle = siteName.charAt(0).toUpperCase() + siteName.slice(1);
8
+
9
+ return {
10
+ "src/routes/__root.tsx": generateRoot(ctx, siteTitle),
11
+ "src/routes/index.tsx": generateIndex(siteTitle),
12
+ "src/routes/$.tsx": generateCatchAll(siteTitle),
13
+ "src/routes/deco/meta.ts": generateDecoMeta(),
14
+ "src/routes/deco/invoke.$.ts": generateDecoInvoke(),
15
+ "src/routes/deco/render.ts": generateDecoRender(),
16
+ };
17
+ }
18
+
19
+ function generateRoot(ctx: MigrationContext, siteTitle: string): string {
20
+ const gtmScript = ctx.gtmId
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";
45
+ // @ts-ignore Vite ?url import
46
+ import appCss from "../styles/app.css?url";
47
+
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>();
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
+ export const Route = createRootRoute({
114
+ head: () => ({
115
+ meta: [
116
+ { charSet: "utf-8" },
117
+ { name: "viewport", content: "width=device-width, initial-scale=1" },
118
+ { title: "${siteTitle}" },
119
+ ],
120
+ links: [
121
+ { rel: "stylesheet", href: appCss },
122
+ { rel: "icon", href: "/favicon.ico" },
123
+ ],
124
+ }),
125
+ component: RootLayout,
126
+ });
127
+
128
+ 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
+ return (
148
+ <html lang="pt-BR" data-theme="light" suppressHydrationWarning>
149
+ <head>
150
+ <HeadContent />
151
+ </head>
152
+ <body className="bg-base-200 text-base-content" suppressHydrationWarning>
153
+ <script dangerouslySetInnerHTML={{ __html: DECO_EVENTS_BOOTSTRAP }} />
154
+ <QueryClientProvider client={queryClient}>
155
+ <NavigationProgress />
156
+ <main>
157
+ <StableOutlet />
158
+ </main>
159
+ </QueryClientProvider>
160
+ <LiveControls site="${ctx.siteName}" />
161
+ <script type="module" dangerouslySetInnerHTML={{ __html: ANALYTICS_SCRIPT }} />
162
+ <Scripts />
163
+ </body>
164
+ </html>
165
+ );
166
+ }
167
+ `;
168
+ }
169
+
170
+ function generateIndex(siteTitle: string): string {
171
+ return `import { createFileRoute } from "@tanstack/react-router";
172
+ import { cmsHomeRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
173
+ import { DecoPageRenderer } from "@decocms/start/hooks";
174
+
175
+ export const Route = createFileRoute("/")({
176
+ ...cmsHomeRouteConfig({
177
+ defaultTitle: "${siteTitle}",
178
+ siteName: "${siteTitle}",
179
+ }),
180
+ component: HomePage,
181
+ });
182
+
183
+ function HomePage() {
184
+ const data = Route.useLoaderData() as Record<string, any> | null;
185
+
186
+ if (!data) {
187
+ return (
188
+ <div className="min-h-screen flex items-center justify-center">
189
+ <div className="text-center">
190
+ <h1 className="text-4xl font-bold mb-4">${siteTitle}</h1>
191
+ <p className="text-sm text-base-content/40 mt-2">Nenhuma pagina CMS encontrada para /</p>
192
+ </div>
193
+ </div>
194
+ );
195
+ }
196
+
197
+ return (
198
+ <DecoPageRenderer
199
+ sections={data.resolvedSections ?? []}
200
+ deferredSections={data.deferredSections ?? []}
201
+ deferredPromises={data.deferredPromises}
202
+ pagePath={data.pagePath}
203
+ pageUrl={data.pageUrl}
204
+ loadDeferredSectionFn={deferredSectionLoader}
205
+ />
206
+ );
207
+ }
208
+ `;
209
+ }
210
+
211
+ function generateCatchAll(siteTitle: string): string {
212
+ return `import { createFileRoute } from "@tanstack/react-router";
213
+ import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
214
+ import { DecoPageRenderer } from "@decocms/start/hooks";
215
+
216
+ const routeConfig = cmsRouteConfig({
217
+ siteName: "${siteTitle}",
218
+ defaultTitle: "${siteTitle}",
219
+ });
220
+
221
+ export const Route = createFileRoute("/$")({
222
+ ...routeConfig,
223
+ component: CmsPage,
224
+ notFoundComponent: NotFoundPage,
225
+ staleTime: 30_000,
226
+ });
227
+
228
+ function CmsPage() {
229
+ const data = Route.useLoaderData() as Record<string, any> | null;
230
+ if (!data) return <NotFoundPage />;
231
+
232
+ return (
233
+ <DecoPageRenderer
234
+ sections={data.resolvedSections ?? []}
235
+ deferredSections={data.deferredSections ?? []}
236
+ deferredPromises={data.deferredPromises}
237
+ pagePath={data.pagePath}
238
+ pageUrl={data.pageUrl}
239
+ loadDeferredSectionFn={deferredSectionLoader}
240
+ />
241
+ );
242
+ }
243
+
244
+ function NotFoundPage() {
245
+ return (
246
+ <div className="min-h-screen flex items-center justify-center">
247
+ <div className="text-center">
248
+ <h1 className="text-6xl font-bold text-base-content/20 mb-4">404</h1>
249
+ <h2 className="text-2xl font-bold mb-2">Pagina nao encontrada</h2>
250
+ <a href="/" className="btn btn-primary">Voltar para Home</a>
251
+ </div>
252
+ </div>
253
+ );
254
+ }
255
+ `;
256
+ }
257
+
258
+ function generateDecoMeta(): string {
259
+ return `import { createFileRoute } from "@tanstack/react-router";
260
+ import { decoMetaRoute } from "@decocms/start/routes";
261
+
262
+ export const Route = createFileRoute("/deco/meta")(decoMetaRoute);
263
+ `;
264
+ }
265
+
266
+ function generateDecoInvoke(): string {
267
+ return `import { createFileRoute } from "@tanstack/react-router";
268
+ import { decoInvokeRoute } from "@decocms/start/routes";
269
+
270
+ export const Route = createFileRoute("/deco/invoke/$")(decoInvokeRoute);
271
+ `;
272
+ }
273
+
274
+ function generateDecoRender(): string {
275
+ return `import { createFileRoute } from "@tanstack/react-router";
276
+ import { decoRenderRoute } from "@decocms/start/routes";
277
+
278
+ export const Route = createFileRoute("/deco/render")(decoRenderRoute);
279
+ `;
280
+ }
@@ -0,0 +1,148 @@
1
+ import type { MigrationContext } from "../types.ts";
2
+
3
+ export function generateServerEntry(
4
+ ctx: MigrationContext,
5
+ ): Record<string, string> {
6
+ return {
7
+ "src/server.ts": generateServer(),
8
+ "src/worker-entry.ts": generateWorkerEntry(ctx),
9
+ "src/router.tsx": generateRouter(),
10
+ "src/runtime.ts": generateRuntime(),
11
+ "src/context.ts": generateContext(ctx),
12
+ };
13
+ }
14
+
15
+ function generateServer(): string {
16
+ return `import {
17
+ createStartHandler,
18
+ defaultStreamHandler,
19
+ } from "@tanstack/react-start/server";
20
+
21
+ export default createStartHandler(defaultStreamHandler);
22
+ `;
23
+ }
24
+
25
+ function generateWorkerEntry(ctx: MigrationContext): string {
26
+ return `/**
27
+ * Cloudflare Worker entry point.
28
+ *
29
+ * For a simple site without VTEX proxy or A/B testing, this is a thin wrapper
30
+ * around the TanStack Start handler. Add proxy logic, security headers, or
31
+ * A/B testing as needed.
32
+ */
33
+ import { createDecoWorkerEntry } from "@decocms/start/worker";
34
+
35
+ const handler = createDecoWorkerEntry({
36
+ siteName: "${ctx.siteName}",
37
+ });
38
+
39
+ export default handler;
40
+ `;
41
+ }
42
+
43
+ function generateRouter(): string {
44
+ return `import { createRouter as createTanStackRouter } from "@tanstack/react-router";
45
+ import type { SearchSerializer, SearchParser } from "@tanstack/react-router";
46
+ import { routeTree } from "./routeTree.gen";
47
+ import "./setup";
48
+
49
+ const parseSearch: SearchParser = (searchStr) => {
50
+ const str = searchStr.startsWith("?") ? searchStr.slice(1) : searchStr;
51
+ if (!str) return {};
52
+ const params = new URLSearchParams(str);
53
+ const result: Record<string, string | string[]> = {};
54
+ for (const key of new Set(params.keys())) {
55
+ const values = params.getAll(key);
56
+ result[key] = values.length === 1 ? values[0] : values;
57
+ }
58
+ return result;
59
+ };
60
+
61
+ const stringifySearch: SearchSerializer = (search) => {
62
+ const params = new URLSearchParams();
63
+ for (const [key, value] of Object.entries(search)) {
64
+ if (value === undefined || value === null || value === "") continue;
65
+ if (Array.isArray(value)) {
66
+ for (const v of value) params.append(key, String(v));
67
+ } else {
68
+ params.append(key, String(value));
69
+ }
70
+ }
71
+ const str = params.toString();
72
+ return str ? \`?\${str}\` : "";
73
+ };
74
+
75
+ export function getRouter() {
76
+ const router = createTanStackRouter({
77
+ routeTree,
78
+ scrollRestoration: true,
79
+ defaultPreload: "intent",
80
+ parseSearch,
81
+ stringifySearch,
82
+ });
83
+ return router;
84
+ }
85
+
86
+ declare module "@tanstack/react-router" {
87
+ interface Register {
88
+ router: ReturnType<typeof getRouter>;
89
+ }
90
+ }
91
+ `;
92
+ }
93
+
94
+ function generateRuntime(): string {
95
+ return `/**
96
+ * Runtime invoke proxy — turns nested property access into typed RPC calls.
97
+ *
98
+ * invoke.vtex.loaders.productList(props)
99
+ * → POST /deco/invoke/vtex/loaders/productList
100
+ */
101
+ function createNestedInvokeProxy(path: string[] = []): any {
102
+ return new Proxy(
103
+ Object.assign(async (props: any) => {
104
+ const key = path.join("/");
105
+ for (const k of [key, \`\${key}.ts\`]) {
106
+ const response = await fetch(\`/deco/invoke/\${k}\`, {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/json" },
109
+ body: JSON.stringify(props ?? {}),
110
+ });
111
+ if (response.status === 404) continue;
112
+ if (!response.ok) {
113
+ throw new Error(\`invoke(\${k}) failed: \${response.status}\`);
114
+ }
115
+ return response.json();
116
+ }
117
+ throw new Error(\`invoke(\${key}) failed: handler not found\`);
118
+ }, {}),
119
+ {
120
+ get(_target: any, prop: string) {
121
+ if (prop === "then" || prop === "catch" || prop === "finally") {
122
+ return undefined;
123
+ }
124
+ return createNestedInvokeProxy([...path, prop]);
125
+ },
126
+ },
127
+ );
128
+ }
129
+
130
+ export const invoke = createNestedInvokeProxy() as any;
131
+ export const Runtime = { invoke };
132
+ `;
133
+ }
134
+
135
+ function generateContext(ctx: MigrationContext): string {
136
+ return `import { createContext } from "react";
137
+
138
+ export interface AccountContextValue {
139
+ name: string;
140
+ }
141
+
142
+ const Account = createContext<AccountContextValue>({
143
+ name: "${ctx.siteName}",
144
+ });
145
+
146
+ export default Account;
147
+ `;
148
+ }
@@ -0,0 +1,32 @@
1
+ import type { MigrationContext } from "../types.ts";
2
+
3
+ export function generateSetup(_ctx: MigrationContext): string {
4
+ return `/**
5
+ * Site setup — registers all sections, loaders and matchers with the CMS.
6
+ *
7
+ * This file is imported by router.tsx at startup.
8
+ * It uses import.meta.glob to lazily discover all section components.
9
+ */
10
+ import { registerSections } from "@decocms/start/cms";
11
+ import { registerMatcher } from "@decocms/start/matchers";
12
+
13
+ // -- Section Registry --
14
+ // Discovers all .tsx files under src/sections/ and registers them as CMS blocks.
15
+ const sectionModules = import.meta.glob("./sections/**/*.tsx");
16
+ registerSections(sectionModules);
17
+
18
+ // -- Matchers --
19
+ // Register any custom matchers here.
20
+ // Example: registerMatcher("device", deviceMatcher);
21
+
22
+ // -- Loader Cache --
23
+ // Register cached loaders here if needed.
24
+ // Example:
25
+ // import { createCachedLoader } from "@decocms/start/loaders";
26
+ // registerLoader("productList", createCachedLoader(vtexProductList, { ttl: 60_000 }));
27
+
28
+ // -- CMS Blocks --
29
+ // Load generated blocks at module level so they're available for resolution.
30
+ import "./server/cms/blocks.gen";
31
+ `;
32
+ }
@@ -0,0 +1,21 @@
1
+ export function generateTsconfig(): string {
2
+ const config = {
3
+ compilerOptions: {
4
+ jsx: "react-jsx",
5
+ moduleResolution: "bundler",
6
+ module: "ESNext",
7
+ target: "ES2022",
8
+ skipLibCheck: true,
9
+ strictNullChecks: true,
10
+ forceConsistentCasingInFileNames: true,
11
+ types: ["vite/client"],
12
+ baseUrl: ".",
13
+ paths: {
14
+ "~/*": ["./src/*"],
15
+ },
16
+ },
17
+ include: ["src/**/*", "vite.config.ts"],
18
+ };
19
+
20
+ return JSON.stringify(config, null, 2) + "\n";
21
+ }
@@ -0,0 +1,108 @@
1
+ import type { MigrationContext } from "../types.ts";
2
+
3
+ export function generateViteConfig(ctx: MigrationContext): string {
4
+ return `import { cloudflare } from "@cloudflare/vite-plugin";
5
+ import { tanstackStart } from "@tanstack/react-start/plugin/vite";
6
+ import { decoVitePlugin } from "@decocms/start/vite";
7
+ import react from "@vitejs/plugin-react";
8
+ import tailwindcss from "@tailwindcss/vite";
9
+ import { defineConfig } from "vite";
10
+ import path from "path";
11
+
12
+ const srcDir = path.resolve(__dirname, "src");
13
+
14
+ export default defineConfig({
15
+ server: {
16
+ allowedHosts: [".decocdn.com"],
17
+ },
18
+ plugins: [
19
+ cloudflare({ viteEnvironment: { name: "ssr" } }),
20
+ tanstackStart({ server: { entry: "server" } }),
21
+ react({
22
+ babel: {
23
+ plugins: [
24
+ ["babel-plugin-react-compiler", { target: "19" }],
25
+ ],
26
+ },
27
+ }),
28
+ tailwindcss(),
29
+ decoVitePlugin(),
30
+ {
31
+ name: "site-manual-chunks",
32
+ config(_cfg, { command }) {
33
+ if (command !== "build") return;
34
+ return {
35
+ build: {
36
+ rollupOptions: {
37
+ output: {
38
+ manualChunks(id: string) {
39
+ if (id.includes("node_modules/react-dom") || id.includes("node_modules/react/"))
40
+ return "vendor-react";
41
+ if (id.includes("@tanstack/react-router") || id.includes("@tanstack/start"))
42
+ return "vendor-router";
43
+ if (id.includes("@tanstack/react-query")) return "vendor-query";
44
+ },
45
+ },
46
+ },
47
+ },
48
+ };
49
+ },
50
+ },
51
+ {
52
+ name: "deco-stub-meta-gen",
53
+ enforce: "pre" as const,
54
+ resolveId(id, importer, options) {
55
+ if (!options?.ssr && importer && id.includes("meta.gen")) {
56
+ return "\\0stub:meta-gen";
57
+ }
58
+ },
59
+ load(id) {
60
+ if (id === "\\0stub:meta-gen") {
61
+ return "export default {};";
62
+ }
63
+ },
64
+ },
65
+ ],
66
+ build: {
67
+ sourcemap: "hidden",
68
+ rollupOptions: {
69
+ onLog(level, log, handler) {
70
+ if (
71
+ log.code === "PLUGIN_WARNING" &&
72
+ log.plugin === "vite:reporter" &&
73
+ log.message?.includes("dynamic import will not move module")
74
+ ) {
75
+ return;
76
+ }
77
+ handler(level, log);
78
+ },
79
+ },
80
+ },
81
+ define: {
82
+ "process.env.DECO_SITE_NAME": JSON.stringify(
83
+ process.env.DECO_SITE_NAME || "${ctx.siteName}"
84
+ ),
85
+ },
86
+ esbuild: {
87
+ jsx: "automatic",
88
+ jsxImportSource: "react",
89
+ },
90
+ resolve: {
91
+ dedupe: [
92
+ "@tanstack/react-start",
93
+ "@tanstack/react-router",
94
+ "@tanstack/react-start-server",
95
+ "@tanstack/start-server-core",
96
+ "@tanstack/start-client-core",
97
+ "@tanstack/start-plugin-core",
98
+ "@tanstack/start-storage-context",
99
+ "react",
100
+ "react-dom",
101
+ ],
102
+ alias: {
103
+ "~": srcDir,
104
+ },
105
+ },
106
+ });
107
+ `;
108
+ }
@@ -0,0 +1,25 @@
1
+ import type { MigrationContext } from "../types.ts";
2
+
3
+ export function generateWrangler(ctx: MigrationContext): string {
4
+ // Sanitize site name for worker name (lowercase, hyphens only)
5
+ const workerName = ctx.siteName
6
+ .toLowerCase()
7
+ .replace(/[^a-z0-9-]/g, "-")
8
+ .replace(/-+/g, "-");
9
+
10
+ return `{
11
+ "name": "${workerName}-tanstack",
12
+ "compatibility_date": "2026-02-14",
13
+ "compatibility_flags": ["nodejs_compat", "no_handle_cross_request_promise_resolution"],
14
+ "main": "./src/worker-entry.ts",
15
+ "workers_dev": true,
16
+ "preview_urls": true,
17
+ "observability": {
18
+ "logs": {
19
+ "enabled": true,
20
+ "invocation_logs": true
21
+ }
22
+ }
23
+ }
24
+ `;
25
+ }