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
@@ -0,0 +1,160 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ const integrationNames = {
4
+ clerk: 'Clerk',
5
+ 'neon-drizzle': 'Neon + Drizzle',
6
+ 'ai-sdk': 'Vercel AI SDK',
7
+ resend: 'Resend',
8
+ firecrawl: 'Firecrawl',
9
+ inngest: 'Inngest',
10
+ uploadthing: 'UploadThing',
11
+ stripe: 'Stripe',
12
+ posthog: 'PostHog',
13
+ sentry: 'Sentry',
14
+ };
15
+ export async function generateReadme(projectPath, config) {
16
+ let content = `# ${config.name}
17
+
18
+ A Next.js application scaffolded with [create-loadout](https://github.com/your-org/create-loadout).
19
+
20
+ ## Getting Started
21
+
22
+ 1. Install dependencies:
23
+ \`\`\`bash
24
+ npm install
25
+ \`\`\`
26
+
27
+ 2. Copy the environment file and configure your API keys:
28
+ \`\`\`bash
29
+ cp .env.example .env.local
30
+ \`\`\`
31
+
32
+ 3. Start the development server:
33
+ \`\`\`bash
34
+ npm run dev
35
+ \`\`\`
36
+
37
+ 4. Open [http://localhost:3000](http://localhost:3000) in your browser.
38
+
39
+ ## Tech Stack
40
+
41
+ - [Next.js](https://nextjs.org/) - React framework
42
+ - [TypeScript](https://www.typescriptlang.org/) - Type safety
43
+ - [Tailwind CSS](https://tailwindcss.com/) - Styling
44
+ - [shadcn/ui](https://ui.shadcn.com/) - UI components
45
+ - [Zod](https://zod.dev/) - Schema validation
46
+ `;
47
+ if (config.integrations.length > 0) {
48
+ content += `\n### Integrations\n\n`;
49
+ for (const id of config.integrations) {
50
+ content += `- ${integrationNames[id]}\n`;
51
+ }
52
+ }
53
+ content += `
54
+ ## Scripts
55
+
56
+ \`\`\`bash
57
+ npm run dev # Start development server
58
+ npm run build # Build for production
59
+ npm run start # Start production server
60
+ npm run lint # Run ESLint
61
+ \`\`\`
62
+ `;
63
+ if (config.integrations.includes('neon-drizzle')) {
64
+ content += `
65
+ ### Database
66
+
67
+ \`\`\`bash
68
+ npm run db:generate # Generate migrations
69
+ npm run db:migrate # Run migrations
70
+ npm run db:studio # Open Drizzle Studio
71
+ \`\`\`
72
+ `;
73
+ }
74
+ if (config.integrations.includes('inngest')) {
75
+ content += `
76
+ ### Background Jobs
77
+
78
+ \`\`\`bash
79
+ npm run inngest:dev # Start Inngest dev server
80
+ \`\`\`
81
+ `;
82
+ }
83
+ content += `
84
+ ## Project Structure
85
+
86
+ \`\`\`
87
+ ├── app/ # Next.js App Router
88
+ ├── components/ # React components
89
+ ├── lib/ # Utilities and clients
90
+ ├── services/ # Business logic
91
+ `;
92
+ if (config.integrations.includes('resend')) {
93
+ content += `├── emails/ # Email templates\n`;
94
+ }
95
+ content += `└── public/ # Static assets
96
+ \`\`\`
97
+
98
+ ## Environment Variables
99
+
100
+ See \`.env.example\` for all required environment variables.
101
+
102
+ ## Learn More
103
+
104
+ - [Next.js Documentation](https://nextjs.org/docs)
105
+ - [CLAUDE.md](./CLAUDE.md) - AI assistant context file
106
+ `;
107
+ await fs.writeFile(path.join(projectPath, 'README.md'), content);
108
+ }
109
+ export async function generateGitignore(projectPath) {
110
+ const content = `# Dependencies
111
+ node_modules/
112
+ .pnp/
113
+ .pnp.js
114
+
115
+ # Build
116
+ .next/
117
+ out/
118
+ build/
119
+ dist/
120
+
121
+ # Environment
122
+ .env
123
+ .env.local
124
+ .env.development.local
125
+ .env.test.local
126
+ .env.production.local
127
+
128
+ # Testing
129
+ coverage/
130
+
131
+ # IDE
132
+ .vscode/
133
+ .idea/
134
+ *.swp
135
+ *.swo
136
+
137
+ # OS
138
+ .DS_Store
139
+ Thumbs.db
140
+
141
+ # Debug
142
+ npm-debug.log*
143
+ yarn-debug.log*
144
+ yarn-error.log*
145
+
146
+ # Vercel
147
+ .vercel
148
+
149
+ # TypeScript
150
+ *.tsbuildinfo
151
+ next-env.d.ts
152
+
153
+ # Drizzle
154
+ drizzle/
155
+
156
+ # Sentry
157
+ .sentryclirc
158
+ `;
159
+ await fs.writeFile(path.join(projectPath, '.gitignore'), content);
160
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { main } from './cli.js';
3
+ main();
@@ -0,0 +1,3 @@
1
+ import type { ProjectConfig } from './types.js';
2
+ export declare function generateInstrumentationClient(projectPath: string, config: ProjectConfig): Promise<void>;
3
+ export declare function generateInstrumentation(projectPath: string, config: ProjectConfig): Promise<void>;
@@ -0,0 +1,95 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ // Generate instrumentation-client.ts content based on selected integrations
4
+ export async function generateInstrumentationClient(projectPath, config) {
5
+ const hasPostHog = config.integrations.includes('posthog');
6
+ const hasSentry = config.integrations.includes('sentry');
7
+ if (!hasPostHog && !hasSentry)
8
+ return;
9
+ let content = '';
10
+ const imports = [];
11
+ // PostHog initialization
12
+ if (hasPostHog) {
13
+ imports.push("import posthog from 'posthog-js';");
14
+ imports.push("import { POSTHOG_KEY, POSTHOG_HOST } from '@/lib/config';");
15
+ }
16
+ // Sentry initialization
17
+ if (hasSentry) {
18
+ imports.push("import * as Sentry from '@sentry/nextjs';");
19
+ imports.push("import { SENTRY_DSN } from '@/lib/config';");
20
+ }
21
+ content += imports.join('\n') + '\n\n';
22
+ // PostHog init
23
+ if (hasPostHog) {
24
+ content += `// PostHog analytics
25
+ posthog.init(POSTHOG_KEY, {
26
+ api_host: POSTHOG_HOST,
27
+ defaults: '2025-11-30',
28
+ });
29
+ `;
30
+ }
31
+ // Sentry init
32
+ if (hasSentry) {
33
+ if (hasPostHog)
34
+ content += '\n';
35
+ content += `// Sentry error tracking
36
+ Sentry.init({
37
+ dsn: SENTRY_DSN,
38
+ environment: process.env.NODE_ENV,
39
+ sendDefaultPii: false,
40
+
41
+ tracesSampleRate: process.env.NODE_ENV === 'production' ? 0.1 : 0,
42
+ replaysSessionSampleRate: 0.1,
43
+ replaysOnErrorSampleRate: 1.0,
44
+
45
+ integrations: [
46
+ Sentry.replayIntegration({
47
+ maskAllText: true,
48
+ blockAllMedia: true,
49
+ }),
50
+ ],
51
+
52
+ beforeSend(event) {
53
+ if (process.env.NODE_ENV === 'development') return null;
54
+
55
+ // Scrub PII
56
+ if (event.user) {
57
+ delete event.user.email;
58
+ delete event.user.ip_address;
59
+ delete event.user.username;
60
+ }
61
+ if (event.request?.headers) {
62
+ delete event.request.headers['authorization'];
63
+ delete event.request.headers['cookie'];
64
+ }
65
+ return event;
66
+ },
67
+ });
68
+
69
+ // Instrument router navigations for performance
70
+ export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
71
+ `;
72
+ }
73
+ await fs.writeFile(path.join(projectPath, 'instrumentation-client.ts'), content);
74
+ }
75
+ // Generate instrumentation.ts for server-side (Sentry only currently)
76
+ export async function generateInstrumentation(projectPath, config) {
77
+ if (!config.integrations.includes('sentry'))
78
+ return;
79
+ const content = `import * as Sentry from '@sentry/nextjs';
80
+
81
+ export async function register() {
82
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
83
+ await import('./sentry.server.config');
84
+ }
85
+
86
+ if (process.env.NEXT_RUNTIME === 'edge') {
87
+ await import('./sentry.edge.config');
88
+ }
89
+ }
90
+
91
+ // Capture errors from Server Components, middleware, and proxies
92
+ export const onRequestError = Sentry.captureRequestError;
93
+ `;
94
+ await fs.writeFile(path.join(projectPath, 'instrumentation.ts'), content);
95
+ }
@@ -0,0 +1,3 @@
1
+ import type { Integration, AIProviderChoice } from '../types.js';
2
+ export declare function createAiSdkIntegration(provider?: AIProviderChoice): Integration;
3
+ export declare const aiSdkIntegration: Integration;
@@ -0,0 +1,20 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { getAiServiceTemplate, getAiPackages, getAiEnvVar } from '../templates/ai-sdk.js';
4
+ export function createAiSdkIntegration(provider = 'openai') {
5
+ const envVar = getAiEnvVar(provider);
6
+ return {
7
+ id: 'ai-sdk',
8
+ name: 'Vercel AI SDK',
9
+ description: 'AI integration with structured generation',
10
+ packages: getAiPackages(provider),
11
+ envVars: [envVar],
12
+ setup: async (projectPath) => {
13
+ // Create AI service
14
+ await fs.mkdir(path.join(projectPath, 'services'), { recursive: true });
15
+ await fs.writeFile(path.join(projectPath, 'services/ai.service.ts'), getAiServiceTemplate(provider));
16
+ },
17
+ };
18
+ }
19
+ // Default export for backwards compatibility
20
+ export const aiSdkIntegration = createAiSdkIntegration('openai');
@@ -0,0 +1,2 @@
1
+ import type { Integration } from '../types.js';
2
+ export declare const clerkIntegration: Integration;
@@ -0,0 +1,50 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { clerkTemplates } from '../templates/clerk.js';
4
+ export const clerkIntegration = {
5
+ id: 'clerk',
6
+ name: 'Clerk',
7
+ description: 'Authentication and user management',
8
+ packages: ['@clerk/nextjs'],
9
+ envVars: [
10
+ {
11
+ key: 'NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY',
12
+ description: 'Clerk publishable key',
13
+ example: 'pk_test_...',
14
+ isPublic: true,
15
+ },
16
+ {
17
+ key: 'CLERK_SECRET_KEY',
18
+ description: 'Clerk secret key',
19
+ example: 'sk_test_...',
20
+ },
21
+ {
22
+ key: 'NEXT_PUBLIC_CLERK_SIGN_IN_URL',
23
+ description: 'Sign in URL',
24
+ example: '/sign-in',
25
+ isPublic: true,
26
+ },
27
+ {
28
+ key: 'NEXT_PUBLIC_CLERK_SIGN_UP_URL',
29
+ description: 'Sign up URL',
30
+ example: '/sign-up',
31
+ isPublic: true,
32
+ },
33
+ ],
34
+ setup: async (projectPath) => {
35
+ // Create services directory
36
+ await fs.mkdir(path.join(projectPath, 'services'), { recursive: true });
37
+ // Create user service
38
+ await fs.writeFile(path.join(projectPath, 'services/user.service.ts'), clerkTemplates.userService);
39
+ // Create proxy.ts (Next.js 16+)
40
+ await fs.writeFile(path.join(projectPath, 'proxy.ts'), clerkTemplates.proxy);
41
+ // Create auth components
42
+ await fs.mkdir(path.join(projectPath, 'components'), { recursive: true });
43
+ await fs.writeFile(path.join(projectPath, 'components/auth-buttons.tsx'), clerkTemplates.authComponents);
44
+ // Update layout.tsx to include ClerkProvider
45
+ const layoutPath = path.join(projectPath, 'app/layout.tsx');
46
+ const layoutContent = await fs.readFile(layoutPath, 'utf-8');
47
+ const updatedLayout = clerkTemplates.wrapLayout(layoutContent);
48
+ await fs.writeFile(layoutPath, updatedLayout);
49
+ },
50
+ };
@@ -0,0 +1,2 @@
1
+ import type { Integration } from '../types.js';
2
+ export declare const firecrawlIntegration: Integration;
@@ -0,0 +1,26 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { firecrawlTemplates } from '../templates/firecrawl.js';
4
+ export const firecrawlIntegration = {
5
+ id: 'firecrawl',
6
+ name: 'Firecrawl',
7
+ description: 'Web scraping',
8
+ packages: ['@mendable/firecrawl-js'],
9
+ envVars: [
10
+ {
11
+ key: 'FIRECRAWL_API_KEY',
12
+ description: 'Firecrawl API key',
13
+ example: 'fc-...',
14
+ },
15
+ ],
16
+ setup: async (projectPath) => {
17
+ // Create scrape service
18
+ await fs.mkdir(path.join(projectPath, 'services'), { recursive: true });
19
+ await fs.writeFile(path.join(projectPath, 'services/scrape.service.ts'), firecrawlTemplates.scrapeService);
20
+ // Create API route
21
+ await fs.mkdir(path.join(projectPath, 'app/api/scrape'), {
22
+ recursive: true,
23
+ });
24
+ await fs.writeFile(path.join(projectPath, 'app/api/scrape/route.ts'), firecrawlTemplates.scrapeRoute);
25
+ },
26
+ };
@@ -0,0 +1,4 @@
1
+ import type { Integration, IntegrationId, EnvVar, ProjectConfig } from '../types.js';
2
+ export declare function installIntegrations(projectPath: string, config: ProjectConfig): Promise<void>;
3
+ export declare function getEnvVars(config: ProjectConfig): EnvVar[];
4
+ export declare const integrations: Partial<Record<IntegrationId, Integration>>;
@@ -0,0 +1,64 @@
1
+ import { execa } from 'execa';
2
+ import { clerkIntegration } from './clerk.js';
3
+ import { neonDrizzleIntegration } from './neon-drizzle.js';
4
+ import { createAiSdkIntegration } from './ai-sdk.js';
5
+ import { resendIntegration } from './resend.js';
6
+ import { firecrawlIntegration } from './firecrawl.js';
7
+ import { inngestIntegration } from './inngest.js';
8
+ import { uploadthingIntegration } from './uploadthing.js';
9
+ import { stripeIntegration } from './stripe.js';
10
+ import { posthogIntegration } from './posthog.js';
11
+ import { sentryIntegration } from './sentry.js';
12
+ // Static integrations (don't need config)
13
+ const staticIntegrations = {
14
+ clerk: clerkIntegration,
15
+ 'neon-drizzle': neonDrizzleIntegration,
16
+ resend: resendIntegration,
17
+ firecrawl: firecrawlIntegration,
18
+ inngest: inngestIntegration,
19
+ uploadthing: uploadthingIntegration,
20
+ stripe: stripeIntegration,
21
+ posthog: posthogIntegration,
22
+ sentry: sentryIntegration,
23
+ };
24
+ // Get integration, with dynamic ones using config
25
+ function getIntegration(id, config) {
26
+ if (id === 'ai-sdk') {
27
+ return createAiSdkIntegration(config.aiProvider ?? 'openai');
28
+ }
29
+ return staticIntegrations[id];
30
+ }
31
+ export async function installIntegrations(projectPath, config) {
32
+ const allPackages = [];
33
+ const allDevPackages = [];
34
+ // Collect all packages
35
+ for (const id of config.integrations) {
36
+ const integration = getIntegration(id, config);
37
+ allPackages.push(...integration.packages);
38
+ if (integration.devPackages) {
39
+ allDevPackages.push(...integration.devPackages);
40
+ }
41
+ }
42
+ // Install all packages at once
43
+ if (allPackages.length > 0) {
44
+ await execa('npm', ['install', ...allPackages], { cwd: projectPath });
45
+ }
46
+ if (allDevPackages.length > 0) {
47
+ await execa('npm', ['install', '-D', ...allDevPackages], { cwd: projectPath });
48
+ }
49
+ // Run setup for each integration
50
+ for (const id of config.integrations) {
51
+ const integration = getIntegration(id, config);
52
+ await integration.setup(projectPath);
53
+ }
54
+ }
55
+ export function getEnvVars(config) {
56
+ const envVars = [];
57
+ for (const id of config.integrations) {
58
+ const integration = getIntegration(id, config);
59
+ envVars.push(...integration.envVars);
60
+ }
61
+ return envVars;
62
+ }
63
+ // Export for backwards compatibility
64
+ export const integrations = staticIntegrations;
@@ -0,0 +1,2 @@
1
+ import type { Integration } from '../types.js';
2
+ export declare const inngestIntegration: Integration;
@@ -0,0 +1,45 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { inngestTemplates } from '../templates/inngest.js';
4
+ export const inngestIntegration = {
5
+ id: 'inngest',
6
+ name: 'Inngest',
7
+ description: 'Background jobs and workflows',
8
+ packages: ['inngest'],
9
+ envVars: [
10
+ {
11
+ key: 'INNGEST_EVENT_KEY',
12
+ description: 'Inngest event key',
13
+ example: '...',
14
+ },
15
+ {
16
+ key: 'INNGEST_SIGNING_KEY',
17
+ description: 'Inngest signing key',
18
+ example: '...',
19
+ },
20
+ ],
21
+ setup: async (projectPath) => {
22
+ // Create lib directory
23
+ await fs.mkdir(path.join(projectPath, 'lib'), { recursive: true });
24
+ // Create Inngest client
25
+ await fs.writeFile(path.join(projectPath, 'lib/inngest.client.ts'), inngestTemplates.inngestClient);
26
+ // Create Inngest functions
27
+ await fs.writeFile(path.join(projectPath, 'lib/inngest.functions.ts'), inngestTemplates.inngestFunctions);
28
+ // Create jobs service
29
+ await fs.mkdir(path.join(projectPath, 'services'), { recursive: true });
30
+ await fs.writeFile(path.join(projectPath, 'services/jobs.service.ts'), inngestTemplates.jobsService);
31
+ // Create API route
32
+ await fs.mkdir(path.join(projectPath, 'app/api/inngest'), {
33
+ recursive: true,
34
+ });
35
+ await fs.writeFile(path.join(projectPath, 'app/api/inngest/route.ts'), inngestTemplates.inngestRoute);
36
+ // Update package.json with inngest script
37
+ const pkgPath = path.join(projectPath, 'package.json');
38
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
39
+ pkg.scripts = {
40
+ ...pkg.scripts,
41
+ 'inngest:dev': 'npx inngest-cli@latest dev',
42
+ };
43
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
44
+ },
45
+ };
@@ -0,0 +1,2 @@
1
+ import type { Integration } from '../types.js';
2
+ export declare const neonDrizzleIntegration: Integration;
@@ -0,0 +1,56 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { neonDrizzleTemplates } from '../templates/neon-drizzle.js';
4
+ export const neonDrizzleIntegration = {
5
+ id: 'neon-drizzle',
6
+ name: 'Neon + Drizzle',
7
+ description: 'Serverless Postgres with TypeScript ORM',
8
+ packages: ['drizzle-orm', '@neondatabase/serverless'],
9
+ devPackages: ['drizzle-kit'],
10
+ envVars: [
11
+ {
12
+ key: 'DATABASE_URL',
13
+ description: 'Neon database connection string',
14
+ example: 'postgresql://user:pass@host/db?sslmode=require',
15
+ },
16
+ ],
17
+ setup: async (projectPath) => {
18
+ // Create drizzle.config.ts
19
+ await fs.writeFile(path.join(projectPath, 'drizzle.config.ts'), neonDrizzleTemplates.drizzleConfig);
20
+ // Create lib/db directory
21
+ await fs.mkdir(path.join(projectPath, 'lib/db'), { recursive: true });
22
+ await fs.writeFile(path.join(projectPath, 'lib/db/client.ts'), neonDrizzleTemplates.dbClient);
23
+ await fs.writeFile(path.join(projectPath, 'lib/db/schema.ts'), neonDrizzleTemplates.schema);
24
+ await fs.writeFile(path.join(projectPath, 'lib/db/index.ts'), neonDrizzleTemplates.dbIndex);
25
+ // Create models directory
26
+ await fs.mkdir(path.join(projectPath, 'models'), { recursive: true });
27
+ await fs.writeFile(path.join(projectPath, 'models/todo.dto.ts'), neonDrizzleTemplates.todoDto);
28
+ await fs.writeFile(path.join(projectPath, 'models/todo.view.ts'), neonDrizzleTemplates.todoView);
29
+ await fs.writeFile(path.join(projectPath, 'models/todoCreate.schema.ts'), neonDrizzleTemplates.todoCreateSchema);
30
+ await fs.writeFile(path.join(projectPath, 'models/todoCreate.state.ts'), neonDrizzleTemplates.todoCreateState);
31
+ await fs.writeFile(path.join(projectPath, 'models/todoUpdate.schema.ts'), neonDrizzleTemplates.todoUpdateSchema);
32
+ await fs.writeFile(path.join(projectPath, 'models/todoUpdate.state.ts'), neonDrizzleTemplates.todoUpdateState);
33
+ // Create dao directory
34
+ await fs.mkdir(path.join(projectPath, 'dao'), { recursive: true });
35
+ await fs.writeFile(path.join(projectPath, 'dao/todo.dao.ts'), neonDrizzleTemplates.todoDao);
36
+ // Create mappers directory
37
+ await fs.mkdir(path.join(projectPath, 'mappers'), { recursive: true });
38
+ await fs.writeFile(path.join(projectPath, 'mappers/todo.mapper.ts'), neonDrizzleTemplates.todoMapper);
39
+ // Create services directory
40
+ await fs.mkdir(path.join(projectPath, 'services'), { recursive: true });
41
+ await fs.writeFile(path.join(projectPath, 'services/todo.service.ts'), neonDrizzleTemplates.todoService);
42
+ // Create actions directory
43
+ await fs.mkdir(path.join(projectPath, 'actions'), { recursive: true });
44
+ await fs.writeFile(path.join(projectPath, 'actions/todo.actions.ts'), neonDrizzleTemplates.todoActions);
45
+ // Update package.json with db scripts
46
+ const pkgPath = path.join(projectPath, 'package.json');
47
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
48
+ pkg.scripts = {
49
+ ...pkg.scripts,
50
+ 'db:generate': 'drizzle-kit generate',
51
+ 'db:migrate': 'drizzle-kit migrate',
52
+ 'db:studio': 'drizzle-kit studio',
53
+ };
54
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2));
55
+ },
56
+ };
@@ -0,0 +1,2 @@
1
+ import type { Integration } from '../types.js';
2
+ export declare const posthogIntegration: Integration;
@@ -0,0 +1,25 @@
1
+ export const posthogIntegration = {
2
+ id: 'posthog',
3
+ name: 'PostHog',
4
+ description: 'Product analytics',
5
+ packages: ['posthog-js'],
6
+ envVars: [
7
+ {
8
+ key: 'NEXT_PUBLIC_POSTHOG_KEY',
9
+ description: 'PostHog project API key',
10
+ example: 'phc_...',
11
+ isPublic: true,
12
+ },
13
+ {
14
+ key: 'NEXT_PUBLIC_POSTHOG_HOST',
15
+ description: 'PostHog host',
16
+ example: 'https://us.i.posthog.com',
17
+ isPublic: true,
18
+ },
19
+ ],
20
+ setup: async () => {
21
+ // instrumentation-client.ts is generated centrally in cli.ts
22
+ // Client components then just: import posthog from 'posthog-js'
23
+ // No service needed - PostHog is client-side only
24
+ },
25
+ };
@@ -0,0 +1,2 @@
1
+ import type { Integration } from '../types.js';
2
+ export declare const resendIntegration: Integration;
@@ -0,0 +1,34 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { resendTemplates } from '../templates/resend.js';
4
+ export const resendIntegration = {
5
+ id: 'resend',
6
+ name: 'Resend',
7
+ description: 'Email API',
8
+ packages: ['resend'],
9
+ envVars: [
10
+ {
11
+ key: 'RESEND_API_KEY',
12
+ description: 'Resend API key',
13
+ example: 're_...',
14
+ },
15
+ {
16
+ key: 'RESEND_FROM_EMAIL',
17
+ description: 'Default from email address',
18
+ example: 'onboarding@resend.dev',
19
+ },
20
+ ],
21
+ setup: async (projectPath) => {
22
+ // Create email service
23
+ await fs.mkdir(path.join(projectPath, 'services'), { recursive: true });
24
+ await fs.writeFile(path.join(projectPath, 'services/email.service.ts'), resendTemplates.emailService);
25
+ // Create email templates in components/emails
26
+ await fs.mkdir(path.join(projectPath, 'components/emails'), { recursive: true });
27
+ await fs.writeFile(path.join(projectPath, 'components/emails/welcome.tsx'), resendTemplates.welcomeEmail);
28
+ // Create API route
29
+ await fs.mkdir(path.join(projectPath, 'app/api/email/send'), {
30
+ recursive: true,
31
+ });
32
+ await fs.writeFile(path.join(projectPath, 'app/api/email/send/route.ts'), resendTemplates.sendRoute);
33
+ },
34
+ };
@@ -0,0 +1,2 @@
1
+ import type { Integration } from '../types.js';
2
+ export declare const sentryIntegration: Integration;