create-ej-app 16.0.0 → 17.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-ej-app
|
|
2
2
|
|
|
3
|
-
A no-bullshit CLI to bootstrap a production-ready **fucking beast** of a app with Next.js, Better Auth, Drizzle ORM, Elysia, Workflow, shadcn/ui, React Email, and deployment wired up so you don’t screw your shit up in production.
|
|
3
|
+
A no-bullshit CLI to bootstrap a production-ready **fucking beast** of a app with Next.js, Better Auth, Drizzle ORM, Elysia, Workflow, shadcn/ui, React Email, OTEL, and deployment wired up so you don’t screw your shit up in production.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
bunx create-ej-app@latest
|
|
@@ -11,6 +11,7 @@ bunx create-ej-app@latest
|
|
|
11
11
|
## ✨ Features (aka the good shit)
|
|
12
12
|
|
|
13
13
|
- **Next.js 16** – Powered by React 19, Server Components, App Router. Fast as hell.
|
|
14
|
+
|
|
14
15
|
- **Better Auth** – Auth that doesn’t suck:
|
|
15
16
|
- Email/Password
|
|
16
17
|
- Magic link (because passwords are annoying as fuck)
|
|
@@ -19,15 +20,31 @@ bunx create-ej-app@latest
|
|
|
19
20
|
- Admin powers
|
|
20
21
|
|
|
21
22
|
- **Drizzle ORM** – Type-safe SQL over PostgreSQL without the usual ORM garbage.
|
|
23
|
+
|
|
22
24
|
- **Elysia** – Built for Bun. Minimal, fast, no nonsense.
|
|
25
|
+
|
|
23
26
|
- **Workflow** – Durable jobs so your background tasks don’t randomly die like weak shit.
|
|
27
|
+
|
|
24
28
|
- **React Email** – Build transactional emails using React components instead of ugly HTML nightmares.
|
|
29
|
+
|
|
30
|
+
- **OTEL (OpenTelemetry)** – Production-grade observability:
|
|
31
|
+
- Distributed tracing
|
|
32
|
+
- Metrics
|
|
33
|
+
- Structured logs
|
|
34
|
+
- Works with GCP, Datadog, Honeycomb, Grafana, etc.
|
|
35
|
+
|
|
25
36
|
- **shadcn/ui** – Clean, accessible components built on Radix UI.
|
|
37
|
+
|
|
26
38
|
- **TailwindCSS 4** – Utility-first styling with zero CSS drama.
|
|
39
|
+
|
|
27
40
|
- **TypeScript** – Strict types so you don’t ship dumb bugs.
|
|
41
|
+
|
|
28
42
|
- **TanStack Query** – Server state that doesn’t implode.
|
|
43
|
+
|
|
29
44
|
- **Lucide React** – Icons that don’t look like 2009 garbage.
|
|
45
|
+
|
|
30
46
|
- **Docker + Terraform** – Ship this shit properly.
|
|
47
|
+
|
|
31
48
|
- Terraform setup for Google Cloud Platform so you don’t YOLO infra.
|
|
32
49
|
|
|
33
50
|
---
|
|
@@ -68,6 +85,7 @@ Boom. Done.
|
|
|
68
85
|
- **Database**: PostgreSQL + Drizzle
|
|
69
86
|
- **Auth**: Better Auth
|
|
70
87
|
- **Emails**: React Email (transactional + auth templates ready)
|
|
88
|
+
- **Observability**: OpenTelemetry
|
|
71
89
|
- **Background Jobs**: Workflow
|
|
72
90
|
- **UI**: shadcn/ui
|
|
73
91
|
- **Styling**: TailwindCSS
|
|
@@ -232,6 +250,7 @@ MIT. Do whatever the hell you want, just don’t blame us if you screw it up.
|
|
|
232
250
|
- [Workflow](https://useworkflow.dev)
|
|
233
251
|
- [shadcn/ui](https://ui.shadcn.com)
|
|
234
252
|
- [React Email](https://react.email)
|
|
253
|
+
- [Logging](https://loggingsucks.com)
|
|
235
254
|
|
|
236
255
|
---
|
|
237
256
|
|
package/package.json
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
"@elysiajs/eden": "1.4.8",
|
|
25
25
|
"@hookform/resolvers": "5.2.2",
|
|
26
26
|
"@next/third-parties": "16.1.6",
|
|
27
|
+
"@opentelemetry/api": "1.9.0",
|
|
28
|
+
"@opentelemetry/sdk-trace-base": "2.5.1",
|
|
27
29
|
"@paralleldrive/cuid2": "3.3.0",
|
|
28
30
|
"@radix-ui/react-label": "2.1.8",
|
|
29
31
|
"@radix-ui/react-separator": "1.1.8",
|
|
@@ -32,12 +34,13 @@
|
|
|
32
34
|
"@react-email/render": "2.0.4",
|
|
33
35
|
"@t3-oss/env-nextjs": "0.13.10",
|
|
34
36
|
"@tanstack/react-query": "5.90.21",
|
|
37
|
+
"@vercel/otel": "2.1.1",
|
|
35
38
|
"better-auth": "1.4.18",
|
|
36
39
|
"class-variance-authority": "0.7.1",
|
|
37
40
|
"clsx": "2.1.1",
|
|
38
41
|
"drizzle-orm": "0.45.1",
|
|
39
42
|
"elysia": "1.4.25",
|
|
40
|
-
"lucide-react": "0.
|
|
43
|
+
"lucide-react": "0.564.0",
|
|
41
44
|
"next": "16.1.6",
|
|
42
45
|
"next-safe-action": "8.0.11",
|
|
43
46
|
"next-themes": "0.4.6",
|
|
@@ -48,9 +51,9 @@
|
|
|
48
51
|
"react-dom": "19.2.4",
|
|
49
52
|
"react-hook-form": "7.71.1",
|
|
50
53
|
"sonner": "2.0.7",
|
|
51
|
-
"tailwind-merge": "3.4.
|
|
54
|
+
"tailwind-merge": "3.4.1",
|
|
52
55
|
"tw-animate-css": "1.4.0",
|
|
53
|
-
"workflow": "4.1.0-beta.
|
|
56
|
+
"workflow": "4.1.0-beta.57",
|
|
54
57
|
"zod": "4.3.6"
|
|
55
58
|
},
|
|
56
59
|
"devDependencies": {
|
|
@@ -58,7 +61,7 @@
|
|
|
58
61
|
"@react-email/preview-server": "5.2.8",
|
|
59
62
|
"@tailwindcss/postcss": "4.1.18",
|
|
60
63
|
"@types/bun": "1.3.9",
|
|
61
|
-
"@types/nodemailer": "7.0.
|
|
64
|
+
"@types/nodemailer": "7.0.10",
|
|
62
65
|
"@types/react": "19.2.14",
|
|
63
66
|
"@types/react-dom": "19.2.3",
|
|
64
67
|
"babel-plugin-react-compiler": "1.0.0",
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
1
4
|
import Link from 'next/link'
|
|
2
|
-
import {
|
|
5
|
+
import { usePathname } from 'next/navigation'
|
|
3
6
|
|
|
4
7
|
export default function NotFound() {
|
|
8
|
+
const pathname = usePathname()
|
|
9
|
+
const homeRoute = pathname?.startsWith('/admin') ? '/admin' : '/'
|
|
10
|
+
|
|
5
11
|
return (
|
|
6
12
|
<main className="bg-background text-foreground flex min-h-screen flex-col">
|
|
7
13
|
<section className="flex min-h-[60vh] flex-col items-center justify-center px-4">
|
|
@@ -12,7 +18,7 @@ export default function NotFound() {
|
|
|
12
18
|
The page you're looking for doesn't exist.
|
|
13
19
|
</p>
|
|
14
20
|
<Button asChild>
|
|
15
|
-
<Link href=
|
|
21
|
+
<Link href={homeRoute}>Return to Home</Link>
|
|
16
22
|
</Button>
|
|
17
23
|
</div>
|
|
18
24
|
</section>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SpanStatusCode } from '@opentelemetry/api'
|
|
2
|
+
import {
|
|
3
|
+
ConsoleSpanExporter,
|
|
4
|
+
ReadableSpan,
|
|
5
|
+
SimpleSpanProcessor
|
|
6
|
+
} from '@opentelemetry/sdk-trace-base'
|
|
7
|
+
import { registerOTel } from '@vercel/otel'
|
|
8
|
+
|
|
9
|
+
class SmartSamplingSpanProcessor extends SimpleSpanProcessor {
|
|
10
|
+
onEnd(span: ReadableSpan): void {
|
|
11
|
+
const isError = span.status.code === SpanStatusCode.ERROR
|
|
12
|
+
const isSlow = span.duration[0] * 1000 + span.duration[1] / 1e6 > 5000
|
|
13
|
+
const isLucky = Math.random() < 0 // Adjust this value to adjust the sampling rate for non-error and non-slow spans
|
|
14
|
+
|
|
15
|
+
if (isError || isSlow || isLucky) {
|
|
16
|
+
super.onEnd(span)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function register() {
|
|
22
|
+
registerOTel({
|
|
23
|
+
serviceName: 'tdc-ai',
|
|
24
|
+
spanProcessors: [new SmartSamplingSpanProcessor(new ConsoleSpanExporter())]
|
|
25
|
+
})
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { SpanStatusCode, trace } from '@opentelemetry/api'
|
|
2
|
+
|
|
3
|
+
function getSpan() {
|
|
4
|
+
const span = trace.getActiveSpan()
|
|
5
|
+
return span
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function recordWideEvent(attributes: Record<string, any>) {
|
|
9
|
+
const span = getSpan()
|
|
10
|
+
if (!span) return
|
|
11
|
+
|
|
12
|
+
span.setAttributes(attributes)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function recordError(err: unknown) {
|
|
16
|
+
const span = getSpan()
|
|
17
|
+
if (!span) return
|
|
18
|
+
|
|
19
|
+
span.setStatus({
|
|
20
|
+
code: SpanStatusCode.ERROR,
|
|
21
|
+
message: err instanceof Error ? err.message : String(err)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
span.recordException(err instanceof Error ? err : String(err))
|
|
25
|
+
|
|
26
|
+
return span.spanContext().traceId
|
|
27
|
+
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import { createSafeActionClient } from 'next-safe-action'
|
|
2
2
|
import { getAuthSession } from './auth'
|
|
3
|
+
import { recordWideEvent } from './otel'
|
|
3
4
|
|
|
4
5
|
export const actionClient = createSafeActionClient().use(
|
|
5
6
|
async ({ next, ctx }) => {
|
|
6
7
|
const authSession = await getAuthSession()
|
|
7
8
|
|
|
9
|
+
if (authSession)
|
|
10
|
+
recordWideEvent({
|
|
11
|
+
'user.id': authSession.user.id,
|
|
12
|
+
'user.admin': authSession.isAdmin,
|
|
13
|
+
'session.id': authSession.session.id
|
|
14
|
+
})
|
|
15
|
+
|
|
8
16
|
return next({
|
|
9
17
|
ctx: {
|
|
10
18
|
...ctx,
|