red64-cli 0.1.0 → 0.2.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 (103) hide show
  1. package/dist/cli/parseArgs.d.ts.map +1 -1
  2. package/dist/cli/parseArgs.js +5 -0
  3. package/dist/cli/parseArgs.js.map +1 -1
  4. package/dist/components/init/CompleteStep.d.ts.map +1 -1
  5. package/dist/components/init/CompleteStep.js +2 -2
  6. package/dist/components/init/CompleteStep.js.map +1 -1
  7. package/dist/components/init/TestCheckStep.d.ts +16 -0
  8. package/dist/components/init/TestCheckStep.d.ts.map +1 -0
  9. package/dist/components/init/TestCheckStep.js +120 -0
  10. package/dist/components/init/TestCheckStep.js.map +1 -0
  11. package/dist/components/init/index.d.ts +1 -0
  12. package/dist/components/init/index.d.ts.map +1 -1
  13. package/dist/components/init/index.js +1 -0
  14. package/dist/components/init/index.js.map +1 -1
  15. package/dist/components/init/types.d.ts +9 -0
  16. package/dist/components/init/types.d.ts.map +1 -1
  17. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  18. package/dist/components/screens/InitScreen.js +69 -6
  19. package/dist/components/screens/InitScreen.js.map +1 -1
  20. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  21. package/dist/components/screens/StartScreen.js +89 -3
  22. package/dist/components/screens/StartScreen.js.map +1 -1
  23. package/dist/services/ConfigService.d.ts +1 -0
  24. package/dist/services/ConfigService.d.ts.map +1 -1
  25. package/dist/services/ConfigService.js.map +1 -1
  26. package/dist/services/ProjectDetector.d.ts +28 -0
  27. package/dist/services/ProjectDetector.d.ts.map +1 -0
  28. package/dist/services/ProjectDetector.js +236 -0
  29. package/dist/services/ProjectDetector.js.map +1 -0
  30. package/dist/services/TestRunner.d.ts +46 -0
  31. package/dist/services/TestRunner.d.ts.map +1 -0
  32. package/dist/services/TestRunner.js +85 -0
  33. package/dist/services/TestRunner.js.map +1 -0
  34. package/dist/services/index.d.ts +2 -0
  35. package/dist/services/index.d.ts.map +1 -1
  36. package/dist/services/index.js +2 -0
  37. package/dist/services/index.js.map +1 -1
  38. package/dist/types/index.d.ts +1 -0
  39. package/dist/types/index.d.ts.map +1 -1
  40. package/dist/types/index.js.map +1 -1
  41. package/framework/agents/claude/.claude/agents/red64/spec-impl.md +131 -2
  42. package/framework/agents/claude/.claude/commands/red64/spec-impl.md +24 -0
  43. package/framework/agents/codex/.codex/agents/red64/spec-impl.md +131 -2
  44. package/framework/agents/codex/.codex/commands/red64/spec-impl.md +24 -0
  45. package/framework/stacks/generic/feedback.md +80 -0
  46. package/framework/stacks/nextjs/accessibility.md +437 -0
  47. package/framework/stacks/nextjs/api.md +431 -0
  48. package/framework/stacks/nextjs/coding-style.md +282 -0
  49. package/framework/stacks/nextjs/commenting.md +226 -0
  50. package/framework/stacks/nextjs/components.md +411 -0
  51. package/framework/stacks/nextjs/conventions.md +333 -0
  52. package/framework/stacks/nextjs/css.md +310 -0
  53. package/framework/stacks/nextjs/error-handling.md +442 -0
  54. package/framework/stacks/nextjs/feedback.md +124 -0
  55. package/framework/stacks/nextjs/migrations.md +332 -0
  56. package/framework/stacks/nextjs/models.md +362 -0
  57. package/framework/stacks/nextjs/queries.md +410 -0
  58. package/framework/stacks/nextjs/responsive.md +338 -0
  59. package/framework/stacks/nextjs/tech-stack.md +177 -0
  60. package/framework/stacks/nextjs/test-writing.md +475 -0
  61. package/framework/stacks/nextjs/validation.md +467 -0
  62. package/framework/stacks/python/api.md +468 -0
  63. package/framework/stacks/python/authentication.md +342 -0
  64. package/framework/stacks/python/code-quality.md +283 -0
  65. package/framework/stacks/python/code-refactoring.md +315 -0
  66. package/framework/stacks/python/coding-style.md +462 -0
  67. package/framework/stacks/python/conventions.md +399 -0
  68. package/framework/stacks/python/error-handling.md +512 -0
  69. package/framework/stacks/python/feedback.md +92 -0
  70. package/framework/stacks/python/implement-ai-llm.md +468 -0
  71. package/framework/stacks/python/migrations.md +388 -0
  72. package/framework/stacks/python/models.md +399 -0
  73. package/framework/stacks/python/python.md +232 -0
  74. package/framework/stacks/python/queries.md +451 -0
  75. package/framework/stacks/python/structure.md +245 -58
  76. package/framework/stacks/python/tech.md +92 -35
  77. package/framework/stacks/python/testing.md +380 -0
  78. package/framework/stacks/python/validation.md +471 -0
  79. package/framework/stacks/rails/authentication.md +176 -0
  80. package/framework/stacks/rails/code-quality.md +287 -0
  81. package/framework/stacks/rails/code-refactoring.md +299 -0
  82. package/framework/stacks/rails/feedback.md +130 -0
  83. package/framework/stacks/rails/implement-ai-llm-with-rubyllm.md +342 -0
  84. package/framework/stacks/rails/rails.md +301 -0
  85. package/framework/stacks/rails/rails8-best-practices.md +498 -0
  86. package/framework/stacks/rails/rails8-css.md +573 -0
  87. package/framework/stacks/rails/structure.md +140 -0
  88. package/framework/stacks/rails/tech.md +108 -0
  89. package/framework/stacks/react/code-quality.md +521 -0
  90. package/framework/stacks/react/components.md +625 -0
  91. package/framework/stacks/react/data-fetching.md +586 -0
  92. package/framework/stacks/react/feedback.md +110 -0
  93. package/framework/stacks/react/forms.md +694 -0
  94. package/framework/stacks/react/performance.md +640 -0
  95. package/framework/stacks/react/product.md +22 -9
  96. package/framework/stacks/react/state-management.md +472 -0
  97. package/framework/stacks/react/structure.md +351 -44
  98. package/framework/stacks/react/tech.md +219 -30
  99. package/framework/stacks/react/testing.md +690 -0
  100. package/package.json +1 -1
  101. package/framework/stacks/node/product.md +0 -27
  102. package/framework/stacks/node/structure.md +0 -82
  103. package/framework/stacks/node/tech.md +0 -63
@@ -0,0 +1,467 @@
1
+ # Validation Patterns
2
+
3
+ Zod schema validation for Next.js with server actions, API routes, forms, and reusable schema design.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **Single source of truth**: Define schemas once, use on both client and server
10
+ - **Server is the authority**: Client validation improves UX; server validation enforces rules
11
+ - **Fail early, fail clearly**: Validate at the boundary, return specific field errors
12
+ - **Type inference**: Derive TypeScript types from Zod schemas, never duplicate
13
+
14
+ ---
15
+
16
+ ## Zod Schema Basics
17
+
18
+ ### Defining Schemas
19
+
20
+ ```typescript
21
+ // lib/validations/user.ts
22
+ import { z } from "zod";
23
+
24
+ export const createUserSchema = z.object({
25
+ email: z.string().email("Invalid email address"),
26
+ name: z.string().min(1, "Name is required").max(255, "Name is too long"),
27
+ password: z
28
+ .string()
29
+ .min(8, "Password must be at least 8 characters")
30
+ .regex(/[A-Z]/, "Password must contain an uppercase letter")
31
+ .regex(/[0-9]/, "Password must contain a number"),
32
+ });
33
+
34
+ export const updateUserSchema = z.object({
35
+ name: z.string().min(1).max(255).optional(),
36
+ bio: z.string().max(500, "Bio must be 500 characters or less").optional(),
37
+ avatarUrl: z.string().url("Invalid URL").optional().or(z.literal("")),
38
+ });
39
+
40
+ // Derive TypeScript types from schemas
41
+ export type CreateUserInput = z.infer<typeof createUserSchema>;
42
+ export type UpdateUserInput = z.infer<typeof updateUserSchema>;
43
+ ```
44
+
45
+ ### Common Field Patterns
46
+
47
+ ```typescript
48
+ // Email
49
+ z.string().email("Invalid email address")
50
+
51
+ // Password with rules
52
+ z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/)
53
+
54
+ // Optional string that can be empty
55
+ z.string().optional().or(z.literal(""))
56
+
57
+ // URL (optional)
58
+ z.string().url().optional()
59
+
60
+ // Enum
61
+ z.enum(["ADMIN", "MEMBER", "VIEWER"])
62
+
63
+ // Date string from form input
64
+ z.string().pipe(z.coerce.date())
65
+
66
+ // Positive integer
67
+ z.coerce.number().int().positive()
68
+
69
+ // Boolean from checkbox (form sends "on" or undefined)
70
+ z.string().optional().transform((val) => val === "on")
71
+
72
+ // ID (cuid format)
73
+ z.string().cuid()
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Server Action Validation
79
+
80
+ ### Basic Pattern
81
+
82
+ ```typescript
83
+ // actions/users.ts
84
+ "use server";
85
+
86
+ import { z } from "zod";
87
+ import { auth } from "@/lib/auth";
88
+ import { prisma } from "@/lib/prisma";
89
+ import { revalidatePath } from "next/cache";
90
+ import { createUserSchema } from "@/lib/validations/user";
91
+
92
+ type ActionState = {
93
+ success?: boolean;
94
+ error?: string;
95
+ fieldErrors?: Record<string, string[]>;
96
+ };
97
+
98
+ export async function createUserAction(
99
+ _prevState: ActionState,
100
+ formData: FormData
101
+ ): Promise<ActionState> {
102
+ const session = await auth();
103
+ if (!session?.user) {
104
+ return { error: "You must be signed in" };
105
+ }
106
+
107
+ const parsed = createUserSchema.safeParse({
108
+ email: formData.get("email"),
109
+ name: formData.get("name"),
110
+ password: formData.get("password"),
111
+ });
112
+
113
+ if (!parsed.success) {
114
+ return {
115
+ fieldErrors: parsed.error.flatten().fieldErrors,
116
+ };
117
+ }
118
+
119
+ try {
120
+ await prisma.user.create({
121
+ data: {
122
+ email: parsed.data.email,
123
+ name: parsed.data.name,
124
+ hashedPassword: await hash(parsed.data.password, 12),
125
+ },
126
+ });
127
+ } catch (error) {
128
+ if (isPrismaUniqueConstraintError(error)) {
129
+ return { fieldErrors: { email: ["Email is already taken"] } };
130
+ }
131
+ return { error: "Failed to create user" };
132
+ }
133
+
134
+ revalidatePath("/users");
135
+ return { success: true };
136
+ }
137
+ ```
138
+
139
+ ---
140
+
141
+ ## API Route Validation
142
+
143
+ ### Request Body Validation
144
+
145
+ ```typescript
146
+ // app/api/posts/route.ts
147
+ import { NextRequest, NextResponse } from "next/server";
148
+ import { z } from "zod";
149
+
150
+ const createPostSchema = z.object({
151
+ title: z.string().min(1).max(500),
152
+ body: z.string().min(1),
153
+ status: z.enum(["DRAFT", "PUBLISHED"]).default("DRAFT"),
154
+ tags: z.array(z.string()).max(10).default([]),
155
+ });
156
+
157
+ export async function POST(request: NextRequest) {
158
+ const body = await request.json().catch(() => null);
159
+
160
+ if (!body) {
161
+ return NextResponse.json(
162
+ { error: { code: "BAD_REQUEST", message: "Invalid JSON body" } },
163
+ { status: 400 }
164
+ );
165
+ }
166
+
167
+ const parsed = createPostSchema.safeParse(body);
168
+
169
+ if (!parsed.success) {
170
+ return NextResponse.json(
171
+ {
172
+ error: {
173
+ code: "VALIDATION_ERROR",
174
+ message: "Invalid input",
175
+ details: parsed.error.flatten(),
176
+ },
177
+ },
178
+ { status: 422 }
179
+ );
180
+ }
181
+
182
+ // parsed.data is fully typed
183
+ const post = await prisma.post.create({ data: parsed.data });
184
+ return NextResponse.json(post, { status: 201 });
185
+ }
186
+ ```
187
+
188
+ ### Query Parameter Validation
189
+
190
+ ```typescript
191
+ const listPostsSchema = z.object({
192
+ page: z.coerce.number().int().positive().default(1),
193
+ limit: z.coerce.number().int().min(1).max(100).default(20),
194
+ status: z.enum(["DRAFT", "PUBLISHED", "ARCHIVED"]).optional(),
195
+ search: z.string().max(200).optional(),
196
+ sort: z.string().regex(/^-?(createdAt|title|updatedAt)$/).default("-createdAt"),
197
+ });
198
+
199
+ export async function GET(request: NextRequest) {
200
+ const { searchParams } = request.nextUrl;
201
+
202
+ const parsed = listPostsSchema.safeParse(
203
+ Object.fromEntries(searchParams.entries())
204
+ );
205
+
206
+ if (!parsed.success) {
207
+ return NextResponse.json(
208
+ { error: { code: "VALIDATION_ERROR", message: "Invalid parameters" } },
209
+ { status: 422 }
210
+ );
211
+ }
212
+
213
+ const { page, limit, status, search, sort } = parsed.data;
214
+ // ... query with validated params
215
+ }
216
+ ```
217
+
218
+ ---
219
+
220
+ ## Form Validation with React Hook Form
221
+
222
+ ### Client-Side with Zod Resolver
223
+
224
+ ```typescript
225
+ // components/forms/create-user-form.tsx
226
+ "use client";
227
+
228
+ import { useForm } from "react-hook-form";
229
+ import { zodResolver } from "@hookform/resolvers/zod";
230
+ import { createUserSchema, type CreateUserInput } from "@/lib/validations/user";
231
+ import { createUserAction } from "@/actions/users";
232
+
233
+ export function CreateUserForm() {
234
+ const {
235
+ register,
236
+ handleSubmit,
237
+ formState: { errors, isSubmitting },
238
+ setError,
239
+ } = useForm<CreateUserInput>({
240
+ resolver: zodResolver(createUserSchema),
241
+ });
242
+
243
+ async function onSubmit(data: CreateUserInput) {
244
+ const formData = new FormData();
245
+ Object.entries(data).forEach(([key, value]) => formData.append(key, value));
246
+
247
+ const result = await createUserAction({}, formData);
248
+
249
+ if (result.fieldErrors) {
250
+ Object.entries(result.fieldErrors).forEach(([field, messages]) => {
251
+ setError(field as keyof CreateUserInput, {
252
+ message: messages[0],
253
+ });
254
+ });
255
+ return;
256
+ }
257
+
258
+ if (result.error) {
259
+ setError("root", { message: result.error });
260
+ }
261
+ }
262
+
263
+ return (
264
+ <form onSubmit={handleSubmit(onSubmit)} noValidate>
265
+ <div>
266
+ <label htmlFor="email">Email</label>
267
+ <input id="email" type="email" {...register("email")} aria-invalid={!!errors.email} />
268
+ {errors.email && <p className="text-sm text-destructive">{errors.email.message}</p>}
269
+ </div>
270
+
271
+ <div>
272
+ <label htmlFor="name">Name</label>
273
+ <input id="name" type="text" {...register("name")} aria-invalid={!!errors.name} />
274
+ {errors.name && <p className="text-sm text-destructive">{errors.name.message}</p>}
275
+ </div>
276
+
277
+ <div>
278
+ <label htmlFor="password">Password</label>
279
+ <input id="password" type="password" {...register("password")} aria-invalid={!!errors.password} />
280
+ {errors.password && <p className="text-sm text-destructive">{errors.password.message}</p>}
281
+ </div>
282
+
283
+ {errors.root && <p className="text-sm text-destructive">{errors.root.message}</p>}
284
+
285
+ <button type="submit" disabled={isSubmitting}>
286
+ {isSubmitting ? "Creating..." : "Create User"}
287
+ </button>
288
+ </form>
289
+ );
290
+ }
291
+ ```
292
+
293
+ ---
294
+
295
+ ## Reusable Schema Patterns
296
+
297
+ ### Shared Field Schemas
298
+
299
+ ```typescript
300
+ // lib/validations/shared.ts
301
+ import { z } from "zod";
302
+
303
+ export const emailSchema = z.string().email("Invalid email address").toLowerCase().trim();
304
+ export const passwordSchema = z.string().min(8, "At least 8 characters").regex(/[A-Z]/, "Needs uppercase").regex(/[0-9]/, "Needs a number");
305
+ export const nameSchema = z.string().min(1, "Required").max(255).trim();
306
+ export const idSchema = z.string().cuid("Invalid ID");
307
+ export const paginationSchema = z.object({
308
+ page: z.coerce.number().int().positive().default(1),
309
+ limit: z.coerce.number().int().min(1).max(100).default(20),
310
+ });
311
+ ```
312
+
313
+ ### Composing Schemas
314
+
315
+ ```typescript
316
+ // lib/validations/user.ts
317
+ import { emailSchema, passwordSchema, nameSchema } from "./shared";
318
+
319
+ export const createUserSchema = z.object({
320
+ email: emailSchema,
321
+ name: nameSchema,
322
+ password: passwordSchema,
323
+ });
324
+
325
+ export const loginSchema = z.object({
326
+ email: emailSchema,
327
+ password: z.string().min(1, "Password is required"),
328
+ });
329
+
330
+ // Partial schema for updates
331
+ export const updateUserSchema = createUserSchema
332
+ .omit({ password: true })
333
+ .partial()
334
+ .extend({
335
+ bio: z.string().max(500).optional(),
336
+ });
337
+ ```
338
+
339
+ ---
340
+
341
+ ## Advanced Patterns
342
+
343
+ ### Discriminated Unions
344
+
345
+ ```typescript
346
+ const notificationSchema = z.discriminatedUnion("type", [
347
+ z.object({
348
+ type: z.literal("email"),
349
+ email: z.string().email(),
350
+ subject: z.string().min(1),
351
+ }),
352
+ z.object({
353
+ type: z.literal("sms"),
354
+ phoneNumber: z.string().regex(/^\+[1-9]\d{1,14}$/),
355
+ message: z.string().max(160),
356
+ }),
357
+ z.object({
358
+ type: z.literal("push"),
359
+ token: z.string(),
360
+ title: z.string().min(1),
361
+ body: z.string().max(500),
362
+ }),
363
+ ]);
364
+
365
+ type Notification = z.infer<typeof notificationSchema>;
366
+ // Type is automatically narrowed based on "type" field
367
+ ```
368
+
369
+ ### Transform and Refine
370
+
371
+ ```typescript
372
+ // Transform: change the output type
373
+ const slugSchema = z.string().transform((val) =>
374
+ val.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "")
375
+ );
376
+
377
+ // Refine: custom validation logic
378
+ const dateRangeSchema = z.object({
379
+ startDate: z.coerce.date(),
380
+ endDate: z.coerce.date(),
381
+ }).refine(
382
+ (data) => data.endDate > data.startDate,
383
+ { message: "End date must be after start date", path: ["endDate"] }
384
+ );
385
+
386
+ // Superrefine: multiple custom errors
387
+ const registerSchema = z.object({
388
+ password: z.string().min(8),
389
+ confirmPassword: z.string(),
390
+ }).superRefine((data, ctx) => {
391
+ if (data.password !== data.confirmPassword) {
392
+ ctx.addIssue({
393
+ code: z.ZodIssueCode.custom,
394
+ message: "Passwords do not match",
395
+ path: ["confirmPassword"],
396
+ });
397
+ }
398
+ });
399
+ ```
400
+
401
+ ### Preprocessing Form Data
402
+
403
+ ```typescript
404
+ // FormData sends everything as strings
405
+ // Use z.coerce or z.preprocess to handle this
406
+
407
+ const productSchema = z.object({
408
+ name: z.string().min(1),
409
+ price: z.coerce.number().positive("Price must be positive"),
410
+ quantity: z.coerce.number().int().nonnegative(),
411
+ isActive: z.preprocess((val) => val === "on" || val === "true", z.boolean()),
412
+ categories: z.preprocess(
413
+ (val) => (typeof val === "string" ? val.split(",").map((s) => s.trim()) : val),
414
+ z.array(z.string())
415
+ ),
416
+ });
417
+ ```
418
+
419
+ ---
420
+
421
+ ## Error Message Customization
422
+
423
+ ### Per-Field Messages
424
+
425
+ ```typescript
426
+ const schema = z.object({
427
+ email: z.string({
428
+ required_error: "Email is required",
429
+ invalid_type_error: "Email must be a string",
430
+ }).email("Please enter a valid email address"),
431
+ });
432
+ ```
433
+
434
+ ### Error Mapping
435
+
436
+ ```typescript
437
+ // lib/validations/format-errors.ts
438
+ import type { ZodError } from "zod";
439
+
440
+ export function formatZodErrors(error: ZodError): Record<string, string> {
441
+ const formatted: Record<string, string> = {};
442
+ for (const issue of error.issues) {
443
+ const path = issue.path.join(".");
444
+ if (!formatted[path]) {
445
+ formatted[path] = issue.message;
446
+ }
447
+ }
448
+ return formatted;
449
+ }
450
+ ```
451
+
452
+ ---
453
+
454
+ ## Anti-Patterns
455
+
456
+ | Anti-Pattern | Problem | Correct Approach |
457
+ |---|---|---|
458
+ | Client-only validation | Server trusts unvalidated data | Always validate on server; client is UX only |
459
+ | Duplicating types and schemas | Types drift from validation | Derive types with `z.infer<typeof schema>` |
460
+ | Generic error messages | Users cannot fix their input | Specific, field-level error messages |
461
+ | Validating in the route handler | Duplicated across handlers | Shared schemas in `lib/validations/` |
462
+ | `z.any()` or `z.unknown()` passthrough | Defeats the purpose of validation | Define the exact shape you expect |
463
+ | No `safeParse` | Throws on invalid input | Use `safeParse` and handle errors gracefully |
464
+
465
+ ---
466
+
467
+ _Validate at the boundary, fail with specificity, and let Zod's type inference eliminate the gap between runtime checks and compile-time types._