@decocms/blocks-cli 7.31.6 → 7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks-cli",
3
- "version": "7.31.6",
3
+ "version": "7.32.0",
4
4
  "type": "module",
5
5
  "description": "Deco codegen (generate-blocks, generate-schema, generate-invoke) and Fresh-to-TanStack migration tooling",
6
6
  "repository": {
@@ -31,7 +31,7 @@
31
31
  "lint:unused": "knip"
32
32
  },
33
33
  "dependencies": {
34
- "@decocms/blocks": "7.31.6",
34
+ "@decocms/blocks": "7.32.0",
35
35
  "ts-morph": "^27.0.0",
36
36
  "tsx": "^4.22.5"
37
37
  },
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
- import { extractPlatform } from "./phase-analyze";
5
+ import { extractGoogleFonts, extractPlatform } from "./phase-analyze";
6
6
 
7
7
  // `extractPlatform` runs four strategies in order:
8
8
  // 1. deno.json imports referencing apps/{platform}/
@@ -16,6 +16,68 @@ import { extractPlatform } from "./phase-analyze";
16
16
  // list so Strategy 2 fires first and short-circuits before Strategy 3 can
17
17
  // over-match.
18
18
 
19
+ describe("extractGoogleFonts", () => {
20
+ let tmp: string;
21
+
22
+ beforeEach(() => {
23
+ tmp = fs.mkdtempSync(path.join(os.tmpdir(), "extract-gfonts-"));
24
+ fs.mkdirSync(path.join(tmp, "routes"), { recursive: true });
25
+ });
26
+
27
+ afterEach(() => {
28
+ fs.rmSync(tmp, { recursive: true, force: true });
29
+ });
30
+
31
+ it("extracts preconnects and stylesheet from _app.tsx", () => {
32
+ fs.writeFileSync(
33
+ path.join(tmp, "routes", "_app.tsx"),
34
+ `<link rel="preconnect" href="https://fonts.googleapis.com" />
35
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
36
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" />`,
37
+ );
38
+ const result = extractGoogleFonts(tmp);
39
+ expect(result.preconnects).toEqual([
40
+ "https://fonts.googleapis.com",
41
+ "https://fonts.gstatic.com",
42
+ ]);
43
+ expect(result.stylesheets).toEqual([
44
+ "https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap",
45
+ ]);
46
+ });
47
+
48
+ it("deduplicates URLs present in both _app.tsx and _layout.tsx", () => {
49
+ const sharedUrl = "https://fonts.googleapis.com/css2?family=Roboto&display=swap";
50
+ fs.writeFileSync(
51
+ path.join(tmp, "routes", "_app.tsx"),
52
+ `<link rel="stylesheet" href="${sharedUrl}" />`,
53
+ );
54
+ fs.writeFileSync(
55
+ path.join(tmp, "routes", "_layout.tsx"),
56
+ `<link rel="stylesheet" href="${sharedUrl}" />`,
57
+ );
58
+ const result = extractGoogleFonts(tmp);
59
+ expect(result.stylesheets).toHaveLength(1);
60
+ });
61
+
62
+ it("extracts Google Fonts @import from tailwind.css", () => {
63
+ fs.writeFileSync(
64
+ path.join(tmp, "tailwind.css"),
65
+ `@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');`,
66
+ );
67
+ const result = extractGoogleFonts(tmp);
68
+ expect(result.stylesheets).toEqual([
69
+ "https://fonts.googleapis.com/css2?family=Poppins&display=swap",
70
+ ]);
71
+ });
72
+
73
+ it("returns empty arrays when no Google Fonts are declared", () => {
74
+ fs.writeFileSync(path.join(tmp, "routes", "_app.tsx"), `<link rel="icon" href="/favicon.ico" />`);
75
+ const result = extractGoogleFonts(tmp);
76
+ expect(result.preconnects).toHaveLength(0);
77
+ expect(result.stylesheets).toHaveLength(0);
78
+ });
79
+ });
80
+
19
81
  describe("extractPlatform", () => {
20
82
  let tmp: string;
21
83
 
@@ -418,6 +418,50 @@ function scanDir(
418
418
  }
419
419
  }
420
420
 
421
+ export function extractGoogleFonts(
422
+ sourceDir: string,
423
+ ): { preconnects: string[]; stylesheets: string[] } {
424
+ const preconnects: string[] = [];
425
+ const stylesheets: string[] = [];
426
+ const seen = new Set<string>();
427
+
428
+ const hrefRe = /href=["'](https:\/\/fonts\.(?:googleapis|gstatic)\.com[^"']*)["']/g;
429
+
430
+ // Read _app.tsx and _layout.tsx for <link> declarations
431
+ for (const candidate of ["routes/_app.tsx", "routes/_layout.tsx"]) {
432
+ const fp = path.join(sourceDir, candidate);
433
+ if (!fs.existsSync(fp)) continue;
434
+ const content = fs.readFileSync(fp, "utf-8");
435
+ for (const m of content.matchAll(hrefRe)) {
436
+ const href = m[1];
437
+ if (seen.has(href)) continue;
438
+ seen.add(href);
439
+ if (href.includes("/css")) {
440
+ stylesheets.push(href);
441
+ } else {
442
+ preconnects.push(href);
443
+ }
444
+ }
445
+ }
446
+
447
+ // Also check tailwind.css for @import url(https://fonts.googleapis.com/...)
448
+ for (const cssCandidate of ["tailwind.css", "styles/app.css", "styles/global.css"]) {
449
+ const fp = path.join(sourceDir, cssCandidate);
450
+ if (!fs.existsSync(fp)) continue;
451
+ const content = fs.readFileSync(fp, "utf-8");
452
+ const importRe = /@import\s+url\(['"]?(https:\/\/fonts\.googleapis\.com[^'")\s]+)/g;
453
+ for (const m of content.matchAll(importRe)) {
454
+ const href = m[1];
455
+ if (!seen.has(href)) {
456
+ seen.add(href);
457
+ stylesheets.push(href);
458
+ }
459
+ }
460
+ }
461
+
462
+ return { preconnects, stylesheets };
463
+ }
464
+
421
465
  function extractGtmId(sourceDir: string): string | null {
422
466
  const appPath = path.join(sourceDir, "routes", "_app.tsx");
423
467
  if (!fs.existsSync(appPath)) return null;
@@ -627,6 +671,20 @@ export function analyze(ctx: MigrationContext): void {
627
671
  ctx.platform = extractPlatform(ctx.sourceDir);
628
672
  ctx.vtexAccount = ctx.platform === "vtex" ? extractVtexAccount(ctx.sourceDir) : null;
629
673
  ctx.gtmId = extractGtmId(ctx.sourceDir);
674
+ ctx.googleFonts = extractGoogleFonts(ctx.sourceDir);
675
+ // If no font URLs were extracted but the file mentions googleapis (likely a
676
+ // JSX expression like href={URL_CONST}), flag it for manual review.
677
+ if (ctx.googleFonts.preconnects.length === 0 && ctx.googleFonts.stylesheets.length === 0) {
678
+ const appPath = path.join(ctx.sourceDir, "routes", "_app.tsx");
679
+ if (fs.existsSync(appPath) && fs.readFileSync(appPath, "utf-8").includes("fonts.googleapis")) {
680
+ ctx.manualReviewItems.push({
681
+ file: "routes/_app.tsx",
682
+ reason:
683
+ "Google Fonts reference found but could not be extracted automatically (may use a JSX expression or variable). Add the font preconnect and stylesheet links manually to src/routes/__root.tsx.",
684
+ severity: "warning",
685
+ });
686
+ }
687
+ }
630
688
 
631
689
  // Extract theme colors and font from CMS
632
690
  const theme = extractThemeFromCms(ctx.sourceDir);
@@ -51,17 +51,26 @@ function generateRoot(ctx: MigrationContext, siteTitle: string, vtexAccount: str
51
51
  preconnects.push(` { rel: "preconnect", href: "https://${vtexAccount}.vtexassets.com", crossOrigin: "anonymous" as const },`);
52
52
  }
53
53
 
54
- // Font preloads
54
+ const googleFontLinks = [
55
+ ...ctx.googleFonts.preconnects.map((href) =>
56
+ ` { rel: "preconnect", href: "${href}"${href.includes("gstatic") ? ', crossOrigin: "anonymous" as const' : ""} },`
57
+ ),
58
+ ...ctx.googleFonts.stylesheets.map((href) =>
59
+ ` { rel: "stylesheet", href: "${href}" },`
60
+ ),
61
+ ];
62
+
55
63
  const fontPreloads = fonts.map((f) =>
56
64
  ` { rel: "preload", href: "${f}", as: "font", type: "font/woff2", crossOrigin: "anonymous" as const },`
57
65
  );
58
66
 
59
- // DNS prefetch for common third-party services
60
67
  const dnsPrefetch: string[] = [];
61
68
  if (isVtex) {
62
69
  dnsPrefetch.push(` { rel: "dns-prefetch", href: "https://sp.vtex.com" },`);
63
70
  }
64
71
 
72
+ const linkLines = [...preconnects, ...googleFontLinks, ...fontPreloads].filter(Boolean);
73
+
65
74
  return `import { createRootRoute } from "@tanstack/react-router";
66
75
  import { DecoRootLayout } from "@decocms/tanstack";
67
76
  // @ts-ignore Vite ?url import
@@ -83,8 +92,7 @@ export const Route = createRootRoute({
83
92
  { property: "og:locale", content: "pt_BR" },
84
93
  ],
85
94
  links: [
86
- ${preconnects.join("\n")}
87
- ${fontPreloads.join("\n")}
95
+ ${linkLines.join("\n")}
88
96
  { rel: "stylesheet", href: appCss },
89
97
  { rel: "icon", href: "/favicon.ico" },
90
98
  ${dnsPrefetch.join("\n")}
@@ -156,6 +156,8 @@ export interface MigrationContext {
156
156
  themeColors: Record<string, string>;
157
157
  /** Font family from CMS config */
158
158
  fontFamily: string | null;
159
+ /** Google Fonts URLs extracted from routes/_app.tsx or CSS @import */
160
+ googleFonts: { preconnects: string[]; stylesheets: string[] };
159
161
 
160
162
  /**
161
163
  * Statically-extracted content of the source site's `tailwind.config.ts`
@@ -226,6 +228,7 @@ export function createContext(
226
228
  discoveredNpmDeps: {},
227
229
  themeColors: {},
228
230
  fontFamily: null,
231
+ googleFonts: { preconnects: [], stylesheets: [] },
229
232
  tailwindConfig: {
230
233
  colors: {},
231
234
  fontFamily: {},