create-loadout 1.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 (72) hide show
  1. package/README.md +154 -0
  2. package/dist/claude-md.d.ts +3 -0
  3. package/dist/claude-md.js +494 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +186 -0
  6. package/dist/config.d.ts +3 -0
  7. package/dist/config.js +98 -0
  8. package/dist/create-next.d.ts +1 -0
  9. package/dist/create-next.js +17 -0
  10. package/dist/detect.d.ts +4 -0
  11. package/dist/detect.js +60 -0
  12. package/dist/env.d.ts +3 -0
  13. package/dist/env.js +183 -0
  14. package/dist/generate-readme.d.ts +3 -0
  15. package/dist/generate-readme.js +160 -0
  16. package/dist/index.d.ts +2 -0
  17. package/dist/index.js +3 -0
  18. package/dist/instrumentation.d.ts +3 -0
  19. package/dist/instrumentation.js +95 -0
  20. package/dist/integrations/ai-sdk.d.ts +3 -0
  21. package/dist/integrations/ai-sdk.js +20 -0
  22. package/dist/integrations/clerk.d.ts +2 -0
  23. package/dist/integrations/clerk.js +50 -0
  24. package/dist/integrations/firecrawl.d.ts +2 -0
  25. package/dist/integrations/firecrawl.js +26 -0
  26. package/dist/integrations/index.d.ts +4 -0
  27. package/dist/integrations/index.js +64 -0
  28. package/dist/integrations/inngest.d.ts +2 -0
  29. package/dist/integrations/inngest.js +45 -0
  30. package/dist/integrations/neon-drizzle.d.ts +2 -0
  31. package/dist/integrations/neon-drizzle.js +56 -0
  32. package/dist/integrations/posthog.d.ts +2 -0
  33. package/dist/integrations/posthog.js +25 -0
  34. package/dist/integrations/resend.d.ts +2 -0
  35. package/dist/integrations/resend.js +34 -0
  36. package/dist/integrations/sentry.d.ts +2 -0
  37. package/dist/integrations/sentry.js +47 -0
  38. package/dist/integrations/stripe.d.ts +2 -0
  39. package/dist/integrations/stripe.js +45 -0
  40. package/dist/integrations/uploadthing.d.ts +2 -0
  41. package/dist/integrations/uploadthing.js +34 -0
  42. package/dist/landing-page.d.ts +2 -0
  43. package/dist/landing-page.js +97 -0
  44. package/dist/prompts.d.ts +7 -0
  45. package/dist/prompts.js +99 -0
  46. package/dist/setup-shadcn.d.ts +1 -0
  47. package/dist/setup-shadcn.js +27 -0
  48. package/dist/templates/ai-sdk.d.ts +12 -0
  49. package/dist/templates/ai-sdk.js +96 -0
  50. package/dist/templates/clerk.d.ts +6 -0
  51. package/dist/templates/clerk.js +96 -0
  52. package/dist/templates/firecrawl.d.ts +4 -0
  53. package/dist/templates/firecrawl.js +106 -0
  54. package/dist/templates/inngest.d.ts +6 -0
  55. package/dist/templates/inngest.js +91 -0
  56. package/dist/templates/neon-drizzle.d.ts +16 -0
  57. package/dist/templates/neon-drizzle.js +343 -0
  58. package/dist/templates/posthog.d.ts +3 -0
  59. package/dist/templates/posthog.js +10 -0
  60. package/dist/templates/resend.d.ts +5 -0
  61. package/dist/templates/resend.js +102 -0
  62. package/dist/templates/sentry.d.ts +8 -0
  63. package/dist/templates/sentry.js +145 -0
  64. package/dist/templates/stripe.d.ts +6 -0
  65. package/dist/templates/stripe.js +215 -0
  66. package/dist/templates/uploadthing.d.ts +7 -0
  67. package/dist/templates/uploadthing.js +150 -0
  68. package/dist/templates/zustand.d.ts +3 -0
  69. package/dist/templates/zustand.js +26 -0
  70. package/dist/types.d.ts +26 -0
  71. package/dist/types.js +1 -0
  72. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,154 @@
1
+ <div align="center">
2
+
3
+ ```txt
4
+ _ _ _
5
+ | | ___ __ _ __| |___ _ _| |_
6
+ | |__/ _ \/ _` / _` / _ \ || | _|
7
+ |____\___/\__,_\__,_\___/\_,_|\__|
8
+
9
+ ```
10
+
11
+ **Stop copy-pasting boilerplate. Start building.**
12
+
13
+ **An opinionated Next.js scaffold with the integrations you probably need.**
14
+
15
+ [![npm version](https://img.shields.io/npm/v/create-loadout?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/create-loadout)
16
+ [![npm downloads](https://img.shields.io/npm/dm/create-loadout?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/create-loadout)
17
+ [![GitHub stars](https://img.shields.io/github/stars/KylerD/loadout?style=for-the-badge&logo=github&color=181717)](https://github.com/KylerD/loadout)
18
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=for-the-badge)](LICENSE)
19
+
20
+ ```bash
21
+ npx create-loadout
22
+ ```
23
+
24
+ Works on Mac, Windows, and Linux.
25
+
26
+ </div>
27
+
28
+ ---
29
+
30
+ ## Why I Built This
31
+
32
+ Every new SaaS project starts the same way. Create the Next.js app. Add Tailwind. Set up shadcn. Copy your auth config from the last project. Wire up the database. Add Stripe. Configure error tracking. Set up analytics.
33
+
34
+ It's the same 2-4 hours every time. And every time, you're copy-pasting from old projects, fixing the inevitable drift, and wondering if you remembered everything.
35
+
36
+ **Loadout gives you a fully-wired Next.js app in under a minute.**
37
+
38
+ You pick your integrations. It scaffolds everything — services, API routes, typed env vars, even a `CLAUDE.md` so your AI assistant knows how the project is structured.
39
+
40
+ No more boilerplate archaeology. Just start building.
41
+
42
+ ---
43
+
44
+ ## What You Get
45
+
46
+ ```
47
+ your-app/
48
+ ├── app/ # Next.js App Router
49
+ ├── components/ # React components + shadcn/ui
50
+ ├── actions/ # Server actions
51
+ ├── services/ # Business logic (DI-ready)
52
+ ├── dao/ # Data access layer (Drizzle ORM)
53
+ ├── models/ # DTOs, views, schemas, state
54
+ ├── lib/
55
+ │ ├── config.ts # Type-safe env vars
56
+ │ └── db/ # Database client + schema
57
+ ├── CLAUDE.md # AI assistant context
58
+ ├── .env.example # Documented env template
59
+ └── .env.local # Your secrets (gitignored)
60
+ ```
61
+
62
+ **Every integration follows the same pattern:**
63
+
64
+ - Services for business logic
65
+ - Type-safe configuration
66
+ - Ready-to-use API routes where needed
67
+ - Zero magic — just clean, readable code
68
+
69
+ ---
70
+
71
+ ## Available Integrations
72
+
73
+ | | Integration | What You Get |
74
+ | :-: | ------------------ | ------------------------------------------ |
75
+ | 🔐 | **Clerk** | Authentication + user service |
76
+ | 🗄️ | **Neon + Drizzle** | Serverless Postgres with full CRUD example |
77
+ | 🤖 | **AI SDK** | OpenAI / Anthropic / Google |
78
+ | 📧 | **Resend** | Email service + React email templates |
79
+ | 🔥 | **Firecrawl** | Web scraping service |
80
+ | ⏰ | **Inngest** | Background jobs |
81
+ | 📁 | **UploadThing** | File uploads |
82
+ | 💳 | **Stripe** | Checkout, webhooks, customer portal |
83
+ | 📊 | **PostHog** | Product analytics |
84
+ | 🐛 | **Sentry** | Error tracking |
85
+
86
+ **Always included:** TypeScript, Tailwind, shadcn/ui, Zod, Zustand, Luxon
87
+
88
+ ---
89
+
90
+ ## How It Works
91
+
92
+ ### 1. Run the CLI
93
+
94
+ ```bash
95
+ npx create-loadout
96
+ ```
97
+
98
+ ### 2. Answer the Prompts
99
+
100
+ - Project name
101
+ - Which integrations you need
102
+ - AI provider (if using AI SDK)
103
+
104
+ ### 3. Start Building
105
+
106
+ ```bash
107
+ cd your-app
108
+ npm install
109
+ npm run dev
110
+ ```
111
+
112
+ That's it. Fill in `.env.local` and you're live.
113
+
114
+ ---
115
+
116
+ ## Architecture
117
+
118
+ Generated projects follow a **layered architecture**:
119
+
120
+ ```
121
+ UI Components (app/, components/)
122
+
123
+ Server Actions (actions/*.actions.ts)
124
+
125
+ Services (services/*.service.ts)
126
+
127
+ DAOs (dao/*.dao.ts + Drizzle ORM)
128
+
129
+ Neon (Serverless Postgres)
130
+ ```
131
+
132
+ Services use constructor-based dependency injection with singleton exports — optimized for Next.js serverless.
133
+
134
+ ---
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ git clone https://github.com/KylerD/loadout.git
140
+ cd loadout
141
+ npm install
142
+ npm run dev
143
+
144
+ # Build and test locally
145
+ npm run build
146
+ npm link
147
+ create-loadout
148
+ ```
149
+
150
+ ---
151
+
152
+ ## License
153
+
154
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { ProjectConfig, IntegrationId } from './types.js';
2
+ export declare function generateClaudeMd(projectPath: string, config: ProjectConfig): Promise<void>;
3
+ export declare function appendClaudeMd(projectPath: string, integrations: IntegrationId[]): Promise<void>;
@@ -0,0 +1,494 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ const stackSections = [
4
+ {
5
+ id: 'core',
6
+ name: 'Core',
7
+ items: [
8
+ { name: 'Next.js', url: 'https://nextjs.org/docs', description: 'React framework with App Router' },
9
+ { name: 'TypeScript', url: 'https://www.typescriptlang.org/docs/', description: 'Type safety' },
10
+ { name: 'Tailwind CSS', url: 'https://tailwindcss.com/docs', description: 'Utility-first CSS' },
11
+ { name: 'shadcn/ui', url: 'https://ui.shadcn.com/docs', description: 'UI components' },
12
+ { name: 'Zod', url: 'https://zod.dev/', description: 'Schema validation' },
13
+ { name: 'Zustand', url: 'https://zustand.docs.pmnd.rs/', description: 'Client state management' },
14
+ { name: 'Luxon', url: 'https://moment.github.io/luxon/', description: 'Date/time manipulation' },
15
+ ],
16
+ },
17
+ {
18
+ id: 'clerk',
19
+ name: 'Authentication',
20
+ items: [
21
+ { name: 'Clerk', url: 'https://clerk.com/docs', description: 'Authentication and user management' },
22
+ ],
23
+ },
24
+ {
25
+ id: 'neon-drizzle',
26
+ name: 'Database',
27
+ items: [
28
+ { name: 'Neon', url: 'https://neon.tech/docs', description: 'Serverless Postgres' },
29
+ { name: 'Drizzle ORM', url: 'https://orm.drizzle.team/docs/overview', description: 'TypeScript ORM' },
30
+ ],
31
+ },
32
+ {
33
+ id: 'ai-sdk',
34
+ name: 'AI',
35
+ items: [
36
+ { name: 'Vercel AI SDK', url: 'https://sdk.vercel.ai/docs', description: 'AI integration' },
37
+ ],
38
+ },
39
+ {
40
+ id: 'resend',
41
+ name: 'Email',
42
+ items: [
43
+ { name: 'Resend', url: 'https://resend.com/docs', description: 'Email API' },
44
+ ],
45
+ },
46
+ {
47
+ id: 'firecrawl',
48
+ name: 'Scraping',
49
+ items: [
50
+ { name: 'Firecrawl', url: 'https://docs.firecrawl.dev/', description: 'Web scraping' },
51
+ ],
52
+ },
53
+ {
54
+ id: 'inngest',
55
+ name: 'Background Jobs',
56
+ items: [
57
+ { name: 'Inngest', url: 'https://www.inngest.com/docs', description: 'Background jobs and workflows' },
58
+ ],
59
+ },
60
+ {
61
+ id: 'uploadthing',
62
+ name: 'File Uploads',
63
+ items: [
64
+ { name: 'UploadThing', url: 'https://docs.uploadthing.com/', description: 'File uploads' },
65
+ ],
66
+ },
67
+ {
68
+ id: 'stripe',
69
+ name: 'Payments',
70
+ items: [
71
+ { name: 'Stripe', url: 'https://docs.stripe.com/', description: 'Payments and subscriptions' },
72
+ ],
73
+ },
74
+ {
75
+ id: 'posthog',
76
+ name: 'Analytics',
77
+ items: [
78
+ { name: 'PostHog', url: 'https://posthog.com/docs', description: 'Product analytics' },
79
+ ],
80
+ },
81
+ {
82
+ id: 'sentry',
83
+ name: 'Error Tracking',
84
+ items: [
85
+ { name: 'Sentry', url: 'https://docs.sentry.io/platforms/javascript/guides/nextjs/', description: 'Error monitoring' },
86
+ ],
87
+ },
88
+ ];
89
+ export async function generateClaudeMd(projectPath, config) {
90
+ const sections = stackSections.filter((section) => section.id === 'core' || config.integrations.includes(section.id));
91
+ const hasDb = config.integrations.includes('neon-drizzle');
92
+ const hasPostHog = config.integrations.includes('posthog');
93
+ const hasSentry = config.integrations.includes('sentry');
94
+ const hasClerk = config.integrations.includes('clerk');
95
+ let content = `# ${config.name}
96
+
97
+ ## Tech Stack
98
+
99
+ `;
100
+ for (const section of sections) {
101
+ content += `### ${section.name}\n`;
102
+ for (const item of section.items) {
103
+ content += `- [${item.name}](${item.url}) - ${item.description}\n`;
104
+ }
105
+ content += '\n';
106
+ }
107
+ content += `## Development Commands
108
+
109
+ \`\`\`bash
110
+ npm run dev # Start development server
111
+ npm run build # Build for production
112
+ npm run start # Start production server
113
+ npm run lint # Run ESLint
114
+ \`\`\`
115
+ `;
116
+ if (hasDb) {
117
+ content += `
118
+ ### Database Commands
119
+
120
+ \`\`\`bash
121
+ npm run db:generate # Generate migrations from schema changes
122
+ npm run db:migrate # Apply migrations to database
123
+ npm run db:studio # Open Drizzle Studio to browse data
124
+ \`\`\`
125
+ `;
126
+ }
127
+ if (config.integrations.includes('inngest')) {
128
+ content += `
129
+ ### Background Jobs
130
+
131
+ \`\`\`bash
132
+ npm run inngest:dev # Start Inngest dev server for local testing
133
+ \`\`\`
134
+ `;
135
+ }
136
+ content += `
137
+ ## Architecture
138
+
139
+ ### Directory Structure
140
+
141
+ \`\`\`
142
+ ├── app/ # Next.js App Router pages and API routes
143
+ ├── components/ # React components (including shadcn/ui)
144
+ `;
145
+ if (config.integrations.includes('resend')) {
146
+ content += `│ └── emails/ # React Email templates
147
+ `;
148
+ }
149
+ if (hasPostHog || hasSentry) {
150
+ content += `├── instrumentation-client.ts # Client-side init
151
+ `;
152
+ }
153
+ if (hasSentry) {
154
+ content += `├── instrumentation.ts # Server-side Sentry registration
155
+ `;
156
+ }
157
+ if (hasDb) {
158
+ content += `├── actions/ # Server actions (form submissions)
159
+ │ └── *.actions.ts
160
+ ├── services/ # Business logic orchestration
161
+ │ └── *.service.ts
162
+ ├── dao/ # Data access objects (database queries)
163
+ │ └── *.dao.ts
164
+ ├── mappers/ # Data transformation
165
+ │ └── *.mapper.ts
166
+ ├── models/ # Type definitions
167
+ │ ├── *.dto.ts # Database types (InferSelectModel)
168
+ │ ├── *.view.ts # View models for UI
169
+ │ ├── *.schema.ts # Zod validation + ServiceRequest/Result
170
+ │ ├── *.state.ts # Action state objects
171
+ │ └── *ServiceError.enum.ts # Service error enums
172
+ `;
173
+ }
174
+ else {
175
+ content += `├── services/ # Business logic services
176
+ │ └── *.service.ts
177
+ `;
178
+ }
179
+ content += `├── lib/
180
+ │ ├── config.ts # Centralized environment variables
181
+ │ ├── stores/ # Zustand stores for client state
182
+ │ │ └── *.store.ts
183
+ `;
184
+ if (hasDb) {
185
+ content += `│ └── db/ # Database client and schema
186
+ `;
187
+ }
188
+ content += `└── public/ # Static assets
189
+ \`\`\`
190
+ `;
191
+ if (hasDb) {
192
+ content += `
193
+ ### Layered Architecture
194
+
195
+ The application follows a strict 4-layer architecture:
196
+
197
+ \`\`\`
198
+ UI Components (app/, components/)
199
+
200
+ Server Actions (actions/*.actions.ts)
201
+
202
+ Services (services/*.service.ts)
203
+
204
+ DAOs (dao/*.dao.ts)
205
+
206
+ Database (Drizzle ORM)
207
+ \`\`\`
208
+
209
+ **Layer responsibilities:**
210
+
211
+ - **Actions** - Handle form submissions, validate with Zod, check auth, call services, revalidate cache
212
+ - **Services** - Orchestrate business logic, coordinate multiple DAOs
213
+ - **DAOs** - Encapsulate all database queries using Drizzle ORM
214
+ - **Mappers** - Transform between DTOs, service requests, and view models
215
+
216
+ ### Model File Naming Conventions
217
+
218
+ Files in \`models/\` follow strict naming:
219
+
220
+ | Pattern | Purpose | Example |
221
+ |---------|---------|---------|
222
+ | \`*.dto.ts\` | Database types from Drizzle | \`UserDto\`, \`UserInsertDto\` |
223
+ | \`*.view.ts\` | View models for UI | \`UserView\` |
224
+ | \`*.schema.ts\` | Zod schemas + service types | \`UserCreateFormSchema\`, \`UserCreateServiceRequest\` |
225
+ | \`*.state.ts\` | Action state objects | \`UserCreateState\` |
226
+ | \`*ServiceError.enum.ts\` | Service error enums | \`UserServiceError\` |
227
+
228
+ ### Action File Organization
229
+
230
+ One action file per domain entity: \`actions/{entity}.action.ts\`. Do NOT split by operation type.
231
+
232
+ \`\`\`
233
+ actions/
234
+ project.action.ts # createProject, updateProject, deleteProject, searchProjects
235
+ task.action.ts # createTask, updateTask, deleteTask, searchTasks
236
+ comment.action.ts # createComment, deleteComment
237
+ settings.action.ts # updateSettings
238
+ \`\`\`
239
+
240
+ Do NOT create separate files like \`project.create.action.ts\` or \`task.search.action.ts\`.
241
+
242
+ ### Server Action Pattern
243
+
244
+ \`\`\`typescript
245
+ 'use server';
246
+
247
+ export async function createEntity(
248
+ state: EntityCreateState,
249
+ formData: FormData
250
+ ): Promise<EntityCreateState> {
251
+ const user = await currentUser();
252
+ if (!user) {
253
+ return { success: false, error: 'Not authenticated', data: null };
254
+ }
255
+
256
+ const rawData = Object.fromEntries(formData);
257
+ const validated = EntityCreateSchema.safeParse(rawData);
258
+
259
+ if (!validated.success) {
260
+ return { success: false, error: z.prettifyError(validated.error), data: null };
261
+ }
262
+
263
+ try {
264
+ const result = await entityService.createEntity(validated.data);
265
+ revalidatePath('/entities');
266
+ return { success: true, error: null, data: result };
267
+ } catch (error) {
268
+ return { success: false, error: 'Failed to create entity', data: null };
269
+ }
270
+ }
271
+ \`\`\`
272
+
273
+ ### DAO Pattern
274
+
275
+ \`\`\`typescript
276
+ export class EntityDAO {
277
+ async create(dto: EntityInsertDto): Promise<EntityDto | undefined> {
278
+ const [created] = await db.insert(entities).values(dto).returning();
279
+ return created;
280
+ }
281
+
282
+ async getById(id: string): Promise<EntityDto | undefined> {
283
+ return await db.query.entities.findFirst({
284
+ where: eq(entities.id, id),
285
+ });
286
+ }
287
+ }
288
+
289
+ export const entityDAO = new EntityDAO();
290
+ \`\`\`
291
+
292
+ ### Service Error Enums
293
+
294
+ Each service class has a corresponding error enum in \`models/{serviceName}ServiceError.enum.ts\`. Services throw errors using enum values, actions catch and translate to user-friendly messages.
295
+
296
+ \`\`\`typescript
297
+ // models/performanceServiceError.enum.ts
298
+ export enum PerformanceServiceError {
299
+ NotFound = "PERFORMANCE_NOT_FOUND",
300
+ NotOwned = "PERFORMANCE_NOT_OWNED",
301
+ DuplicateTime = "PERFORMANCE_DUPLICATE_TIME",
302
+ }
303
+
304
+ // services/performance.service.ts
305
+ import { PerformanceServiceError } from "@/models/performanceServiceError.enum";
306
+
307
+ if (conflict) {
308
+ throw new Error(PerformanceServiceError.DuplicateTime);
309
+ }
310
+
311
+ // actions/performance.action.ts
312
+ import { PerformanceServiceError } from "@/models/performanceServiceError.enum";
313
+
314
+ catch (error) {
315
+ if (error instanceof Error) {
316
+ switch (error.message) {
317
+ case PerformanceServiceError.DuplicateTime:
318
+ return { success: false, error: 'A performance already exists at this time', data: null };
319
+ case PerformanceServiceError.NotFound:
320
+ return { success: false, error: 'Performance not found', data: null };
321
+ case PerformanceServiceError.NotOwned:
322
+ return { success: false, error: 'You do not have permission to modify this performance', data: null };
323
+ }
324
+ }
325
+ return { success: false, error: 'Failed to update performance', data: null };
326
+ }
327
+ \`\`\`
328
+ `;
329
+ }
330
+ content += `
331
+ ## Client State Management (Zustand)
332
+
333
+ For complex multi-step forms or flows, use Zustand stores.
334
+
335
+ **Store location**: \`lib/stores/*.store.ts\`
336
+
337
+ ### Store Pattern
338
+
339
+ \`\`\`typescript
340
+ import { createStore, useStore } from 'zustand';
341
+
342
+ interface FormState {
343
+ title: string;
344
+ description: string;
345
+ setTitle: (title: string) => void;
346
+ setDescription: (description: string) => void;
347
+ reset: () => void;
348
+ }
349
+
350
+ const initialState = {
351
+ title: '',
352
+ description: '',
353
+ };
354
+
355
+ export const formStore = createStore<FormState>()((set) => ({
356
+ ...initialState,
357
+ setTitle: (title) => set({ title }),
358
+ setDescription: (description) => set({ description }),
359
+ reset: () => set(initialState),
360
+ }));
361
+
362
+ export const useFormStore = <T>(selector: (state: FormState) => T): T => {
363
+ return useStore(formStore, selector);
364
+ };
365
+ \`\`\`
366
+
367
+ ### Usage in Components
368
+
369
+ \`\`\`tsx
370
+ 'use client';
371
+
372
+ import { Input } from '@/components/ui/input';
373
+ import { Label } from '@/components/ui/label';
374
+
375
+ function TitleStep() {
376
+ const title = useFormStore((state) => state.title);
377
+ const setTitle = useFormStore((state) => state.setTitle);
378
+
379
+ return (
380
+ <div className="space-y-2">
381
+ <Label htmlFor="title">Title</Label>
382
+ <Input
383
+ id="title"
384
+ value={title}
385
+ onChange={(e) => setTitle(e.target.value)}
386
+ placeholder="Enter a title"
387
+ />
388
+ </div>
389
+ );
390
+ }
391
+ \`\`\`
392
+
393
+ ## Code Style
394
+
395
+ ### No Comments
396
+
397
+ Do not add comments to the codebase. Code should be self-documenting through:
398
+ - Clear, descriptive variable and function names
399
+ - Proper TypeScript types
400
+ - Logical code structure
401
+ - Small, focused functions
402
+
403
+ ### Closure Variable Naming
404
+
405
+ Always use verbose singular names in closures (\`.map()\`, \`.filter()\`, etc.):
406
+
407
+ \`\`\`typescript
408
+ // ✅ Correct
409
+ users.map((user) => user.email)
410
+ items.filter((item) => item.isActive)
411
+
412
+ // ❌ Wrong
413
+ users.map((u) => u.email)
414
+ items.filter((i) => i.isActive)
415
+ \`\`\`
416
+
417
+ ## Utility Functions
418
+
419
+ Import from \`@/lib/utils\`:
420
+
421
+ \`\`\`typescript
422
+ import { cn, formatDate, formatRelative, debounce } from '@/lib/utils';
423
+
424
+ // Class name merging (shadcn/ui)
425
+ cn('text-sm', isActive && 'text-blue-500')
426
+
427
+ // Date formatting with Luxon
428
+ formatDate(new Date()) // "Jan 15, 2024"
429
+ formatDate('2024-01-15', 'yyyy-MM-dd') // "2024-01-15"
430
+ formatRelative(new Date()) // "2 hours ago"
431
+
432
+ // Debounce function calls
433
+ const debouncedSearch = debounce((query: string) => {
434
+ // search logic
435
+ }, 300);
436
+ \`\`\`
437
+
438
+ ## Environment Variables
439
+
440
+ Copy \`.env.example\` to \`.env.local\` and fill in your API keys.
441
+
442
+ **Important:** Import environment variables from \`@/lib/config\`:
443
+
444
+ \`\`\`typescript
445
+ // ✅ Good
446
+ import { STRIPE_SECRET_KEY } from '@/lib/config';
447
+
448
+ // ❌ Avoid
449
+ const key = process.env.STRIPE_SECRET_KEY;
450
+ \`\`\`
451
+ `;
452
+ if (hasClerk) {
453
+ content += `
454
+ ## Authentication
455
+
456
+ **Provider:** Clerk
457
+
458
+ - Route protection via \`proxy.ts\` (Next.js 16+)
459
+ - Use \`currentUser()\` in Server Components and Actions for auth checks
460
+ - Client-side: Use Clerk hooks (\`useUser()\`, \`useAuth()\`, \`<SignedIn>\`, \`<SignedOut>\`)
461
+
462
+ \`\`\`typescript
463
+ // Server-side auth check
464
+ const user = await currentUser();
465
+ if (!user) {
466
+ return redirect('/');
467
+ }
468
+ \`\`\`
469
+ `;
470
+ }
471
+ await fs.writeFile(path.join(projectPath, 'CLAUDE.md'), content);
472
+ }
473
+ export async function appendClaudeMd(projectPath, integrations) {
474
+ const claudeMdPath = path.join(projectPath, 'CLAUDE.md');
475
+ let existing = '';
476
+ try {
477
+ existing = await fs.readFile(claudeMdPath, 'utf-8');
478
+ }
479
+ catch {
480
+ return;
481
+ }
482
+ const newSections = stackSections.filter((section) => integrations.includes(section.id));
483
+ if (newSections.length === 0)
484
+ return;
485
+ let content = '\n## Added Integrations\n\n';
486
+ for (const section of newSections) {
487
+ content += `### ${section.name}\n`;
488
+ for (const item of section.items) {
489
+ content += `- [${item.name}](${item.url}) - ${item.description}\n`;
490
+ }
491
+ content += '\n';
492
+ }
493
+ await fs.writeFile(claudeMdPath, existing.trimEnd() + '\n' + content);
494
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function main(): Promise<void>;