create-lumen 2.0.0-alpha.2 → 2.0.0-beta.2

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 (174) hide show
  1. package/README.md +24 -6
  2. package/bin/cli.js +2 -1
  3. package/package.json +2 -2
  4. package/schema/lumen.config.v2.json +29 -0
  5. package/src/agent-context.js +71 -47
  6. package/src/cleanup.js +19 -1
  7. package/src/cli-args.js +131 -5
  8. package/src/configure.js +8 -4
  9. package/src/dependencies.js +46 -13
  10. package/src/engine/capabilities.js +71 -1
  11. package/src/engine/composition.js +334 -17
  12. package/src/engine/providers/base-provider.js +4 -4
  13. package/src/env.js +24 -6
  14. package/src/main.js +38 -15
  15. package/src/manifest/emit.js +14 -1
  16. package/src/manifest/schema.js +30 -0
  17. package/src/prompts.js +227 -23
  18. package/templates/bases/next/app-router/js/.provenance.json +19 -0
  19. package/templates/bases/next/app-router/js/README.md +36 -0
  20. package/templates/bases/next/app-router/js/jsconfig.json +7 -0
  21. package/templates/bases/next/app-router/js/next.config.mjs +6 -0
  22. package/templates/bases/next/app-router/js/package.json +15 -0
  23. package/templates/bases/next/app-router/js/public/file.svg +1 -0
  24. package/templates/bases/next/app-router/js/public/globe.svg +1 -0
  25. package/templates/bases/next/app-router/js/public/next.svg +1 -0
  26. package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
  27. package/templates/bases/next/app-router/js/public/window.svg +1 -0
  28. package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
  29. package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
  30. package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
  31. package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
  32. package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
  33. package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
  34. package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
  35. package/templates/bases/next/app-router/js/src/app/page.js +69 -0
  36. package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
  37. package/templates/bases/next/app-router/ts/.provenance.json +19 -0
  38. package/templates/bases/next/app-router/ts/README.md +36 -0
  39. package/templates/bases/next/app-router/ts/next.config.ts +7 -0
  40. package/templates/bases/next/app-router/ts/package.json +21 -0
  41. package/templates/bases/next/app-router/ts/public/file.svg +1 -0
  42. package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
  43. package/templates/bases/next/app-router/ts/public/next.svg +1 -0
  44. package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
  45. package/templates/bases/next/app-router/ts/public/window.svg +1 -0
  46. package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
  47. package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
  48. package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
  49. package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
  50. package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
  51. package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
  52. package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
  53. package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
  54. package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
  55. package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
  56. package/templates/bases/next/pages-router/js/.provenance.json +19 -0
  57. package/templates/bases/next/pages-router/js/README.md +40 -0
  58. package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
  59. package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
  60. package/templates/bases/next/pages-router/js/package.json +15 -0
  61. package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
  62. package/templates/bases/next/pages-router/js/public/file.svg +1 -0
  63. package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
  64. package/templates/bases/next/pages-router/js/public/next.svg +1 -0
  65. package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
  66. package/templates/bases/next/pages-router/js/public/window.svg +1 -0
  67. package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
  68. package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
  69. package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
  70. package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
  71. package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
  72. package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
  73. package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
  74. package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
  75. package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
  76. package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
  77. package/templates/bases/next/pages-router/ts/README.md +40 -0
  78. package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
  79. package/templates/bases/next/pages-router/ts/package.json +21 -0
  80. package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
  81. package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
  82. package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
  83. package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
  84. package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
  85. package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
  86. package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
  87. package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
  88. package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
  89. package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
  90. package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
  91. package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
  92. package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
  93. package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
  94. package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
  95. package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
  96. package/templates/conditional/database/prisma/db.js +14 -0
  97. package/templates/conditional/database/prisma/db.ts +16 -0
  98. package/templates/conditional/database/prisma/schema.prisma +9 -0
  99. package/templates/conditional/database/prisma/seed.js +16 -0
  100. package/templates/conditional/database/prisma/seed.ts +16 -0
  101. package/templates/conditional/docker/.dockerignore +10 -0
  102. package/templates/conditional/docker/Dockerfile +57 -0
  103. package/templates/conditional/docker/docker-compose.yml +35 -0
  104. package/templates/conditional/linting/biome/biome.json +31 -0
  105. package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
  106. package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
  107. package/templates/conditional/next/config/node/next.config.mjs +6 -0
  108. package/templates/conditional/next/config/node/next.config.ts +7 -0
  109. package/templates/conditional/next/config/static/next.config.mjs +6 -0
  110. package/templates/conditional/next/config/static/next.config.ts +7 -0
  111. package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
  112. package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
  113. package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
  114. package/templates/conditional/next/config/vercel/next.config.ts +7 -0
  115. package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
  116. package/templates/conditional/next/config/webpack/next.config.ts +10 -0
  117. package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
  118. package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
  119. package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
  120. package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
  121. package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
  122. package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
  123. package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
  124. package/templates/conditional/next/font/src/app/layout.js +25 -0
  125. package/templates/conditional/next/font/src/app/layout.tsx +30 -0
  126. package/templates/conditional/next/font/src/lib/fonts.js +11 -0
  127. package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
  128. package/templates/conditional/next/font/src/pages/_app.js +20 -0
  129. package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
  130. package/templates/conditional/next/font/src/pages/_document.js +18 -0
  131. package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
  132. package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
  133. package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
  134. package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
  135. package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
  136. package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
  137. package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
  138. package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
  139. package/templates/conditional/next/image/src/components/image/index.js +1 -0
  140. package/templates/conditional/next/image/src/components/image/index.ts +2 -0
  141. package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
  142. package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
  143. package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
  144. package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
  145. package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
  146. package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
  147. package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
  148. package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
  149. package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
  150. package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
  151. package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
  152. package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
  153. package/templates/conditional/next/server-components/README.md +13 -0
  154. package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
  155. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
  156. package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
  157. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
  158. package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
  159. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
  160. package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
  161. package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
  162. package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -0
  163. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
  164. package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
  165. package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
  166. package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
  167. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
  168. package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
  169. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
  170. package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
  171. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
  172. package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
  173. package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
  174. package/templates/conditional/testing/vitest/next/vitest.config.ts +16 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * ESLint Rule: rsc/no-server-imports-in-client
3
+ *
4
+ * Enforces server/client boundaries in React Server Components (RSC).
5
+ * When a file is marked with the "use client" directive, it is prohibited
6
+ * from importing server-only packages (e.g. 'server-only', Node.js builtins,
7
+ * or modules from server-designated paths).
8
+ */
9
+
10
+ export const noServerImportsInClient = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Disallow server-only imports in client components marked with 'use client'",
15
+ category: "Possible Errors",
16
+ recommended: true,
17
+ },
18
+ schema: [
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ serverModules: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ },
26
+ },
27
+ additionalProperties: false,
28
+ },
29
+ ],
30
+ messages: {
31
+ noServerImport:
32
+ "Server-only module '{{name}}' cannot be imported into a Client Component ('use client'). Keep server logic on the server side of the RSC boundary.",
33
+ },
34
+ },
35
+ create(context) {
36
+ const options = context.options[0] || {};
37
+ const restrictedSet = new Set(
38
+ options.serverModules || [
39
+ "server-only",
40
+ "node:fs",
41
+ "node:fs/promises",
42
+ "fs",
43
+ "fs/promises",
44
+ "node:child_process",
45
+ "child_process",
46
+ "node:net",
47
+ "net",
48
+ "node:tls",
49
+ "tls",
50
+ ]
51
+ );
52
+
53
+ let isClientComponent = false;
54
+
55
+ return {
56
+ Program(node) {
57
+ // Inspect top-level statements for "use client" directive
58
+ for (const stmt of node.body) {
59
+ if (
60
+ stmt.type === "ExpressionStatement" &&
61
+ stmt.expression.type === "Literal" &&
62
+ stmt.expression.value === "use client"
63
+ ) {
64
+ isClientComponent = true;
65
+ break;
66
+ }
67
+ }
68
+ },
69
+ ImportDeclaration(node) {
70
+ if (!isClientComponent) return;
71
+ const source = node.source.value;
72
+ if (
73
+ restrictedSet.has(source) ||
74
+ source.startsWith("server-only") ||
75
+ source.includes("/server/") ||
76
+ source.endsWith("/server")
77
+ ) {
78
+ context.report({
79
+ node,
80
+ messageId: "noServerImport",
81
+ data: { name: source },
82
+ });
83
+ }
84
+ },
85
+ };
86
+ },
87
+ };
88
+
89
+ export default noServerImportsInClient;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * ESLint Rule: rsc/no-server-imports-in-client
3
+ *
4
+ * Enforces server/client boundaries in React Server Components (RSC).
5
+ * When a file is marked with the "use client" directive, it is prohibited
6
+ * from importing server-only packages (e.g. 'server-only', Node.js builtins,
7
+ * or modules from server-designated paths).
8
+ */
9
+
10
+ export const noServerImportsInClient = {
11
+ meta: {
12
+ type: "problem",
13
+ docs: {
14
+ description: "Disallow server-only imports in client components marked with 'use client'",
15
+ category: "Possible Errors",
16
+ recommended: true,
17
+ },
18
+ schema: [
19
+ {
20
+ type: "object",
21
+ properties: {
22
+ serverModules: {
23
+ type: "array",
24
+ items: { type: "string" },
25
+ },
26
+ },
27
+ additionalProperties: false,
28
+ },
29
+ ],
30
+ messages: {
31
+ noServerImport:
32
+ "Server-only module '{{name}}' cannot be imported into a Client Component ('use client'). Keep server logic on the server side of the RSC boundary.",
33
+ },
34
+ },
35
+ create(context) {
36
+ const options = context.options[0] || {};
37
+ const restrictedSet = new Set(
38
+ options.serverModules || [
39
+ "server-only",
40
+ "node:fs",
41
+ "node:fs/promises",
42
+ "fs",
43
+ "fs/promises",
44
+ "node:child_process",
45
+ "child_process",
46
+ "node:net",
47
+ "net",
48
+ "node:tls",
49
+ "tls",
50
+ ]
51
+ );
52
+
53
+ let isClientComponent = false;
54
+
55
+ return {
56
+ Program(node) {
57
+ for (const stmt of node.body) {
58
+ if (
59
+ stmt.type === "ExpressionStatement" &&
60
+ stmt.expression.type === "Literal" &&
61
+ stmt.expression.value === "use client"
62
+ ) {
63
+ isClientComponent = true;
64
+ break;
65
+ }
66
+ }
67
+ },
68
+ ImportDeclaration(node) {
69
+ if (!isClientComponent) return;
70
+ const source = node.source.value;
71
+ if (
72
+ restrictedSet.has(source) ||
73
+ source.startsWith("server-only") ||
74
+ source.includes("/server/") ||
75
+ source.endsWith("/server")
76
+ ) {
77
+ context.report({
78
+ node,
79
+ messageId: "noServerImport",
80
+ data: { name: source },
81
+ });
82
+ }
83
+ },
84
+ };
85
+ },
86
+ };
87
+
88
+ export default noServerImportsInClient;
@@ -0,0 +1,48 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+
5
+ /**
6
+ * Client Component example for RSC boundary.
7
+ * Marked with "use client" directive — renders on client with full interactivity,
8
+ * React hooks, and state management.
9
+ */
10
+ export function ClientCounter({ initialCount = 0, children }) {
11
+ const [count, setCount] = useState(initialCount);
12
+
13
+ return (
14
+ <div className="client-boundary p-4 rounded-lg border border-blue-500/20 bg-blue-500/5 my-4">
15
+ <div className="flex items-center gap-2 mb-2">
16
+ <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800 dark:bg-blue-900/60 dark:text-blue-300">
17
+ Client Component (&quot;use client&quot;)
18
+ </span>
19
+ </div>
20
+ <p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
21
+ Interactive client boundary component. Manages state and handles user interaction.
22
+ </p>
23
+ <div className="flex items-center gap-3">
24
+ <button
25
+ type="button"
26
+ onClick={() => setCount((c) => c + 1)}
27
+ className="px-3 py-1.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors cursor-pointer"
28
+ >
29
+ Count: {count}
30
+ </button>
31
+ <button
32
+ type="button"
33
+ onClick={() => setCount(0)}
34
+ className="px-2 py-1.5 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 cursor-pointer"
35
+ >
36
+ Reset
37
+ </button>
38
+ </div>
39
+ {children && (
40
+ <div className="mt-4 pt-3 border-t border-blue-500/10">
41
+ {children}
42
+ </div>
43
+ )}
44
+ </div>
45
+ );
46
+ }
47
+
48
+ export default ClientCounter;
@@ -0,0 +1,53 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+
5
+ export interface ClientCounterProps {
6
+ initialCount?: number;
7
+ children?: React.ReactNode;
8
+ }
9
+
10
+ /**
11
+ * Client Component example for RSC boundary.
12
+ * Marked with "use client" directive — renders on client with full interactivity,
13
+ * React hooks, and state management.
14
+ */
15
+ export function ClientCounter({ initialCount = 0, children }: ClientCounterProps) {
16
+ const [count, setCount] = useState(initialCount);
17
+
18
+ return (
19
+ <div className="client-boundary p-4 rounded-lg border border-blue-500/20 bg-blue-500/5 my-4">
20
+ <div className="flex items-center gap-2 mb-2">
21
+ <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-blue-100 text-blue-800 dark:bg-blue-900/60 dark:text-blue-300">
22
+ Client Component (&quot;use client&quot;)
23
+ </span>
24
+ </div>
25
+ <p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
26
+ Interactive client boundary component. Manages state and handles user interaction.
27
+ </p>
28
+ <div className="flex items-center gap-3">
29
+ <button
30
+ type="button"
31
+ onClick={() => setCount((c) => c + 1)}
32
+ className="px-3 py-1.5 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700 transition-colors cursor-pointer"
33
+ >
34
+ Count: {count}
35
+ </button>
36
+ <button
37
+ type="button"
38
+ onClick={() => setCount(0)}
39
+ className="px-2 py-1.5 text-xs text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 cursor-pointer"
40
+ >
41
+ Reset
42
+ </button>
43
+ </div>
44
+ {children && (
45
+ <div className="mt-4 pt-3 border-t border-blue-500/10">
46
+ {children}
47
+ </div>
48
+ )}
49
+ </div>
50
+ );
51
+ }
52
+
53
+ export default ClientCounter;
@@ -0,0 +1,41 @@
1
+ import React from "react";
2
+ import { ClientCounter } from "./ClientCounter";
3
+
4
+ /**
5
+ * Server Component example for RSC boundary.
6
+ * Renders on the server by default (zero client-side bundle impact).
7
+ * Demonstrates composing a Client Component across the RSC boundary.
8
+ * Compatible with both Next.js App Router and Pages Router.
9
+ */
10
+ export function ServerCard({
11
+ title = "Server Component Shell",
12
+ serverMessage = "Rendered on the server with zero client bundle overhead for this card frame.",
13
+ }) {
14
+ return (
15
+ <div className="server-boundary p-6 rounded-xl border border-emerald-500/20 bg-emerald-500/5 my-4">
16
+ <div className="flex items-center justify-between mb-3">
17
+ <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-800 dark:bg-emerald-900/60 dark:text-emerald-300">
18
+ Server Component (Default RSC)
19
+ </span>
20
+ </div>
21
+ <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
22
+ {title}
23
+ </h3>
24
+ <p className="text-sm text-gray-600 dark:text-gray-300 mb-4">
25
+ {serverMessage}
26
+ </p>
27
+ <div className="p-4 rounded-lg border border-dashed border-emerald-500/30 bg-white/40 dark:bg-black/20">
28
+ <p className="text-xs font-semibold text-emerald-700 dark:text-emerald-400 uppercase tracking-wider mb-2">
29
+ Server / Client Boundary
30
+ </p>
31
+ <ClientCounter initialCount={0}>
32
+ <p className="text-xs text-gray-500 dark:text-gray-400 italic">
33
+ Server-rendered child passed through client component boundary.
34
+ </p>
35
+ </ClientCounter>
36
+ </div>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export default ServerCard;
@@ -0,0 +1,46 @@
1
+ import React from "react";
2
+ import { ClientCounter } from "./ClientCounter";
3
+
4
+ export interface ServerCardProps {
5
+ title?: string;
6
+ serverMessage?: string;
7
+ }
8
+
9
+ /**
10
+ * Server Component example for RSC boundary.
11
+ * Renders on the server by default (zero client-side bundle impact).
12
+ * Demonstrates composing a Client Component across the RSC boundary.
13
+ * Compatible with both Next.js App Router and Pages Router.
14
+ */
15
+ export function ServerCard({
16
+ title = "Server Component Shell",
17
+ serverMessage = "Rendered on the server with zero client bundle overhead for this card frame.",
18
+ }: ServerCardProps) {
19
+ return (
20
+ <div className="server-boundary p-6 rounded-xl border border-emerald-500/20 bg-emerald-500/5 my-4">
21
+ <div className="flex items-center justify-between mb-3">
22
+ <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-800 dark:bg-emerald-900/60 dark:text-emerald-300">
23
+ Server Component (Default RSC)
24
+ </span>
25
+ </div>
26
+ <h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
27
+ {title}
28
+ </h3>
29
+ <p className="text-sm text-gray-600 dark:text-gray-300 mb-4">
30
+ {serverMessage}
31
+ </p>
32
+ <div className="p-4 rounded-lg border border-dashed border-emerald-500/30 bg-white/40 dark:bg-black/20">
33
+ <p className="text-xs font-semibold text-emerald-700 dark:text-emerald-400 uppercase tracking-wider mb-2">
34
+ Server / Client Boundary
35
+ </p>
36
+ <ClientCounter initialCount={0}>
37
+ <p className="text-xs text-gray-500 dark:text-gray-400 italic">
38
+ Server-rendered child passed through client component boundary.
39
+ </p>
40
+ </ClientCounter>
41
+ </div>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ export default ServerCard;
@@ -0,0 +1,2 @@
1
+ export { ClientCounter } from "./ClientCounter";
2
+ export { ServerCard } from "./ServerCard";
@@ -0,0 +1,4 @@
1
+ export { ClientCounter } from "./ClientCounter";
2
+ export type { ClientCounterProps } from "./ClientCounter";
3
+ export { ServerCard } from "./ServerCard";
4
+ export type { ServerCardProps } from "./ServerCard";
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/app/page";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/app/page";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,20 @@
1
+ import nextJest from "next/jest.js";
2
+
3
+ const createJestConfig = nextJest({
4
+ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
5
+ dir: "./",
6
+ });
7
+
8
+ // Add any custom config to be passed to Jest
9
+ /** @type {import('jest').Config} */
10
+ const config = {
11
+ coverageProvider: "v8",
12
+ testEnvironment: "jsdom",
13
+ setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
14
+ moduleNameMapper: {
15
+ "^@/(.*)$": "<rootDir>/src/$1",
16
+ },
17
+ };
18
+
19
+ // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
20
+ export default createJestConfig(config);
@@ -0,0 +1,20 @@
1
+ import type { Config } from "jest";
2
+ import nextJest from "next/jest.js";
3
+
4
+ const createJestConfig = nextJest({
5
+ // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
6
+ dir: "./",
7
+ });
8
+
9
+ // Add any custom config to be passed to Jest
10
+ const config: Config = {
11
+ coverageProvider: "v8",
12
+ testEnvironment: "jsdom",
13
+ setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
14
+ moduleNameMapper: {
15
+ "^@/(.*)$": "<rootDir>/src/$1",
16
+ },
17
+ };
18
+
19
+ // createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
20
+ export default createJestConfig(config);
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/pages/index";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,10 @@
1
+ import "@testing-library/jest-dom";
2
+ import { render, screen } from "@testing-library/react";
3
+ import Home from "@/pages/index";
4
+
5
+ describe("Home", () => {
6
+ it("renders a heading", () => {
7
+ render(<Home />);
8
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
9
+ });
10
+ });
@@ -0,0 +1,11 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, it, expect } from "vitest";
4
+ import Home from "@/app/page";
5
+
6
+ describe("Home", () => {
7
+ it("renders a heading", () => {
8
+ render(<Home />);
9
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
10
+ });
11
+ });
@@ -0,0 +1,11 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, it, expect } from "vitest";
4
+ import Home from "@/app/page";
5
+
6
+ describe("Home", () => {
7
+ it("renders a heading", () => {
8
+ render(<Home />);
9
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
10
+ });
11
+ });
@@ -0,0 +1,11 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, it, expect } from "vitest";
4
+ import Home from "@/pages/index";
5
+
6
+ describe("Home", () => {
7
+ it("renders a heading", () => {
8
+ render(<Home />);
9
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
10
+ });
11
+ });
@@ -0,0 +1,11 @@
1
+ import "@testing-library/jest-dom/vitest";
2
+ import { render, screen } from "@testing-library/react";
3
+ import { describe, it, expect } from "vitest";
4
+ import Home from "@/pages/index";
5
+
6
+ describe("Home", () => {
7
+ it("renders a heading", () => {
8
+ render(<Home />);
9
+ expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();
10
+ });
11
+ });
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import path from "path";
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ "@": path.resolve(__dirname, "./src"),
8
+ },
9
+ },
10
+ test: {
11
+ globals: true,
12
+ environment: "jsdom",
13
+ setupFiles: "./src/test/setup.js",
14
+ css: true,
15
+ },
16
+ });
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "vitest/config";
2
+ import path from "path";
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ alias: {
7
+ "@": path.resolve(__dirname, "./src"),
8
+ },
9
+ },
10
+ test: {
11
+ globals: true,
12
+ environment: "jsdom",
13
+ setupFiles: "./src/test/setup.ts",
14
+ css: true,
15
+ },
16
+ });