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.
- package/README.md +24 -6
- package/bin/cli.js +2 -1
- package/package.json +2 -2
- package/schema/lumen.config.v2.json +29 -0
- package/src/agent-context.js +71 -47
- package/src/cleanup.js +19 -1
- package/src/cli-args.js +131 -5
- package/src/configure.js +8 -4
- package/src/dependencies.js +46 -13
- package/src/engine/capabilities.js +71 -1
- package/src/engine/composition.js +334 -17
- package/src/engine/providers/base-provider.js +4 -4
- package/src/env.js +24 -6
- package/src/main.js +38 -15
- package/src/manifest/emit.js +14 -1
- package/src/manifest/schema.js +30 -0
- package/src/prompts.js +227 -23
- package/templates/bases/next/app-router/js/.provenance.json +19 -0
- package/templates/bases/next/app-router/js/README.md +36 -0
- package/templates/bases/next/app-router/js/jsconfig.json +7 -0
- package/templates/bases/next/app-router/js/next.config.mjs +6 -0
- package/templates/bases/next/app-router/js/package.json +15 -0
- package/templates/bases/next/app-router/js/public/file.svg +1 -0
- package/templates/bases/next/app-router/js/public/globe.svg +1 -0
- package/templates/bases/next/app-router/js/public/next.svg +1 -0
- package/templates/bases/next/app-router/js/public/vercel.svg +1 -0
- package/templates/bases/next/app-router/js/public/window.svg +1 -0
- package/templates/bases/next/app-router/js/src/app/api/hello/route.js +5 -0
- package/templates/bases/next/app-router/js/src/app/blog/[slug]/page.js +33 -0
- package/templates/bases/next/app-router/js/src/app/favicon.ico +0 -0
- package/templates/bases/next/app-router/js/src/app/globals.css +49 -0
- package/templates/bases/next/app-router/js/src/app/layout.js +25 -0
- package/templates/bases/next/app-router/js/src/app/loading.js +15 -0
- package/templates/bases/next/app-router/js/src/app/not-found.js +29 -0
- package/templates/bases/next/app-router/js/src/app/page.js +69 -0
- package/templates/bases/next/app-router/js/src/app/page.module.css +150 -0
- package/templates/bases/next/app-router/ts/.provenance.json +19 -0
- package/templates/bases/next/app-router/ts/README.md +36 -0
- package/templates/bases/next/app-router/ts/next.config.ts +7 -0
- package/templates/bases/next/app-router/ts/package.json +21 -0
- package/templates/bases/next/app-router/ts/public/file.svg +1 -0
- package/templates/bases/next/app-router/ts/public/globe.svg +1 -0
- package/templates/bases/next/app-router/ts/public/next.svg +1 -0
- package/templates/bases/next/app-router/ts/public/vercel.svg +1 -0
- package/templates/bases/next/app-router/ts/public/window.svg +1 -0
- package/templates/bases/next/app-router/ts/src/app/api/hello/route.ts +5 -0
- package/templates/bases/next/app-router/ts/src/app/blog/[slug]/page.tsx +38 -0
- package/templates/bases/next/app-router/ts/src/app/favicon.ico +0 -0
- package/templates/bases/next/app-router/ts/src/app/globals.css +49 -0
- package/templates/bases/next/app-router/ts/src/app/layout.tsx +30 -0
- package/templates/bases/next/app-router/ts/src/app/loading.tsx +15 -0
- package/templates/bases/next/app-router/ts/src/app/not-found.tsx +29 -0
- package/templates/bases/next/app-router/ts/src/app/page.module.css +150 -0
- package/templates/bases/next/app-router/ts/src/app/page.tsx +69 -0
- package/templates/bases/next/app-router/ts/tsconfig.json +34 -0
- package/templates/bases/next/pages-router/js/.provenance.json +19 -0
- package/templates/bases/next/pages-router/js/README.md +40 -0
- package/templates/bases/next/pages-router/js/jsconfig.json +7 -0
- package/templates/bases/next/pages-router/js/next.config.mjs +7 -0
- package/templates/bases/next/pages-router/js/package.json +15 -0
- package/templates/bases/next/pages-router/js/public/favicon.ico +0 -0
- package/templates/bases/next/pages-router/js/public/file.svg +1 -0
- package/templates/bases/next/pages-router/js/public/globe.svg +1 -0
- package/templates/bases/next/pages-router/js/public/next.svg +1 -0
- package/templates/bases/next/pages-router/js/public/vercel.svg +1 -0
- package/templates/bases/next/pages-router/js/public/window.svg +1 -0
- package/templates/bases/next/pages-router/js/src/pages/404.js +35 -0
- package/templates/bases/next/pages-router/js/src/pages/500.js +35 -0
- package/templates/bases/next/pages-router/js/src/pages/_app.js +20 -0
- package/templates/bases/next/pages-router/js/src/pages/_document.js +15 -0
- package/templates/bases/next/pages-router/js/src/pages/api/hello.js +5 -0
- package/templates/bases/next/pages-router/js/src/pages/blog/[slug].js +41 -0
- package/templates/bases/next/pages-router/js/src/pages/index.js +77 -0
- package/templates/bases/next/pages-router/js/src/styles/Home.module.css +149 -0
- package/templates/bases/next/pages-router/js/src/styles/globals.css +42 -0
- package/templates/bases/next/pages-router/ts/.provenance.json +19 -0
- package/templates/bases/next/pages-router/ts/README.md +40 -0
- package/templates/bases/next/pages-router/ts/next.config.ts +8 -0
- package/templates/bases/next/pages-router/ts/package.json +21 -0
- package/templates/bases/next/pages-router/ts/public/favicon.ico +0 -0
- package/templates/bases/next/pages-router/ts/public/file.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/globe.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/next.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/vercel.svg +1 -0
- package/templates/bases/next/pages-router/ts/public/window.svg +1 -0
- package/templates/bases/next/pages-router/ts/src/pages/404.tsx +35 -0
- package/templates/bases/next/pages-router/ts/src/pages/500.tsx +35 -0
- package/templates/bases/next/pages-router/ts/src/pages/_app.tsx +21 -0
- package/templates/bases/next/pages-router/ts/src/pages/_document.tsx +15 -0
- package/templates/bases/next/pages-router/ts/src/pages/api/hello.ts +13 -0
- package/templates/bases/next/pages-router/ts/src/pages/blog/[slug].tsx +46 -0
- package/templates/bases/next/pages-router/ts/src/pages/index.tsx +77 -0
- package/templates/bases/next/pages-router/ts/src/styles/Home.module.css +149 -0
- package/templates/bases/next/pages-router/ts/src/styles/globals.css +42 -0
- package/templates/bases/next/pages-router/ts/tsconfig.json +29 -0
- package/templates/conditional/database/prisma/db.js +14 -0
- package/templates/conditional/database/prisma/db.ts +16 -0
- package/templates/conditional/database/prisma/schema.prisma +9 -0
- package/templates/conditional/database/prisma/seed.js +16 -0
- package/templates/conditional/database/prisma/seed.ts +16 -0
- package/templates/conditional/docker/.dockerignore +10 -0
- package/templates/conditional/docker/Dockerfile +57 -0
- package/templates/conditional/docker/docker-compose.yml +35 -0
- package/templates/conditional/linting/biome/biome.json +31 -0
- package/templates/conditional/next/config/cloudflare/next.config.mjs +6 -0
- package/templates/conditional/next/config/cloudflare/next.config.ts +7 -0
- package/templates/conditional/next/config/node/next.config.mjs +6 -0
- package/templates/conditional/next/config/node/next.config.ts +7 -0
- package/templates/conditional/next/config/static/next.config.mjs +6 -0
- package/templates/conditional/next/config/static/next.config.ts +7 -0
- package/templates/conditional/next/config/turbopack/next.config.mjs +7 -0
- package/templates/conditional/next/config/turbopack/next.config.ts +8 -0
- package/templates/conditional/next/config/vercel/next.config.mjs +6 -0
- package/templates/conditional/next/config/vercel/next.config.ts +7 -0
- package/templates/conditional/next/config/webpack/next.config.mjs +9 -0
- package/templates/conditional/next/config/webpack/next.config.ts +10 -0
- package/templates/conditional/next/font/BUILD_SIZE_NOTE.md +17 -0
- package/templates/conditional/next/font/app-router/src/app/layout.js +25 -0
- package/templates/conditional/next/font/app-router/src/app/layout.tsx +30 -0
- package/templates/conditional/next/font/pages-router/src/pages/_app.js +20 -0
- package/templates/conditional/next/font/pages-router/src/pages/_app.tsx +21 -0
- package/templates/conditional/next/font/pages-router/src/pages/_document.js +18 -0
- package/templates/conditional/next/font/pages-router/src/pages/_document.tsx +18 -0
- package/templates/conditional/next/font/src/app/layout.js +25 -0
- package/templates/conditional/next/font/src/app/layout.tsx +30 -0
- package/templates/conditional/next/font/src/lib/fonts.js +11 -0
- package/templates/conditional/next/font/src/lib/fonts.ts +11 -0
- package/templates/conditional/next/font/src/pages/_app.js +20 -0
- package/templates/conditional/next/font/src/pages/_app.tsx +21 -0
- package/templates/conditional/next/font/src/pages/_document.js +18 -0
- package/templates/conditional/next/font/src/pages/_document.tsx +18 -0
- package/templates/conditional/next/image/BUILD_SIZE_NOTE.md +15 -0
- package/templates/conditional/next/image/app-router/src/components/ImageExample.jsx +34 -0
- package/templates/conditional/next/image/app-router/src/components/ImageExample.tsx +44 -0
- package/templates/conditional/next/image/pages-router/src/components/ImageExample.jsx +34 -0
- package/templates/conditional/next/image/pages-router/src/components/ImageExample.tsx +44 -0
- package/templates/conditional/next/image/src/components/ImageExample.jsx +44 -0
- package/templates/conditional/next/image/src/components/ImageExample.tsx +54 -0
- package/templates/conditional/next/image/src/components/image/index.js +1 -0
- package/templates/conditional/next/image/src/components/image/index.ts +2 -0
- package/templates/conditional/next/route-groups/js/src/app/(admin)/layout.jsx +23 -0
- package/templates/conditional/next/route-groups/js/src/app/(auth)/layout.jsx +17 -0
- package/templates/conditional/next/route-groups/js/src/app/(private)/layout.jsx +27 -0
- package/templates/conditional/next/route-groups/js/src/app/(public)/layout.jsx +28 -0
- package/templates/conditional/next/route-groups/js/src/app/(public)/page.jsx +38 -0
- package/templates/conditional/next/route-groups/js/src/middleware.js +48 -0
- package/templates/conditional/next/route-groups/ts/src/app/(admin)/layout.tsx +28 -0
- package/templates/conditional/next/route-groups/ts/src/app/(auth)/layout.tsx +22 -0
- package/templates/conditional/next/route-groups/ts/src/app/(private)/layout.tsx +32 -0
- package/templates/conditional/next/route-groups/ts/src/app/(public)/layout.tsx +33 -0
- package/templates/conditional/next/route-groups/ts/src/app/(public)/page.tsx +38 -0
- package/templates/conditional/next/route-groups/ts/src/middleware.ts +49 -0
- package/templates/conditional/next/server-components/README.md +13 -0
- package/templates/conditional/next/server-components/eslint.config.mjs +47 -0
- package/templates/conditional/next/server-components/rules/no-server-imports-in-client.js +89 -0
- package/templates/conditional/next/server-components/rules/no-server-imports-in-client.mjs +88 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx +48 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx +53 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.jsx +41 -0
- package/templates/conditional/next/server-components/src/components/boundaries/ServerCard.tsx +46 -0
- package/templates/conditional/next/server-components/src/components/boundaries/index.js +2 -0
- package/templates/conditional/next/server-components/src/components/boundaries/index.ts +4 -0
- package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.jsx +10 -0
- package/templates/conditional/testing/jest/next/app-router/src/__tests__/page.test.tsx +10 -0
- package/templates/conditional/testing/jest/next/jest.config.mjs +20 -0
- package/templates/conditional/testing/jest/next/jest.config.ts +20 -0
- package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.jsx +10 -0
- package/templates/conditional/testing/jest/next/pages-router/src/__tests__/index.test.tsx +10 -0
- package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.jsx +11 -0
- package/templates/conditional/testing/vitest/next/app-router/src/__tests__/page.test.tsx +11 -0
- package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.jsx +11 -0
- package/templates/conditional/testing/vitest/next/pages-router/src/__tests__/index.test.tsx +11 -0
- package/templates/conditional/testing/vitest/next/vitest.config.js +16 -0
- 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;
|
package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.jsx
ADDED
|
@@ -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 ("use client")
|
|
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;
|
package/templates/conditional/next/server-components/src/components/boundaries/ClientCounter.tsx
ADDED
|
@@ -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 ("use client")
|
|
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,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
|
+
});
|