project-mcp-server 2.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.
Files changed (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +335 -0
  3. package/dist/config.d.ts +15 -0
  4. package/dist/config.js +38 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/index.d.ts +2 -0
  7. package/dist/index.js +28 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/scanner/actions.d.ts +9 -0
  10. package/dist/scanner/actions.js +77 -0
  11. package/dist/scanner/actions.js.map +1 -0
  12. package/dist/scanner/cache.d.ts +4 -0
  13. package/dist/scanner/cache.js +34 -0
  14. package/dist/scanner/cache.js.map +1 -0
  15. package/dist/scanner/components.d.ts +17 -0
  16. package/dist/scanner/components.js +74 -0
  17. package/dist/scanner/components.js.map +1 -0
  18. package/dist/scanner/prisma.d.ts +17 -0
  19. package/dist/scanner/prisma.js +75 -0
  20. package/dist/scanner/prisma.js.map +1 -0
  21. package/dist/scanner/routes.d.ts +11 -0
  22. package/dist/scanner/routes.js +98 -0
  23. package/dist/scanner/routes.js.map +1 -0
  24. package/dist/setup.d.ts +10 -0
  25. package/dist/setup.js +289 -0
  26. package/dist/setup.js.map +1 -0
  27. package/dist/tools/env/index.d.ts +2 -0
  28. package/dist/tools/env/index.js +152 -0
  29. package/dist/tools/env/index.js.map +1 -0
  30. package/dist/tools/generate/index.d.ts +2 -0
  31. package/dist/tools/generate/index.js +320 -0
  32. package/dist/tools/generate/index.js.map +1 -0
  33. package/dist/tools/project/index.d.ts +2 -0
  34. package/dist/tools/project/index.js +193 -0
  35. package/dist/tools/project/index.js.map +1 -0
  36. package/package.json +35 -0
  37. package/skills/commit.md +109 -0
  38. package/skills/infra.md +218 -0
  39. package/skills/nextauth.md +256 -0
  40. package/skills/nextjs.md +262 -0
  41. package/skills/prisma.md +281 -0
  42. package/skills/project-intelligence.SKILL.md +141 -0
  43. package/skills/security.md +353 -0
  44. package/skills/shadcn.md +299 -0
  45. package/skills/testing.md +188 -0
  46. package/skills/zod.md +253 -0
@@ -0,0 +1,256 @@
1
+ ---
2
+ name: nextauth
3
+ description: "Activar cuando se configura o modifica autenticacion, se protegen rutas o layouts, o se trabaja con sesiones y roles"
4
+ license: MIT
5
+ metadata:
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ # Skill: NextAuth.js v5 (beta 30)
10
+
11
+ ## Cuándo cargar esta skill
12
+ - Configurar o modificar autenticación
13
+ - Proteger rutas o layouts
14
+ - Acceder a la sesión del usuario
15
+ - Implementar middleware de auth
16
+ - Manejar roles y permisos en rutas
17
+
18
+ ---
19
+
20
+ ## Configuración base (v5 beta 30)
21
+
22
+ ```typescript
23
+ // lib/auth/config.ts
24
+ import type { NextAuthConfig } from "next-auth"
25
+ import Credentials from "next-auth/providers/credentials"
26
+ import bcrypt from "bcrypt"
27
+ import { prismaPostgres } from "@/lib/db/postgres"
28
+ import { LoginSchema } from "@/types/schemas"
29
+
30
+ export const authConfig: NextAuthConfig = {
31
+ providers: [
32
+ Credentials({
33
+ async authorize(credentials) {
34
+ const parsed = LoginSchema.safeParse(credentials)
35
+ if (!parsed.success) return null
36
+
37
+ const user = await prismaPostgres.user.findUnique({
38
+ where: { email: parsed.data.email },
39
+ select: { id: true, email: true, name: true, password: true, roleId: true }
40
+ })
41
+ if (!user) return null
42
+
43
+ const valid = await bcrypt.compare(parsed.data.password, user.password)
44
+ if (!valid) return null
45
+
46
+ // No retornar el password al cliente
47
+ const { password: _, ...safeUser } = user
48
+ return safeUser
49
+ }
50
+ })
51
+ ],
52
+
53
+ callbacks: {
54
+ // Extender el token JWT con datos del usuario
55
+ async jwt({ token, user }) {
56
+ if (user) {
57
+ token.id = user.id
58
+ token.roleId = user.roleId
59
+ }
60
+ return token
61
+ },
62
+ // Extender la sesión con datos del token
63
+ async session({ session, token }) {
64
+ if (token && session.user) {
65
+ session.user.id = token.id as string
66
+ session.user.roleId = token.roleId as string
67
+ }
68
+ return session
69
+ }
70
+ },
71
+
72
+ pages: {
73
+ signIn: "/login",
74
+ error: "/login" // Redirigir errores al login
75
+ },
76
+
77
+ session: { strategy: "jwt" }
78
+ }
79
+ ```
80
+
81
+ ```typescript
82
+ // lib/auth/index.ts — exportar handlers y helper auth()
83
+ import NextAuth from "next-auth"
84
+ import { authConfig } from "./config"
85
+
86
+ export const { handlers, auth, signIn, signOut } = NextAuth(authConfig)
87
+ ```
88
+
89
+ ```typescript
90
+ // app/api/auth/[...nextauth]/route.ts
91
+ import { handlers } from "@/lib/auth"
92
+ export const { GET, POST } = handlers
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Acceder a la sesión
98
+
99
+ ### En RSC o Server Actions
100
+ ```typescript
101
+ import { auth } from "@/lib/auth"
102
+
103
+ // En un RSC
104
+ export default async function Page() {
105
+ const session = await auth()
106
+ if (!session) redirect("/login")
107
+
108
+ return <div>Hola {session.user.name}</div>
109
+ }
110
+
111
+ // En una Server Action
112
+ export async function deletePost(id: string) {
113
+ "use server"
114
+ const session = await auth()
115
+ if (!session) throw new Error("Unauthorized")
116
+ if (session.user.roleId !== "admin") throw new Error("Forbidden")
117
+ // ...
118
+ }
119
+ ```
120
+
121
+ ### En Client Components
122
+ ```typescript
123
+ "use client"
124
+ import { useSession } from "next-auth/react"
125
+
126
+ export function UserMenu() {
127
+ const { data: session, status } = useSession()
128
+
129
+ if (status === "loading") return <Skeleton />
130
+ if (!session) return <LoginButton />
131
+
132
+ return <div>{session.user.name}</div>
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Middleware — protección de rutas
139
+
140
+ ```typescript
141
+ // middleware.ts (raíz del proyecto)
142
+ import { auth } from "@/lib/auth"
143
+ import { NextResponse } from "next/server"
144
+
145
+ export default auth((req) => {
146
+ const isLoggedIn = !!req.auth
147
+ const isOnDashboard = req.nextUrl.pathname.startsWith("/dashboard")
148
+ const isOnAdmin = req.nextUrl.pathname.startsWith("/admin")
149
+ const isOnLogin = req.nextUrl.pathname === "/login"
150
+
151
+ // Si intenta entrar al dashboard sin sesión → login
152
+ if (isOnDashboard && !isLoggedIn) {
153
+ return NextResponse.redirect(new URL("/login", req.url))
154
+ }
155
+
156
+ // Si intenta admin sin rol admin → 403
157
+ if (isOnAdmin) {
158
+ if (!isLoggedIn) return NextResponse.redirect(new URL("/login", req.url))
159
+ if (req.auth?.user?.roleId !== "admin") {
160
+ return NextResponse.redirect(new URL("/403", req.url))
161
+ }
162
+ }
163
+
164
+ // Si ya está logueado e intenta ir al login → dashboard
165
+ if (isOnLogin && isLoggedIn) {
166
+ return NextResponse.redirect(new URL("/dashboard", req.url))
167
+ }
168
+ })
169
+
170
+ export const config = {
171
+ matcher: ["/dashboard/:path*", "/admin/:path*", "/login"]
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Extender tipos de sesión (TypeScript)
178
+
179
+ ```typescript
180
+ // types/next-auth.d.ts
181
+ import "next-auth"
182
+ import "next-auth/jwt"
183
+
184
+ declare module "next-auth" {
185
+ interface Session {
186
+ user: {
187
+ id: string
188
+ name: string
189
+ email: string
190
+ roleId: string
191
+ }
192
+ }
193
+
194
+ interface User {
195
+ roleId: string
196
+ }
197
+ }
198
+
199
+ declare module "next-auth/jwt" {
200
+ interface JWT {
201
+ id: string
202
+ roleId: string
203
+ }
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Login y logout desde Client Components
210
+
211
+ ```typescript
212
+ "use client"
213
+ import { signIn, signOut } from "next-auth/react"
214
+
215
+ // Login con redirect
216
+ <button onClick={() => signIn("credentials", { redirect: true, callbackUrl: "/dashboard" })}>
217
+ Iniciar sesión
218
+ </button>
219
+
220
+ // Logout
221
+ <button onClick={() => signOut({ callbackUrl: "/login" })}>
222
+ Cerrar sesión
223
+ </button>
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Helper de permisos (RBAC)
229
+
230
+ ```typescript
231
+ // lib/auth/permissions.ts
232
+ import { auth } from "@/lib/auth"
233
+ import { prismaPostgres } from "@/lib/db/postgres"
234
+
235
+ export async function hasPermission(action: string): Promise<boolean> {
236
+ const session = await auth()
237
+ if (!session?.user?.roleId) return false
238
+
239
+ const permission = await prismaPostgres.permission.findFirst({
240
+ where: {
241
+ roleId: session.user.roleId,
242
+ action: { in: [action, `${action.split(":")[0]}:*`] }
243
+ }
244
+ })
245
+
246
+ return !!permission
247
+ }
248
+
249
+ // Uso en Server Action
250
+ export async function createPost(data: unknown) {
251
+ "use server"
252
+ const allowed = await hasPermission("posts:create")
253
+ if (!allowed) throw new Error("Forbidden")
254
+ // ...
255
+ }
256
+ ```
@@ -0,0 +1,262 @@
1
+ ---
2
+ name: nextjs
3
+ description: "Activar cuando se crean o modifican paginas, layouts, componentes React, Server Actions o Route Handlers en Next.js"
4
+ license: MIT
5
+ metadata:
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ # Skill: Next.js 16 + React 19
10
+
11
+ ## Cuándo cargar esta skill
12
+ - Crear o modificar páginas, layouts, o componentes React
13
+ - Implementar Server Actions o Route Handlers
14
+ - Trabajar con fetching de datos en RSC
15
+ - Configurar metadata, loading, error boundaries
16
+ - Cualquier tarea que involucre el App Router
17
+
18
+ ---
19
+
20
+ ## Decisión RSC vs Client Component
21
+
22
+ ```
23
+ ¿Necesita hooks (useState, useEffect, useRef)? → "use client"
24
+ ¿Maneja eventos del DOM (onClick, onChange)? → "use client"
25
+ ¿Usa Context de React? → "use client"
26
+ ¿Solo muestra datos / layout / estructura? → RSC (default)
27
+ ¿Hace fetch de datos desde DB o API externa? → RSC
28
+ ¿Necesita acceso a cookies/headers en runtime? → RSC con cookies()/headers()
29
+ ```
30
+
31
+ Regla práctica: empezar como RSC, agregar `"use client"` solo cuando el compilador
32
+ o la lógica lo exija. Los Client Components deben ser hojas del árbol, no raíces.
33
+
34
+ ---
35
+
36
+ ## Patrones de data fetching (Next.js 16)
37
+
38
+ ### En RSC — fetch directo o query de DB
39
+ ```typescript
40
+ // app/(dashboard)/users/page.tsx
41
+ import { prismaPostgres } from "@/lib/db/postgres"
42
+
43
+ export default async function UsersPage() {
44
+ // Directo — no hace falta useEffect ni SWR
45
+ const users = await prismaPostgres.user.findMany({
46
+ where: { active: true },
47
+ select: { id: true, name: true, email: true }
48
+ })
49
+
50
+ return <UserList users={users} />
51
+ }
52
+ ```
53
+
54
+ ### Cache en RSC — usar `use cache` (Next 16, NO unstable_cache)
55
+ ```typescript
56
+ import { unstable_cache as useCache } from "next/cache"
57
+
58
+ // Revalidar cada hora, tag para invalidación manual
59
+ const getProducts = useCache(
60
+ async () => prismaPostgres.product.findMany(),
61
+ ["products"],
62
+ { revalidate: 3600, tags: ["products"] }
63
+ )
64
+
65
+ // Invalidar desde Server Action
66
+ import { revalidateTag } from "next/cache"
67
+ revalidateTag("products")
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Server Actions
73
+
74
+ ```typescript
75
+ // server/actions/users.ts
76
+ "use server"
77
+
78
+ import { z } from "zod"
79
+ import { prismaPostgres } from "@/lib/db/postgres"
80
+ import { auth } from "@/lib/auth"
81
+
82
+ const CreateUserSchema = z.object({
83
+ name: z.string().min(2),
84
+ email: z.string().email(),
85
+ role: z.enum(["admin", "user"])
86
+ })
87
+
88
+ export async function createUser(formData: FormData) {
89
+ // 1. Autenticar
90
+ const session = await auth()
91
+ if (!session) throw new Error("Unauthorized")
92
+
93
+ // 2. Validar con Zod
94
+ const parsed = CreateUserSchema.safeParse({
95
+ name: formData.get("name"),
96
+ email: formData.get("email"),
97
+ role: formData.get("role")
98
+ })
99
+ if (!parsed.success) {
100
+ return { error: parsed.error.flatten().fieldErrors }
101
+ }
102
+
103
+ // 3. Ejecutar
104
+ const user = await prismaPostgres.user.create({ data: parsed.data })
105
+
106
+ // 4. Revalidar cache si aplica
107
+ revalidateTag("users")
108
+
109
+ return { success: true, user }
110
+ }
111
+ ```
112
+
113
+ ### Consumir Server Action desde Client Component
114
+ ```typescript
115
+ "use client"
116
+
117
+ import { useActionState } from "react" // React 19 — reemplaza useFormState
118
+ import { createUser } from "@/server/actions/users"
119
+
120
+ export function CreateUserForm() {
121
+ const [state, action, isPending] = useActionState(createUser, null)
122
+
123
+ return (
124
+ <form action={action}>
125
+ <input name="name" />
126
+ <input name="email" type="email" />
127
+ <button type="submit" disabled={isPending}>
128
+ {isPending ? "Creando..." : "Crear usuario"}
129
+ </button>
130
+ {state?.error && <p>{JSON.stringify(state.error)}</p>}
131
+ </form>
132
+ )
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Route Handlers (API)
139
+
140
+ ```typescript
141
+ // app/api/users/route.ts
142
+ import { NextRequest, NextResponse } from "next/server"
143
+ import { auth } from "@/lib/auth"
144
+ import { prismaPostgres } from "@/lib/db/postgres"
145
+
146
+ export async function GET(request: NextRequest) {
147
+ const session = await auth()
148
+ if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
149
+
150
+ const { searchParams } = new URL(request.url)
151
+ const page = Number(searchParams.get("page") ?? 1)
152
+
153
+ const users = await prismaPostgres.user.findMany({
154
+ skip: (page - 1) * 20,
155
+ take: 20
156
+ })
157
+
158
+ return NextResponse.json({ users, page })
159
+ }
160
+
161
+ export async function POST(request: NextRequest) {
162
+ const body = await request.json()
163
+ // validar con Zod, crear, retornar
164
+ }
165
+ ```
166
+
167
+ ---
168
+
169
+ ## Layouts y metadata
170
+
171
+ ```typescript
172
+ // app/(dashboard)/layout.tsx
173
+ import { auth } from "@/lib/auth"
174
+ import { redirect } from "next/navigation"
175
+
176
+ export default async function DashboardLayout({
177
+ children
178
+ }: {
179
+ children: React.ReactNode
180
+ }) {
181
+ const session = await auth()
182
+ if (!session) redirect("/login")
183
+
184
+ return (
185
+ <div className="flex min-h-screen">
186
+ <aside>...</aside>
187
+ <main className="flex-1">{children}</main>
188
+ </div>
189
+ )
190
+ }
191
+
192
+ // Metadata dinámica
193
+ export async function generateMetadata({ params }: { params: { id: string } }) {
194
+ const user = await prismaPostgres.user.findUnique({ where: { id: params.id } })
195
+ return { title: user?.name ?? "Usuario" }
196
+ }
197
+ ```
198
+
199
+ ---
200
+
201
+ ## React 19 — novedades a usar
202
+
203
+ ```typescript
204
+ // use() — leer promesas y context en render
205
+ import { use } from "react"
206
+
207
+ function UserDetails({ promise }: { promise: Promise<User> }) {
208
+ const user = use(promise) // Suspense-compatible
209
+ return <div>{user.name}</div>
210
+ }
211
+
212
+ // useOptimistic — updates optimistas
213
+ import { useOptimistic } from "react"
214
+
215
+ function LikeButton({ post }) {
216
+ const [optimisticLikes, addOptimisticLike] = useOptimistic(
217
+ post.likes,
218
+ (current, increment: number) => current + increment
219
+ )
220
+
221
+ async function handleLike() {
222
+ addOptimisticLike(1)
223
+ await likePost(post.id)
224
+ }
225
+ }
226
+
227
+ // useActionState — reemplaza useFormState de React DOM
228
+ import { useActionState } from "react"
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Anti-patrones a evitar
234
+
235
+ ```typescript
236
+ // ❌ No mezclar lógica de servidor en Client Components
237
+ "use client"
238
+ const data = await fetch("/api/users") // Error: await en cliente sin Suspense
239
+
240
+ // ❌ No usar cookies()/headers() fuera de RSC o Route Handlers
241
+ // (en Client Components no están disponibles)
242
+
243
+ // ❌ No importar código de servidor en Client Components
244
+ import { prismaPostgres } from "@/lib/db/postgres" // Expone DB al cliente
245
+
246
+ // ✓ Pasar datos como props desde RSC padre
247
+ export default async function Page() {
248
+ const data = await getData()
249
+ return <ClientComponent initialData={data} />
250
+ }
251
+ ```
252
+
253
+ ---
254
+
255
+ ## Comandos frecuentes
256
+
257
+ ```bash
258
+ pnpm dev # dev con Turbopack
259
+ pnpm build # build de producción
260
+ pnpm lint # ESLint 9
261
+ pnpm type-check # tsc --noEmit
262
+ ```