@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,237 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { MigrationContext } from "../types";
|
|
4
|
+
|
|
5
|
+
function discoverFonts(ctx: MigrationContext): string[] {
|
|
6
|
+
const fontsDir = path.join(ctx.sourceDir, "public", "fonts");
|
|
7
|
+
if (!fs.existsSync(fontsDir)) {
|
|
8
|
+
const staticFonts = path.join(ctx.sourceDir, "static", "fonts");
|
|
9
|
+
if (!fs.existsSync(staticFonts)) return [];
|
|
10
|
+
return scanFontDir(staticFonts);
|
|
11
|
+
}
|
|
12
|
+
return scanFontDir(fontsDir);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function scanFontDir(dir: string): string[] {
|
|
16
|
+
try {
|
|
17
|
+
return fs.readdirSync(dir)
|
|
18
|
+
.filter((f) => /\.(woff2)$/i.test(f))
|
|
19
|
+
.map((f) => `/fonts/${f}`);
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function generateRoutes(
|
|
26
|
+
ctx: MigrationContext,
|
|
27
|
+
): Record<string, string> {
|
|
28
|
+
const siteName = ctx.siteName;
|
|
29
|
+
const siteTitle = siteName.charAt(0).toUpperCase() + siteName.slice(1);
|
|
30
|
+
const vtexAccount = ctx.vtexAccount || siteName;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
"src/routes/__root.tsx": generateRoot(ctx, siteTitle, vtexAccount),
|
|
34
|
+
"src/routes/index.tsx": generateIndex(ctx, siteTitle),
|
|
35
|
+
"src/routes/$.tsx": generateCatchAll(ctx, siteTitle),
|
|
36
|
+
"src/routes/deco/meta.ts": generateDecoMeta(),
|
|
37
|
+
"src/routes/deco/invoke.$.ts": generateDecoInvoke(),
|
|
38
|
+
"src/routes/deco/render.ts": generateDecoRender(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function generateRoot(ctx: MigrationContext, siteTitle: string, vtexAccount: string): string {
|
|
43
|
+
const fonts = discoverFonts(ctx);
|
|
44
|
+
const isVtex = ctx.platform === "vtex";
|
|
45
|
+
const deployedSiteName = `${ctx.siteName}-tanstack`;
|
|
46
|
+
|
|
47
|
+
// Build preconnect list based on platform
|
|
48
|
+
const preconnects: string[] = [];
|
|
49
|
+
preconnects.push(` { rel: "preconnect", href: "https://decoims.com", crossOrigin: "anonymous" as const },`);
|
|
50
|
+
if (isVtex) {
|
|
51
|
+
preconnects.push(` { rel: "preconnect", href: "https://${vtexAccount}.vtexassets.com", crossOrigin: "anonymous" as const },`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Font preloads
|
|
55
|
+
const fontPreloads = fonts.map((f) =>
|
|
56
|
+
` { rel: "preload", href: "${f}", as: "font", type: "font/woff2", crossOrigin: "anonymous" as const },`
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// DNS prefetch for common third-party services
|
|
60
|
+
const dnsPrefetch: string[] = [];
|
|
61
|
+
if (isVtex) {
|
|
62
|
+
dnsPrefetch.push(` { rel: "dns-prefetch", href: "https://sp.vtex.com" },`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return `import { createRootRoute } from "@tanstack/react-router";
|
|
66
|
+
import { DecoRootLayout } from "@decocms/start/hooks";
|
|
67
|
+
// @ts-ignore Vite ?url import
|
|
68
|
+
import appCss from "../styles/app.css?url";
|
|
69
|
+
|
|
70
|
+
// MIGRATION TODO: customize description, OG image, and locale for ${siteTitle}.
|
|
71
|
+
// The migration scaffold leaves a generic default so it never falls through;
|
|
72
|
+
// CMS \`Site.seo\` overrides this once block resolution kicks in.
|
|
73
|
+
const DEFAULT_DESCRIPTION = "${siteTitle}";
|
|
74
|
+
|
|
75
|
+
export const Route = createRootRoute({
|
|
76
|
+
head: () => ({
|
|
77
|
+
meta: [
|
|
78
|
+
{ charSet: "utf-8" },
|
|
79
|
+
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
|
80
|
+
{ title: "${siteTitle}" },
|
|
81
|
+
{ name: "description", content: DEFAULT_DESCRIPTION },
|
|
82
|
+
{ property: "og:site_name", content: "${siteTitle}" },
|
|
83
|
+
{ property: "og:locale", content: "pt_BR" },
|
|
84
|
+
],
|
|
85
|
+
links: [
|
|
86
|
+
${preconnects.join("\n")}
|
|
87
|
+
${fontPreloads.join("\n")}
|
|
88
|
+
{ rel: "stylesheet", href: appCss },
|
|
89
|
+
{ rel: "icon", href: "/favicon.ico" },
|
|
90
|
+
${dnsPrefetch.join("\n")}
|
|
91
|
+
],
|
|
92
|
+
}),
|
|
93
|
+
component: RootLayout,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
function RootLayout() {
|
|
97
|
+
return (
|
|
98
|
+
<DecoRootLayout
|
|
99
|
+
lang="pt-BR"
|
|
100
|
+
siteName="${deployedSiteName}"${isVtex ? `
|
|
101
|
+
account="${vtexAccount}"` : ""}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function generateIndex(ctx: MigrationContext, siteTitle: string): string {
|
|
109
|
+
return `import { createFileRoute } from "@tanstack/react-router";
|
|
110
|
+
import { cmsHomeRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
111
|
+
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
112
|
+
|
|
113
|
+
// MIGRATION TODO: customize defaultTitle / defaultDescription / fallback
|
|
114
|
+
// copy below for ${siteTitle}. CMS \`Site.seo\` overrides these once block
|
|
115
|
+
// resolution kicks in, so leaving the migration scaffold defaults is safe
|
|
116
|
+
// but visible in pre-block-resolution states.
|
|
117
|
+
export const Route = createFileRoute("/")({
|
|
118
|
+
...cmsHomeRouteConfig({
|
|
119
|
+
defaultTitle: "${siteTitle}",
|
|
120
|
+
defaultDescription: "${siteTitle}",
|
|
121
|
+
siteName: "${siteTitle}",
|
|
122
|
+
}),
|
|
123
|
+
component: HomePage,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
function HomePage() {
|
|
127
|
+
const data = Route.useLoaderData() as Record<string, any> | null;
|
|
128
|
+
|
|
129
|
+
if (!data) {
|
|
130
|
+
return (
|
|
131
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
132
|
+
<div className="text-center">
|
|
133
|
+
<h1 className="text-4xl font-bold mb-4">${siteTitle}</h1>
|
|
134
|
+
<p className="text-sm text-base-content/40 mt-2">No CMS page registered for /</p>
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<DecoPageRenderer
|
|
142
|
+
sections={data.resolvedSections ?? []}
|
|
143
|
+
deferredSections={data.deferredSections ?? []}
|
|
144
|
+
deferredPromises={data.deferredPromises}
|
|
145
|
+
pagePath={data.pagePath}
|
|
146
|
+
pageUrl={data.pageUrl}
|
|
147
|
+
loadDeferredSectionFn={deferredSectionLoader}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function generateCatchAll(ctx: MigrationContext, siteTitle: string): string {
|
|
155
|
+
return `import { createFileRoute } from "@tanstack/react-router";
|
|
156
|
+
import { cmsRouteConfig, deferredSectionLoader } from "@decocms/start/routes";
|
|
157
|
+
import { DecoPageRenderer } from "@decocms/start/hooks";
|
|
158
|
+
|
|
159
|
+
// MIGRATION TODO: customize defaultTitle / defaultDescription for ${siteTitle}
|
|
160
|
+
// (CMS \`Site.seo\` overrides these per-page once block resolution kicks in).
|
|
161
|
+
const routeConfig = cmsRouteConfig({
|
|
162
|
+
siteName: "${siteTitle}",
|
|
163
|
+
defaultTitle: "${siteTitle}",
|
|
164
|
+
defaultDescription: "${siteTitle}",
|
|
165
|
+
ignoreSearchParams: ["skuId"],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
export const Route = createFileRoute("/$")({
|
|
169
|
+
...routeConfig,
|
|
170
|
+
component: CmsPage,
|
|
171
|
+
notFoundComponent: NotFoundPage,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
function CmsPage() {
|
|
175
|
+
const data = Route.useLoaderData() as Record<string, any> | null;
|
|
176
|
+
if (!data) return <NotFoundPage />;
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<DecoPageRenderer
|
|
180
|
+
sections={data.resolvedSections ?? []}
|
|
181
|
+
deferredSections={data.deferredSections ?? []}
|
|
182
|
+
deferredPromises={data.deferredPromises}
|
|
183
|
+
pagePath={data.pagePath}
|
|
184
|
+
pageUrl={data.pageUrl}
|
|
185
|
+
loadDeferredSectionFn={deferredSectionLoader}
|
|
186
|
+
/>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function PendingPage() {
|
|
191
|
+
return (
|
|
192
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
193
|
+
<span className="loading loading-ring loading-xl" />
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function NotFoundPage() {
|
|
199
|
+
return (
|
|
200
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
201
|
+
<div className="text-center">
|
|
202
|
+
<h1 className="text-6xl font-bold text-base-content/20 mb-4">404</h1>
|
|
203
|
+
<h2 className="text-2xl font-bold mb-2">Page Not Found</h2>
|
|
204
|
+
<p className="text-base-content/60 mb-6">No CMS page block matches this URL.</p>
|
|
205
|
+
<a href="/" className="btn btn-primary">
|
|
206
|
+
Go Home
|
|
207
|
+
</a>
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function generateDecoMeta(): string {
|
|
216
|
+
return `import { createFileRoute } from "@tanstack/react-router";
|
|
217
|
+
import { decoMetaRoute } from "@decocms/start/routes";
|
|
218
|
+
|
|
219
|
+
export const Route = createFileRoute("/deco/meta")(decoMetaRoute);
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function generateDecoInvoke(): string {
|
|
224
|
+
return `import { createFileRoute } from "@tanstack/react-router";
|
|
225
|
+
import { decoInvokeRoute } from "@decocms/start/routes";
|
|
226
|
+
|
|
227
|
+
export const Route = createFileRoute("/deco/invoke/$")(decoInvokeRoute);
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function generateDecoRender(): string {
|
|
232
|
+
return `import { createFileRoute } from "@tanstack/react-router";
|
|
233
|
+
import { decoRenderRoute } from "@decocms/start/routes";
|
|
234
|
+
|
|
235
|
+
export const Route = createFileRoute("/deco/render")(decoRenderRoute);
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { MigrationContext } from "../types";
|
|
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
|
+
}
|