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.
- package/README.md +154 -0
- package/dist/claude-md.d.ts +3 -0
- package/dist/claude-md.js +494 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +186 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +98 -0
- package/dist/create-next.d.ts +1 -0
- package/dist/create-next.js +17 -0
- package/dist/detect.d.ts +4 -0
- package/dist/detect.js +60 -0
- package/dist/env.d.ts +3 -0
- package/dist/env.js +183 -0
- package/dist/generate-readme.d.ts +3 -0
- package/dist/generate-readme.js +160 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/instrumentation.d.ts +3 -0
- package/dist/instrumentation.js +95 -0
- package/dist/integrations/ai-sdk.d.ts +3 -0
- package/dist/integrations/ai-sdk.js +20 -0
- package/dist/integrations/clerk.d.ts +2 -0
- package/dist/integrations/clerk.js +50 -0
- package/dist/integrations/firecrawl.d.ts +2 -0
- package/dist/integrations/firecrawl.js +26 -0
- package/dist/integrations/index.d.ts +4 -0
- package/dist/integrations/index.js +64 -0
- package/dist/integrations/inngest.d.ts +2 -0
- package/dist/integrations/inngest.js +45 -0
- package/dist/integrations/neon-drizzle.d.ts +2 -0
- package/dist/integrations/neon-drizzle.js +56 -0
- package/dist/integrations/posthog.d.ts +2 -0
- package/dist/integrations/posthog.js +25 -0
- package/dist/integrations/resend.d.ts +2 -0
- package/dist/integrations/resend.js +34 -0
- package/dist/integrations/sentry.d.ts +2 -0
- package/dist/integrations/sentry.js +47 -0
- package/dist/integrations/stripe.d.ts +2 -0
- package/dist/integrations/stripe.js +45 -0
- package/dist/integrations/uploadthing.d.ts +2 -0
- package/dist/integrations/uploadthing.js +34 -0
- package/dist/landing-page.d.ts +2 -0
- package/dist/landing-page.js +97 -0
- package/dist/prompts.d.ts +7 -0
- package/dist/prompts.js +99 -0
- package/dist/setup-shadcn.d.ts +1 -0
- package/dist/setup-shadcn.js +27 -0
- package/dist/templates/ai-sdk.d.ts +12 -0
- package/dist/templates/ai-sdk.js +96 -0
- package/dist/templates/clerk.d.ts +6 -0
- package/dist/templates/clerk.js +96 -0
- package/dist/templates/firecrawl.d.ts +4 -0
- package/dist/templates/firecrawl.js +106 -0
- package/dist/templates/inngest.d.ts +6 -0
- package/dist/templates/inngest.js +91 -0
- package/dist/templates/neon-drizzle.d.ts +16 -0
- package/dist/templates/neon-drizzle.js +343 -0
- package/dist/templates/posthog.d.ts +3 -0
- package/dist/templates/posthog.js +10 -0
- package/dist/templates/resend.d.ts +5 -0
- package/dist/templates/resend.js +102 -0
- package/dist/templates/sentry.d.ts +8 -0
- package/dist/templates/sentry.js +145 -0
- package/dist/templates/stripe.d.ts +6 -0
- package/dist/templates/stripe.js +215 -0
- package/dist/templates/uploadthing.d.ts +7 -0
- package/dist/templates/uploadthing.js +150 -0
- package/dist/templates/zustand.d.ts +3 -0
- package/dist/templates/zustand.js +26 -0
- package/dist/types.d.ts +26 -0
- package/dist/types.js +1 -0
- 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
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -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,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,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,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,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,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,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,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
|
+
};
|