@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.
Files changed (93) hide show
  1. package/package.json +44 -0
  2. package/scripts/analyze-traces.mjs +1117 -0
  3. package/scripts/audit-observability-config.test.ts +446 -0
  4. package/scripts/audit-observability-config.ts +511 -0
  5. package/scripts/deco-migrate-cli.ts +444 -0
  6. package/scripts/fast-deploy-kv.test.ts +131 -0
  7. package/scripts/generate-blocks.test.ts +94 -0
  8. package/scripts/generate-blocks.ts +274 -0
  9. package/scripts/generate-invoke.test.ts +195 -0
  10. package/scripts/generate-invoke.ts +469 -0
  11. package/scripts/generate-loaders.ts +217 -0
  12. package/scripts/generate-schema.ts +1287 -0
  13. package/scripts/generate-sections.ts +237 -0
  14. package/scripts/htmx-analyze.ts +226 -0
  15. package/scripts/lib/blocks-dedupe.test.ts +179 -0
  16. package/scripts/lib/blocks-dedupe.ts +142 -0
  17. package/scripts/lib/cf-kv-rest.ts +78 -0
  18. package/scripts/lib/jsonc.ts +122 -0
  19. package/scripts/lib/kv-snapshot.ts +51 -0
  20. package/scripts/lib/read-decofile.ts +70 -0
  21. package/scripts/lib/sync-helpers.ts +44 -0
  22. package/scripts/migrate/analyzers/htmx-analyze.test.ts +372 -0
  23. package/scripts/migrate/analyzers/htmx-analyze.ts +425 -0
  24. package/scripts/migrate/analyzers/island-classifier.ts +96 -0
  25. package/scripts/migrate/analyzers/loader-inventory.ts +63 -0
  26. package/scripts/migrate/analyzers/section-metadata.ts +147 -0
  27. package/scripts/migrate/analyzers/theme-extractor.ts +122 -0
  28. package/scripts/migrate/colors.ts +46 -0
  29. package/scripts/migrate/config.test.ts +202 -0
  30. package/scripts/migrate/config.ts +186 -0
  31. package/scripts/migrate/phase-analyze.test.ts +63 -0
  32. package/scripts/migrate/phase-analyze.ts +782 -0
  33. package/scripts/migrate/phase-cleanup-audit.test.ts +137 -0
  34. package/scripts/migrate/phase-cleanup-audit.ts +105 -0
  35. package/scripts/migrate/phase-cleanup.test.ts +141 -0
  36. package/scripts/migrate/phase-cleanup.ts +1588 -0
  37. package/scripts/migrate/phase-compile.test.ts +193 -0
  38. package/scripts/migrate/phase-compile.ts +177 -0
  39. package/scripts/migrate/phase-report.ts +243 -0
  40. package/scripts/migrate/phase-scaffold.ts +593 -0
  41. package/scripts/migrate/phase-transform.ts +310 -0
  42. package/scripts/migrate/phase-verify.test.ts +127 -0
  43. package/scripts/migrate/phase-verify.ts +572 -0
  44. package/scripts/migrate/post-cleanup/rules.ts +1708 -0
  45. package/scripts/migrate/post-cleanup/runner.test.ts +1771 -0
  46. package/scripts/migrate/post-cleanup/runner.ts +137 -0
  47. package/scripts/migrate/post-cleanup/shim-classify.test.ts +352 -0
  48. package/scripts/migrate/post-cleanup/shim-classify.ts +246 -0
  49. package/scripts/migrate/post-cleanup/types.ts +106 -0
  50. package/scripts/migrate/source-layout.test.ts +111 -0
  51. package/scripts/migrate/source-layout.ts +103 -0
  52. package/scripts/migrate/templates/app-css.ts +366 -0
  53. package/scripts/migrate/templates/cache-config.ts +26 -0
  54. package/scripts/migrate/templates/commerce-loaders.ts +230 -0
  55. package/scripts/migrate/templates/cursor-rules.test.ts +59 -0
  56. package/scripts/migrate/templates/cursor-rules.ts +70 -0
  57. package/scripts/migrate/templates/hooks.test.ts +141 -0
  58. package/scripts/migrate/templates/hooks.ts +152 -0
  59. package/scripts/migrate/templates/knip-config.ts +27 -0
  60. package/scripts/migrate/templates/lib-utils.test.ts +139 -0
  61. package/scripts/migrate/templates/lib-utils.ts +326 -0
  62. package/scripts/migrate/templates/lockfile-check-yml.test.ts +26 -0
  63. package/scripts/migrate/templates/lockfile-check-yml.ts +66 -0
  64. package/scripts/migrate/templates/package-json.ts +177 -0
  65. package/scripts/migrate/templates/routes.ts +237 -0
  66. package/scripts/migrate/templates/sdk-gen.ts +59 -0
  67. package/scripts/migrate/templates/section-loaders.ts +456 -0
  68. package/scripts/migrate/templates/server-entry.ts +561 -0
  69. package/scripts/migrate/templates/setup.ts +148 -0
  70. package/scripts/migrate/templates/tsconfig.ts +21 -0
  71. package/scripts/migrate/templates/types-gen.ts +174 -0
  72. package/scripts/migrate/templates/ui-components.ts +144 -0
  73. package/scripts/migrate/templates/vite-config.ts +101 -0
  74. package/scripts/migrate/transforms/dead-code.ts +455 -0
  75. package/scripts/migrate/transforms/deno-isms.ts +85 -0
  76. package/scripts/migrate/transforms/fresh-apis.ts +223 -0
  77. package/scripts/migrate/transforms/htmx-on-events.test.ts +305 -0
  78. package/scripts/migrate/transforms/htmx-on-events.ts +193 -0
  79. package/scripts/migrate/transforms/imports.ts +385 -0
  80. package/scripts/migrate/transforms/jsx.ts +317 -0
  81. package/scripts/migrate/transforms/section-conventions.ts +210 -0
  82. package/scripts/migrate/transforms/tailwind.ts +739 -0
  83. package/scripts/migrate/types.ts +244 -0
  84. package/scripts/migrate-blocks-to-kv.ts +104 -0
  85. package/scripts/migrate-post-cleanup.ts +191 -0
  86. package/scripts/migrate-to-cf-observability.test.ts +215 -0
  87. package/scripts/migrate-to-cf-observability.ts +699 -0
  88. package/scripts/migrate.ts +282 -0
  89. package/scripts/smoke-otlp-errorlog.ts +46 -0
  90. package/scripts/smoke-otlp-meter.ts +48 -0
  91. package/scripts/sync-blocks-to-kv.ts +153 -0
  92. package/scripts/tailwind-lint.ts +518 -0
  93. 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
+ }