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,188 @@
1
+ ---
2
+ name: testing
3
+ description: "Activar cuando se escriben tests unitarios o de integracion, o se testean Server Actions, Route Handlers o componentes React"
4
+ license: MIT
5
+ metadata:
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ # Skill: Testing
10
+
11
+ ## Cuándo cargar esta skill
12
+ - Escribir tests unitarios o de integración
13
+ - Testear Server Actions o Route Handlers
14
+ - Testear componentes React
15
+ - Correr o debuggear la suite de tests
16
+
17
+ ---
18
+
19
+ ## Setup recomendado (si no está instalado)
20
+
21
+ ```bash
22
+ # Vitest para unit/integration (más rápido que Jest, nativo ESM)
23
+ pnpm add -D vitest @vitejs/plugin-react jsdom @testing-library/react @testing-library/user-event
24
+
25
+ # O Jest si ya está configurado en el proyecto
26
+ pnpm add -D jest @types/jest jest-environment-jsdom ts-jest
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Tests de Server Actions
32
+
33
+ ```typescript
34
+ // server/actions/__tests__/users.test.ts
35
+ import { describe, it, expect, vi, beforeEach } from "vitest"
36
+ import { createUser } from "../users"
37
+
38
+ // Mock de Prisma — nunca tocar la DB real en tests
39
+ vi.mock("@/lib/db/postgres", () => ({
40
+ prismaPostgres: {
41
+ user: {
42
+ create: vi.fn(),
43
+ findUnique: vi.fn()
44
+ }
45
+ }
46
+ }))
47
+
48
+ // Mock de auth
49
+ vi.mock("@/lib/auth", () => ({
50
+ auth: vi.fn().mockResolvedValue({
51
+ user: { id: "user-1", email: "test@test.com", roleId: "admin" }
52
+ })
53
+ }))
54
+
55
+ import { prismaPostgres } from "@/lib/db/postgres"
56
+
57
+ describe("createUser", () => {
58
+ beforeEach(() => vi.clearAllMocks())
59
+
60
+ it("crea un usuario con datos válidos", async () => {
61
+ const mockUser = { id: "new-id", name: "Ana", email: "ana@test.com" }
62
+ vi.mocked(prismaPostgres.user.create).mockResolvedValue(mockUser as any)
63
+
64
+ const formData = new FormData()
65
+ formData.set("name", "Ana")
66
+ formData.set("email", "ana@test.com")
67
+ formData.set("roleId", "cluid123")
68
+
69
+ const result = await createUser(null, formData)
70
+
71
+ expect(result.success).toBe(true)
72
+ expect(prismaPostgres.user.create).toHaveBeenCalledWith({
73
+ data: { name: "Ana", email: "ana@test.com", roleId: "cluid123" }
74
+ })
75
+ })
76
+
77
+ it("retorna error con email inválido", async () => {
78
+ const formData = new FormData()
79
+ formData.set("name", "Ana")
80
+ formData.set("email", "no-es-email")
81
+ formData.set("roleId", "cluid123")
82
+
83
+ const result = await createUser(null, formData)
84
+
85
+ expect(result.success).toBe(false)
86
+ if (!result.success) {
87
+ expect(result.error).toHaveProperty("email")
88
+ }
89
+ })
90
+
91
+ it("retorna error si no hay sesión", async () => {
92
+ const { auth } = await import("@/lib/auth")
93
+ vi.mocked(auth).mockResolvedValueOnce(null)
94
+
95
+ const formData = new FormData()
96
+ formData.set("name", "Ana")
97
+ formData.set("email", "ana@test.com")
98
+ formData.set("roleId", "cluid123")
99
+
100
+ const result = await createUser(null, formData)
101
+ expect(result.success).toBe(false)
102
+ })
103
+ })
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Tests de Schemas Zod
109
+
110
+ ```typescript
111
+ // types/schemas/__tests__/auth.test.ts
112
+ import { describe, it, expect } from "vitest"
113
+ import { LoginSchema, RegisterSchema } from "../auth"
114
+
115
+ describe("LoginSchema", () => {
116
+ it("acepta credenciales válidas", () => {
117
+ const result = LoginSchema.safeParse({
118
+ email: "user@example.com",
119
+ password: "password123"
120
+ })
121
+ expect(result.success).toBe(true)
122
+ })
123
+
124
+ it("rechaza email inválido", () => {
125
+ const result = LoginSchema.safeParse({
126
+ email: "no-es-email",
127
+ password: "password123"
128
+ })
129
+ expect(result.success).toBe(false)
130
+ expect(result.error?.flatten().fieldErrors.email).toBeDefined()
131
+ })
132
+ })
133
+
134
+ describe("RegisterSchema", () => {
135
+ it("rechaza contraseñas que no coinciden", () => {
136
+ const result = RegisterSchema.safeParse({
137
+ name: "Ana",
138
+ email: "ana@test.com",
139
+ password: "Password1",
140
+ confirmPassword: "Diferente1"
141
+ })
142
+ expect(result.success).toBe(false)
143
+ expect(result.error?.flatten().fieldErrors.confirmPassword).toBeDefined()
144
+ })
145
+ })
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Tests de componentes React
151
+
152
+ ```typescript
153
+ // components/users/__tests__/user-card.test.tsx
154
+ import { describe, it, expect } from "vitest"
155
+ import { render, screen } from "@testing-library/react"
156
+ import { UserCard } from "../user-card"
157
+
158
+ const mockUser = {
159
+ name: "Ana García",
160
+ email: "ana@test.com",
161
+ role: "admin"
162
+ }
163
+
164
+ describe("UserCard", () => {
165
+ it("muestra el nombre y email del usuario", () => {
166
+ render(<UserCard user={mockUser} />)
167
+
168
+ expect(screen.getByText("Ana García")).toBeInTheDocument()
169
+ expect(screen.getByText("ana@test.com")).toBeInTheDocument()
170
+ })
171
+
172
+ it("muestra el badge del rol", () => {
173
+ render(<UserCard user={mockUser} />)
174
+ expect(screen.getByText("admin")).toBeInTheDocument()
175
+ })
176
+ })
177
+ ```
178
+
179
+ ---
180
+
181
+ ## Comandos
182
+
183
+ ```bash
184
+ pnpm test # correr tests en watch mode
185
+ pnpm test --run # correr una sola vez (CI)
186
+ pnpm test --coverage # con reporte de cobertura
187
+ pnpm test src/server # solo un directorio
188
+ ```
package/skills/zod.md ADDED
@@ -0,0 +1,253 @@
1
+ ---
2
+ name: zod
3
+ description: "Activar cuando se crean o modifican schemas de validacion con Zod en Server Actions, Route Handlers o formularios"
4
+ license: MIT
5
+ metadata:
6
+ version: "1.0.0"
7
+ ---
8
+
9
+ # Skill: Zod 4 — validación y schemas
10
+
11
+ ## Cuándo cargar esta skill
12
+ - Crear o modificar schemas de validación
13
+ - Validar datos en Server Actions o Route Handlers
14
+ - Definir DTOs de entrada/salida
15
+ - Inferir tipos TypeScript desde schemas
16
+
17
+ ---
18
+
19
+ ## Patrones base de Zod 4
20
+
21
+ ```typescript
22
+ import { z } from "zod"
23
+
24
+ // Schema básico con mensajes en español
25
+ const UserSchema = z.object({
26
+ name: z.string().min(2, "Mínimo 2 caracteres").max(100, "Máximo 100 caracteres"),
27
+ email: z.string().email("Email inválido"),
28
+ age: z.number().int().min(18, "Debe ser mayor de 18").optional(),
29
+ role: z.enum(["admin", "editor", "viewer"], {
30
+ errorMap: () => ({ message: "Rol inválido" })
31
+ }),
32
+ metadata: z.record(z.string(), z.unknown()).optional()
33
+ })
34
+
35
+ // Inferir tipo TypeScript automáticamente
36
+ type User = z.infer<typeof UserSchema>
37
+ // → { name: string; email: string; age?: number; role: "admin"|"editor"|"viewer" }
38
+ ```
39
+
40
+ ---
41
+
42
+ ## Dónde ubicar los schemas
43
+
44
+ ```
45
+ src/
46
+ └── types/
47
+ └── schemas/
48
+ ├── auth.ts → LoginSchema, RegisterSchema
49
+ ├── users.ts → CreateUserSchema, UpdateUserSchema, UserFiltersSchema
50
+ ├── posts.ts → CreatePostSchema, UpdatePostSchema
51
+ └── index.ts → re-exporta todo
52
+ ```
53
+
54
+ ---
55
+
56
+ ## Validación en Server Actions
57
+
58
+ ```typescript
59
+ // server/actions/users.ts
60
+ "use server"
61
+
62
+ import { z } from "zod"
63
+ import { prismaPostgres } from "@/lib/db/postgres"
64
+ import { auth } from "@/lib/auth"
65
+
66
+ // Schema del DTO de entrada
67
+ const CreateUserSchema = z.object({
68
+ name: z.string().min(2).max(100),
69
+ email: z.string().email(),
70
+ roleId: z.string().cuid()
71
+ })
72
+
73
+ // Tipo de respuesta tipado
74
+ type ActionResult<T> =
75
+ | { success: true; data: T }
76
+ | { success: false; error: Record<string, string[]> | string }
77
+
78
+ export async function createUser(
79
+ _prevState: unknown,
80
+ formData: FormData
81
+ ): Promise<ActionResult<{ id: string }>> {
82
+ // 1. Autenticar
83
+ const session = await auth()
84
+ if (!session) return { success: false, error: "No autenticado" }
85
+
86
+ // 2. Parsear y validar
87
+ const parsed = CreateUserSchema.safeParse({
88
+ name: formData.get("name"),
89
+ email: formData.get("email"),
90
+ roleId: formData.get("roleId")
91
+ })
92
+
93
+ if (!parsed.success) {
94
+ return {
95
+ success: false,
96
+ error: parsed.error.flatten().fieldErrors
97
+ }
98
+ }
99
+
100
+ // 3. Ejecutar
101
+ const user = await prismaPostgres.user.create({ data: parsed.data })
102
+
103
+ return { success: true, data: { id: user.id } }
104
+ }
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Validación en Route Handlers
110
+
111
+ ```typescript
112
+ // app/api/users/route.ts
113
+ import { NextRequest, NextResponse } from "next/server"
114
+ import { z } from "zod"
115
+
116
+ const QueryParamsSchema = z.object({
117
+ page: z.coerce.number().int().min(1).default(1),
118
+ limit: z.coerce.number().int().min(1).max(100).default(20),
119
+ search: z.string().optional(),
120
+ role: z.enum(["admin", "editor", "viewer"]).optional()
121
+ })
122
+
123
+ const CreateUserBodySchema = z.object({
124
+ name: z.string().min(2),
125
+ email: z.string().email(),
126
+ password: z.string().min(8).regex(/[A-Z]/, "Debe tener al menos una mayúscula")
127
+ })
128
+
129
+ export async function GET(request: NextRequest) {
130
+ const { searchParams } = new URL(request.url)
131
+ const params = QueryParamsSchema.safeParse(Object.fromEntries(searchParams))
132
+
133
+ if (!params.success) {
134
+ return NextResponse.json(
135
+ { error: params.error.flatten().fieldErrors },
136
+ { status: 400 }
137
+ )
138
+ }
139
+
140
+ const { page, limit, search, role } = params.data
141
+ // ...
142
+ }
143
+
144
+ export async function POST(request: NextRequest) {
145
+ const body = await request.json().catch(() => null)
146
+ if (!body) {
147
+ return NextResponse.json({ error: "Body inválido" }, { status: 400 })
148
+ }
149
+
150
+ const parsed = CreateUserBodySchema.safeParse(body)
151
+ if (!parsed.success) {
152
+ return NextResponse.json(
153
+ { error: parsed.error.flatten().fieldErrors },
154
+ { status: 422 }
155
+ )
156
+ }
157
+
158
+ // ...
159
+ }
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Schemas de autenticación
165
+
166
+ ```typescript
167
+ // types/schemas/auth.ts
168
+ import { z } from "zod"
169
+
170
+ export const LoginSchema = z.object({
171
+ email: z.string().email("Email inválido"),
172
+ password: z.string().min(1, "La contraseña es requerida")
173
+ })
174
+
175
+ export const RegisterSchema = z.object({
176
+ name: z.string().min(2, "Mínimo 2 caracteres"),
177
+ email: z.string().email("Email inválido"),
178
+ password: z
179
+ .string()
180
+ .min(8, "Mínimo 8 caracteres")
181
+ .regex(/[A-Z]/, "Al menos una mayúscula")
182
+ .regex(/[0-9]/, "Al menos un número"),
183
+ confirmPassword: z.string()
184
+ }).refine(
185
+ (data) => data.password === data.confirmPassword,
186
+ { message: "Las contraseñas no coinciden", path: ["confirmPassword"] }
187
+ )
188
+
189
+ export type LoginInput = z.infer<typeof LoginSchema>
190
+ export type RegisterInput = z.infer<typeof RegisterSchema>
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Transformaciones y refinements útiles
196
+
197
+ ```typescript
198
+ // Transformar y sanitizar datos
199
+ const SlugSchema = z
200
+ .string()
201
+ .toLowerCase()
202
+ .trim()
203
+ .transform(s => s.replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, ""))
204
+
205
+ // Validaciones condicionales
206
+ const PaymentSchema = z.discriminatedUnion("method", [
207
+ z.object({
208
+ method: z.literal("credit_card"),
209
+ cardNumber: z.string().length(16),
210
+ cvv: z.string().length(3)
211
+ }),
212
+ z.object({
213
+ method: z.literal("bank_transfer"),
214
+ bankCode: z.string(),
215
+ accountNumber: z.string()
216
+ })
217
+ ])
218
+
219
+ // Coerciones útiles para FormData (siempre strings)
220
+ const FilterSchema = z.object({
221
+ page: z.coerce.number().int().min(1).default(1),
222
+ active: z.coerce.boolean().default(true),
223
+ date: z.coerce.date().optional()
224
+ })
225
+
226
+ // Parsear desde FormData con coerción
227
+ const data = FilterSchema.parse({
228
+ page: formData.get("page"), // "2" → 2
229
+ active: formData.get("active"), // "true" → true
230
+ })
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Zod 4 — cambios respecto a v3
236
+
237
+ ```typescript
238
+ // Zod 4: .flatten() sigue igual para errores
239
+ const result = schema.safeParse(input)
240
+ if (!result.success) {
241
+ result.error.flatten().fieldErrors // { campo: ["mensaje"] }
242
+ result.error.flatten().formErrors // ["error general"]
243
+ }
244
+
245
+ // Zod 4: z.coerce es igual pero más rápido
246
+ z.coerce.number() // "123" → 123
247
+
248
+ // Zod 4: z.pipe() para transformaciones en cadena
249
+ const schema = z.string().pipe(z.coerce.number())
250
+
251
+ // Zod 4: mejor performance en .parse() con objetos grandes
252
+ // No necesita cambios en el código, mejora automática
253
+ ```