@farm.js/create-app 0.1.0-beta.21 → 0.1.0-beta.23
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 +8 -6
- package/bin/create-farm-app.js +1 -1
- package/dist/index.js +34 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34 -8
- package/dist/index.mjs.map +1 -1
- package/dist/utils.js +8 -6
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +8 -6
- package/dist/utils.mjs.map +1 -1
- package/package.json +2 -2
- package/templates/auth/.env.example +6 -0
- package/templates/auth/README.md +95 -0
- package/templates/auth/farm.config.ts +14 -0
- package/templates/auth/gitignore +10 -0
- package/templates/auth/package.json +32 -0
- package/templates/auth/pnpm-workspace.yaml +13 -0
- package/templates/auth/src/app/dashboard/middleware.ts +13 -0
- package/templates/auth/src/app/dashboard/page.tsx +140 -0
- package/templates/auth/src/app/error.tsx +25 -0
- package/templates/auth/src/app/globals.css +1064 -0
- package/templates/auth/src/app/layout.tsx +11 -0
- package/templates/auth/src/app/loading.tsx +15 -0
- package/templates/auth/src/app/not-found.tsx +26 -0
- package/templates/auth/src/app/page.tsx +46 -0
- package/templates/auth/src/app/sign-in/page.tsx +16 -0
- package/templates/auth/src/app/sign-up/page.tsx +16 -0
- package/templates/auth/src/components/auth-form.tsx +120 -0
- package/templates/auth/src/components/auth-shell.tsx +21 -0
- package/templates/auth/src/components/resource-links.tsx +78 -0
- package/templates/auth/src/components/sign-out-button.tsx +45 -0
- package/templates/auth/src/components/site-header.tsx +46 -0
- package/templates/auth/tsconfig.json +20 -0
- package/templates/basic/farm.config.ts +3 -0
- package/templates/basic/package.json +4 -2
- package/templates/basic/pnpm-workspace.yaml +3 -0
- package/templates/basic/src/app/globals.css +241 -0
- package/templates/basic/src/app/layout.tsx +2 -4
- package/templates/basic/src/app/page.tsx +20 -61
- package/templates/basic/src/components/resource-links.tsx +78 -0
- package/templates/better-auth/.env.example +3 -0
- package/templates/better-auth/README.md +87 -0
- package/templates/better-auth/farm.config.ts +32 -0
- package/templates/better-auth/gitignore +10 -0
- package/templates/better-auth/package.json +34 -0
- package/templates/better-auth/pnpm-workspace.yaml +13 -0
- package/templates/better-auth/src/app/dashboard/middleware.ts +15 -0
- package/templates/better-auth/src/app/dashboard/page.tsx +188 -0
- package/templates/better-auth/src/app/error.tsx +25 -0
- package/templates/better-auth/src/app/globals.css +1064 -0
- package/templates/better-auth/src/app/layout.tsx +11 -0
- package/templates/better-auth/src/app/loading.tsx +15 -0
- package/templates/better-auth/src/app/not-found.tsx +26 -0
- package/templates/better-auth/src/app/page.tsx +50 -0
- package/templates/better-auth/src/app/sign-in/page.tsx +16 -0
- package/templates/better-auth/src/app/sign-up/page.tsx +16 -0
- package/templates/better-auth/src/components/auth-form.tsx +120 -0
- package/templates/better-auth/src/components/auth-shell.tsx +21 -0
- package/templates/better-auth/src/components/resource-links.tsx +78 -0
- package/templates/better-auth/src/components/sign-out-button.tsx +45 -0
- package/templates/better-auth/src/components/site-header.tsx +46 -0
- package/templates/better-auth/src/lib/auth-client.ts +5 -0
- package/templates/better-auth/src/lib/auth.ts +50 -0
- package/templates/better-auth/src/lib/session.ts +8 -0
- package/templates/better-auth/tsconfig.json +20 -0
- package/templates/basic/src/app/about/page.tsx +0 -32
|
@@ -1 +1,242 @@
|
|
|
1
|
+
@import "@fontsource-variable/geist/wght.css";
|
|
2
|
+
@import "@fontsource-variable/geist-mono/wght.css";
|
|
1
3
|
@import "tailwindcss";
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--paper: #000000;
|
|
7
|
+
--paper-soft: #080808;
|
|
8
|
+
--ink: #f5f5f5;
|
|
9
|
+
--ink-soft: #a1a1a1;
|
|
10
|
+
--ink-faint: #686868;
|
|
11
|
+
--line: rgb(255 255 255 / 0.12);
|
|
12
|
+
--line-strong: rgb(255 255 255 / 0.22);
|
|
13
|
+
--focus: #ffffff;
|
|
14
|
+
--font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
|
|
15
|
+
--font-mono: "Geist Mono Variable", ui-monospace, monospace;
|
|
16
|
+
color-scheme: dark;
|
|
17
|
+
color: var(--ink);
|
|
18
|
+
font-family: var(--font-sans);
|
|
19
|
+
font-synthesis: none;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
* {
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
html {
|
|
27
|
+
min-width: 320px;
|
|
28
|
+
min-height: 100%;
|
|
29
|
+
background: var(--paper);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
body {
|
|
33
|
+
min-height: 100vh;
|
|
34
|
+
margin: 0;
|
|
35
|
+
background: var(--paper);
|
|
36
|
+
color: var(--ink);
|
|
37
|
+
font-family: var(--font-sans);
|
|
38
|
+
text-rendering: optimizeLegibility;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
button,
|
|
42
|
+
input {
|
|
43
|
+
font: inherit;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
a {
|
|
47
|
+
color: inherit;
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
-webkit-tap-highlight-color: transparent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
code {
|
|
53
|
+
font-family: var(--font-mono);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
::selection {
|
|
57
|
+
background: white;
|
|
58
|
+
color: black;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:focus-visible {
|
|
62
|
+
outline: 2px solid var(--focus);
|
|
63
|
+
outline-offset: 3px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.landing-main {
|
|
67
|
+
position: relative;
|
|
68
|
+
isolation: isolate;
|
|
69
|
+
width: 100%;
|
|
70
|
+
min-height: 100vh;
|
|
71
|
+
overflow: hidden;
|
|
72
|
+
background: var(--paper);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.hero-section {
|
|
76
|
+
position: relative;
|
|
77
|
+
display: flex;
|
|
78
|
+
width: 100%;
|
|
79
|
+
overflow: hidden;
|
|
80
|
+
min-height: 100vh;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: flex-start;
|
|
83
|
+
padding: clamp(2rem, 6vh, 4rem) clamp(1.25rem, 7vw, 6rem);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.hero-section::before,
|
|
87
|
+
.hero-section::after {
|
|
88
|
+
position: absolute;
|
|
89
|
+
width: 9px;
|
|
90
|
+
height: 9px;
|
|
91
|
+
border-color: rgb(255 255 255 / 0.3);
|
|
92
|
+
content: "";
|
|
93
|
+
pointer-events: none;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.hero-section::before {
|
|
97
|
+
top: -1px;
|
|
98
|
+
left: -1px;
|
|
99
|
+
border-top: 1px solid;
|
|
100
|
+
border-left: 1px solid;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.hero-section::after {
|
|
104
|
+
right: 0;
|
|
105
|
+
bottom: -1px;
|
|
106
|
+
border-right: 1px solid;
|
|
107
|
+
border-bottom: 1px solid;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.hero-copy {
|
|
111
|
+
width: min(100%, 680px);
|
|
112
|
+
margin: 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.eyebrow-row {
|
|
116
|
+
display: flex;
|
|
117
|
+
align-items: center;
|
|
118
|
+
gap: 0.65rem;
|
|
119
|
+
color: var(--ink-faint);
|
|
120
|
+
font-family: var(--font-mono);
|
|
121
|
+
font-size: 0.67rem;
|
|
122
|
+
text-transform: uppercase;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.eyebrow-row span:first-child {
|
|
126
|
+
color: var(--ink);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.hero-copy h1 {
|
|
130
|
+
max-width: 620px;
|
|
131
|
+
margin: 1.4rem 0 0;
|
|
132
|
+
font-size: clamp(1.85rem, 4vw, 3.2rem);
|
|
133
|
+
font-weight: 510;
|
|
134
|
+
letter-spacing: -0.05em;
|
|
135
|
+
line-height: 1.05;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.hero-copy h1 code {
|
|
139
|
+
border: 1px solid var(--line);
|
|
140
|
+
background: rgb(255 255 255 / 0.07);
|
|
141
|
+
padding: 0.04em 0.2em;
|
|
142
|
+
font-size: 0.78em;
|
|
143
|
+
font-weight: 450;
|
|
144
|
+
letter-spacing: -0.035em;
|
|
145
|
+
white-space: nowrap;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.command-list {
|
|
149
|
+
max-width: 520px;
|
|
150
|
+
margin-top: 1.75rem;
|
|
151
|
+
border-block: 1px solid var(--line);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.command-row {
|
|
155
|
+
display: grid;
|
|
156
|
+
min-width: 0;
|
|
157
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
158
|
+
align-items: center;
|
|
159
|
+
gap: 0.8rem;
|
|
160
|
+
padding: 0.9rem 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.command-row + .command-row {
|
|
164
|
+
border-top: 1px solid var(--line);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.command-row > span {
|
|
168
|
+
color: var(--ink-faint);
|
|
169
|
+
font-family: var(--font-mono);
|
|
170
|
+
font-size: 0.62rem;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.command-row code {
|
|
174
|
+
color: var(--ink);
|
|
175
|
+
font-size: 0.75rem;
|
|
176
|
+
overflow-wrap: anywhere;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.resource-links {
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-wrap: wrap;
|
|
182
|
+
gap: 0.65rem;
|
|
183
|
+
margin-top: 1.5rem;
|
|
184
|
+
color: var(--ink-soft);
|
|
185
|
+
font-family: var(--font-mono);
|
|
186
|
+
font-size: 0.68rem;
|
|
187
|
+
text-transform: uppercase;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.resource-link-item {
|
|
191
|
+
display: inline-flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
gap: 0.65rem;
|
|
194
|
+
white-space: nowrap;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.resource-separator {
|
|
198
|
+
color: var(--ink-faint);
|
|
199
|
+
user-select: none;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.resource-links a {
|
|
203
|
+
display: inline-flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
gap: 0.45rem;
|
|
206
|
+
border-bottom: 1px solid transparent;
|
|
207
|
+
padding-block: 0.2rem;
|
|
208
|
+
transition:
|
|
209
|
+
border-color 150ms ease,
|
|
210
|
+
color 150ms ease;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.resource-icon {
|
|
214
|
+
width: 0.85rem;
|
|
215
|
+
height: 0.85rem;
|
|
216
|
+
flex: 0 0 auto;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.resource-links a:hover {
|
|
220
|
+
border-color: currentColor;
|
|
221
|
+
color: var(--ink);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@media (max-width: 620px) {
|
|
225
|
+
.hero-section {
|
|
226
|
+
padding: 2rem 1.25rem;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.hero-copy h1 {
|
|
230
|
+
font-size: clamp(1.85rem, 9vw, 2.5rem);
|
|
231
|
+
overflow-wrap: anywhere;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@media (prefers-reduced-motion: reduce) {
|
|
236
|
+
*,
|
|
237
|
+
*::before,
|
|
238
|
+
*::after {
|
|
239
|
+
scroll-behavior: auto !important;
|
|
240
|
+
transition-duration: 0.01ms !important;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { LayoutProps, Metadata } from "@farm.js/core";
|
|
|
2
2
|
import "./globals.css";
|
|
3
3
|
|
|
4
4
|
export const metadata: Metadata = {
|
|
5
|
-
title: "
|
|
5
|
+
title: "FARMJS App",
|
|
6
6
|
description: "A modern React meta-framework built on Vite",
|
|
7
7
|
icons: {
|
|
8
8
|
icon: [{ url: "/favicon.svg", type: "image/svg+xml", sizes: "any" }],
|
|
@@ -16,9 +16,7 @@ export default function RootLayout({ children }: LayoutProps) {
|
|
|
16
16
|
<meta charSet="utf-8" />
|
|
17
17
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
18
18
|
</head>
|
|
19
|
-
<body
|
|
20
|
-
<main className="min-h-screen">{children}</main>
|
|
21
|
-
</body>
|
|
19
|
+
<body>{children}</body>
|
|
22
20
|
</html>
|
|
23
21
|
);
|
|
24
22
|
}
|
|
@@ -1,70 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FARM_VERSION } from "@farm.js/core/version";
|
|
1
|
+
import { ResourceLinks } from "../components/resource-links";
|
|
3
2
|
|
|
4
3
|
export default function HomePage() {
|
|
5
4
|
return (
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
<div>
|
|
9
|
-
<
|
|
10
|
-
<span
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</h1>
|
|
14
|
-
|
|
15
|
-
<p className="text-xl text-gray-600 max-w-2xl mx-auto">
|
|
16
|
-
A modern React meta-framework built on Vite with Next.js-like semantics
|
|
17
|
-
</p>
|
|
18
|
-
</div>
|
|
5
|
+
<main className="landing-main">
|
|
6
|
+
<section className="hero-section">
|
|
7
|
+
<div className="hero-copy">
|
|
8
|
+
<div className="eyebrow-row">
|
|
9
|
+
<span>00</span>
|
|
10
|
+
<span>FARMJS / Basic starter</span>
|
|
11
|
+
</div>
|
|
19
12
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium shadow-md hover:shadow-lg"
|
|
24
|
-
>
|
|
25
|
-
About Page
|
|
26
|
-
</Link>
|
|
13
|
+
<h1>
|
|
14
|
+
Edit <code>page.tsx</code> to begin.
|
|
15
|
+
</h1>
|
|
27
16
|
|
|
28
|
-
<
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
>
|
|
34
|
-
Documentation
|
|
35
|
-
</a>
|
|
36
|
-
</div>
|
|
17
|
+
<div className="command-list" aria-label="Development command">
|
|
18
|
+
<div className="command-row">
|
|
19
|
+
<span>01</span>
|
|
20
|
+
<code>pnpm dev</code>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
<h2 className="text-2xl font-bold mb-6 text-gray-900">Features</h2>
|
|
40
|
-
<ul className="space-y-3">
|
|
41
|
-
<li className="flex items-start gap-3">
|
|
42
|
-
<span className="text-2xl">🚀</span>
|
|
43
|
-
<span className="text-gray-700">Blazing fast development with Vite</span>
|
|
44
|
-
</li>
|
|
45
|
-
<li className="flex items-start gap-3">
|
|
46
|
-
<span className="text-2xl">⚛️</span>
|
|
47
|
-
<span className="text-gray-700">React Server Components support</span>
|
|
48
|
-
</li>
|
|
49
|
-
<li className="flex items-start gap-3">
|
|
50
|
-
<span className="text-2xl">🎯</span>
|
|
51
|
-
<span className="text-gray-700">Next.js-like file-based routing</span>
|
|
52
|
-
</li>
|
|
53
|
-
<li className="flex items-start gap-3">
|
|
54
|
-
<span className="text-2xl">🎨</span>
|
|
55
|
-
<span className="text-gray-700">Tailwind CSS built-in</span>
|
|
56
|
-
</li>
|
|
57
|
-
<li className="flex items-start gap-3">
|
|
58
|
-
<span className="text-2xl">📦</span>
|
|
59
|
-
<span className="text-gray-700">Zero configuration setup</span>
|
|
60
|
-
</li>
|
|
61
|
-
<li className="flex items-start gap-3">
|
|
62
|
-
<span className="text-2xl">🧪</span>
|
|
63
|
-
<span className="text-gray-700">AI-friendly code structure</span>
|
|
64
|
-
</li>
|
|
65
|
-
</ul>
|
|
24
|
+
<ResourceLinks className="resource-links" />
|
|
66
25
|
</div>
|
|
67
|
-
</
|
|
68
|
-
</
|
|
26
|
+
</section>
|
|
27
|
+
</main>
|
|
69
28
|
);
|
|
70
29
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
interface ResourceLinkAction {
|
|
2
|
+
href: string;
|
|
3
|
+
label: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface ResourceLinksProps {
|
|
7
|
+
className: string;
|
|
8
|
+
primary?: ResourceLinkAction;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function DocsIcon() {
|
|
12
|
+
return (
|
|
13
|
+
<svg
|
|
14
|
+
className="resource-icon"
|
|
15
|
+
viewBox="0 0 24 24"
|
|
16
|
+
fill="none"
|
|
17
|
+
stroke="currentColor"
|
|
18
|
+
strokeWidth="1.8"
|
|
19
|
+
strokeLinecap="round"
|
|
20
|
+
strokeLinejoin="round"
|
|
21
|
+
aria-hidden="true"
|
|
22
|
+
focusable="false"
|
|
23
|
+
>
|
|
24
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8Z" />
|
|
25
|
+
<path d="M14 2v6h6M8 13h8M8 17h6" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function GitHubIcon() {
|
|
31
|
+
return (
|
|
32
|
+
<svg
|
|
33
|
+
className="resource-icon"
|
|
34
|
+
viewBox="0 0 24 24"
|
|
35
|
+
fill="currentColor"
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
focusable="false"
|
|
38
|
+
>
|
|
39
|
+
<path d="M12 .7a11.5 11.5 0 0 0-3.64 22.41c.58.1.79-.25.79-.56v-2.23c-3.22.7-3.9-1.37-3.9-1.37-.52-1.34-1.28-1.69-1.28-1.69-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.77 2.7 1.26 3.36.96.1-.75.4-1.26.73-1.55-2.57-.29-5.27-1.29-5.27-5.68 0-1.26.45-2.28 1.18-3.09-.12-.29-.51-1.47.11-3.05 0 0 .96-.31 3.16 1.18A10.95 10.95 0 0 1 12 6.1c.98 0 1.95.13 2.87.39 2.2-1.49 3.16-1.18 3.16-1.18.62 1.58.23 2.76.11 3.05.74.81 1.18 1.83 1.18 3.09 0 4.4-2.71 5.38-5.29 5.67.42.36.79 1.07.79 2.16v3.27c0 .31.21.67.8.56A11.5 11.5 0 0 0 12 .7Z" />
|
|
40
|
+
</svg>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function ResourceSeparator() {
|
|
45
|
+
return (
|
|
46
|
+
<span className="resource-separator" aria-hidden="true">
|
|
47
|
+
/
|
|
48
|
+
</span>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function ResourceLinks({ className, primary }: ResourceLinksProps) {
|
|
53
|
+
return (
|
|
54
|
+
<nav className={className} aria-label="Starter resources">
|
|
55
|
+
{primary ? (
|
|
56
|
+
<span className="resource-link-item">
|
|
57
|
+
<a className="resource-link-primary" href={primary.href}>
|
|
58
|
+
{primary.label}
|
|
59
|
+
</a>
|
|
60
|
+
</span>
|
|
61
|
+
) : null}
|
|
62
|
+
<span className="resource-link-item">
|
|
63
|
+
{primary ? <ResourceSeparator /> : null}
|
|
64
|
+
<a href="https://farmjs.dev">
|
|
65
|
+
<DocsIcon />
|
|
66
|
+
<span>Docs</span>
|
|
67
|
+
</a>
|
|
68
|
+
</span>
|
|
69
|
+
<span className="resource-link-item">
|
|
70
|
+
<ResourceSeparator />
|
|
71
|
+
<a href="https://github.com/farming-labs/farm.js">
|
|
72
|
+
<GitHubIcon />
|
|
73
|
+
<span>GitHub</span>
|
|
74
|
+
</a>
|
|
75
|
+
</span>
|
|
76
|
+
</nav>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# FARMJS Better Auth Starter
|
|
2
|
+
|
|
3
|
+
A standalone authentication starter built with FARMJS, Better Auth, React, and Neon Postgres.
|
|
4
|
+
|
|
5
|
+
This project was generated from the Better Auth template included with `@farm.js/create-app`.
|
|
6
|
+
|
|
7
|
+
## Included
|
|
8
|
+
|
|
9
|
+
- email and password sign-up and sign-in
|
|
10
|
+
- Better Auth session cookies
|
|
11
|
+
- a server-middleware-protected `/dashboard`
|
|
12
|
+
- pooled Postgres persistence and automatic Better Auth migrations
|
|
13
|
+
- a dark-first, one-page welcome screen set in Geist Sans and Geist Mono
|
|
14
|
+
- pending, error, unauthorized, loading, and not-found states
|
|
15
|
+
- responsive starter UI
|
|
16
|
+
- exact FARMJS beta dependencies for reproducible installs
|
|
17
|
+
|
|
18
|
+
## Quick start
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm create @farm.js/app@beta my-app --template better-auth --typescript
|
|
22
|
+
cd my-app
|
|
23
|
+
cp .env.example .env.local
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Generate a secret and place it in `BETTER_AUTH_SECRET`:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
openssl rand -base64 32
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then install and start the app:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pnpm install
|
|
36
|
+
pnpm dev
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Open [http://localhost:3000](http://localhost:3000), create an account, and continue to the protected dashboard.
|
|
40
|
+
|
|
41
|
+
## How it is wired
|
|
42
|
+
|
|
43
|
+
- [`src/lib/auth.ts`](./src/lib/auth.ts) creates the Better Auth instance, configures the pooled
|
|
44
|
+
Postgres connection, and runs programmatic migrations.
|
|
45
|
+
- [`farm.config.ts`](./farm.config.ts) mounts that instance through `@farm.js/better-auth`.
|
|
46
|
+
- [`src/lib/auth-client.ts`](./src/lib/auth-client.ts) exposes the browser client.
|
|
47
|
+
- [`src/lib/session.ts`](./src/lib/session.ts) resolves the current request session on the server.
|
|
48
|
+
- [`src/app/dashboard/middleware.ts`](./src/app/dashboard/middleware.ts) redirects unauthenticated requests before the dashboard route runs.
|
|
49
|
+
- [`src/app/dashboard/page.tsx`](./src/app/dashboard/page.tsx) reads the already-authorized session and renders the account UI.
|
|
50
|
+
|
|
51
|
+
FARMJS owns the catch-all integration route, so the starter does not need a manual API route:
|
|
52
|
+
|
|
53
|
+
```text
|
|
54
|
+
/api/auth/[...auth]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Environment
|
|
58
|
+
|
|
59
|
+
| Variable | Purpose |
|
|
60
|
+
| -------------------- | ----------------------------------------- |
|
|
61
|
+
| `BETTER_AUTH_URL` | Public origin used by Better Auth |
|
|
62
|
+
| `BETTER_AUTH_SECRET` | Secret used to sign and encrypt auth data |
|
|
63
|
+
| `DATABASE_URL` | Pooled Postgres connection string |
|
|
64
|
+
|
|
65
|
+
Never commit `.env.local` or expose the database connection string.
|
|
66
|
+
|
|
67
|
+
## Deployment note
|
|
68
|
+
|
|
69
|
+
The starter uses `pg` with a small connection pool suitable for a pooled Neon endpoint. Add
|
|
70
|
+
`DATABASE_URL`, `BETTER_AUTH_SECRET`, and the production `BETTER_AUTH_URL` to your deployment
|
|
71
|
+
environment before building.
|
|
72
|
+
|
|
73
|
+
The FARMJS deployment target is configured in [`farm.config.ts`](./farm.config.ts) for Vercel.
|
|
74
|
+
|
|
75
|
+
## Commands
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pnpm dev # start the development server
|
|
79
|
+
pnpm type-check # run TypeScript checks
|
|
80
|
+
pnpm build # create the production build
|
|
81
|
+
pnpm check # type-check and build
|
|
82
|
+
pnpm run deploy -- --prod # deploy the prebuilt Farm output to Vercel
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { defineConfig } from "@farm.js/core";
|
|
2
|
+
import { betterAuth } from "@farm.js/better-auth";
|
|
3
|
+
import { auth } from "./src/lib/auth.ts";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
srcDir: "src",
|
|
7
|
+
theme: {
|
|
8
|
+
default: "dark",
|
|
9
|
+
},
|
|
10
|
+
experimental: {
|
|
11
|
+
serverComponents: true,
|
|
12
|
+
},
|
|
13
|
+
vite: {
|
|
14
|
+
server: {
|
|
15
|
+
port: 3000,
|
|
16
|
+
strictPort: true,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
integrations: {
|
|
20
|
+
auth: betterAuth({
|
|
21
|
+
instance: auth,
|
|
22
|
+
log(event) {
|
|
23
|
+
if (process.env.NODE_ENV !== "production") {
|
|
24
|
+
console.log("[better-auth]", event.phase, event.route?.path ?? "request");
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
},
|
|
29
|
+
deploy: {
|
|
30
|
+
target: "vercel",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "farmjs-better-auth-starter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "farm dev",
|
|
7
|
+
"build": "farm build",
|
|
8
|
+
"check": "pnpm type-check && pnpm build",
|
|
9
|
+
"deploy": "farm deploy",
|
|
10
|
+
"start": "farm start",
|
|
11
|
+
"type-check": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@farm.js/better-auth": "0.1.0-beta.23",
|
|
15
|
+
"@farm.js/core": "0.1.0-beta.23",
|
|
16
|
+
"@fontsource-variable/geist": "5.3.0",
|
|
17
|
+
"@fontsource-variable/geist-mono": "5.3.0",
|
|
18
|
+
"better-auth": "1.6.25",
|
|
19
|
+
"better-call": "1.3.7",
|
|
20
|
+
"pg": "8.22.0",
|
|
21
|
+
"react": "^19.0.0",
|
|
22
|
+
"react-dom": "^19.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@farm.js/cli": "0.1.0-beta.23",
|
|
26
|
+
"@types/node": "20.19.9",
|
|
27
|
+
"@types/pg": "8.20.0",
|
|
28
|
+
"@types/react": "^19.0.0",
|
|
29
|
+
"@types/react-dom": "^19.0.0",
|
|
30
|
+
"tailwindcss": "^4.0.0",
|
|
31
|
+
"typescript": "^5.3.3"
|
|
32
|
+
},
|
|
33
|
+
"packageManager": "pnpm@11.18.0"
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { auth } from "../../lib/auth";
|
|
2
|
+
|
|
3
|
+
export const config = {
|
|
4
|
+
runtime: "nodejs" as const,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export async function middleware(request: Request) {
|
|
8
|
+
const session = await auth.api.getSession({
|
|
9
|
+
headers: request.headers,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
if (!session?.user) {
|
|
13
|
+
return Response.redirect(new URL("/sign-in", request.url), 307);
|
|
14
|
+
}
|
|
15
|
+
}
|