create-loadout 1.0.1 → 1.0.4
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 +76 -9
- package/dist/claude-md.js +504 -333
- package/dist/cli.js +14 -81
- package/dist/create-next.js +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.js +84 -0
- package/dist/env.js +12 -25
- package/dist/generate-readme.js +123 -120
- package/dist/index.js +156 -1
- package/dist/integrations/index.d.ts +1 -0
- package/dist/integrations/index.js +1 -1
- package/dist/integrations/stripe.js +0 -14
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +165 -0
- package/dist/metadata.d.ts +18 -0
- package/dist/metadata.js +28 -0
- package/dist/prompts.js +3 -6
- package/dist/templates/firecrawl.js +1 -1
- package/dist/templates/neon-drizzle.js +329 -327
- package/dist/templates/stripe.d.ts +0 -3
- package/dist/templates/stripe.js +0 -130
- package/dist/validate.d.ts +14 -0
- package/dist/validate.js +66 -0
- package/package.json +10 -5
package/dist/templates/stripe.js
CHANGED
|
@@ -81,135 +81,5 @@ export class PaymentService {
|
|
|
81
81
|
export const paymentService = new PaymentService(STRIPE_SECRET_KEY);
|
|
82
82
|
|
|
83
83
|
export type { Stripe };
|
|
84
|
-
`,
|
|
85
|
-
checkoutRoute: `import { NextResponse } from 'next/server';
|
|
86
|
-
import { paymentService } from '@/services/payment.service';
|
|
87
|
-
import { z } from 'zod';
|
|
88
|
-
|
|
89
|
-
const checkoutSchema = z.object({
|
|
90
|
-
priceId: z.string(),
|
|
91
|
-
customerId: z.string().optional(),
|
|
92
|
-
successUrl: z.url(),
|
|
93
|
-
cancelUrl: z.url(),
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
export async function POST(req: Request) {
|
|
97
|
-
try {
|
|
98
|
-
const body = await req.json();
|
|
99
|
-
const { priceId, customerId, successUrl, cancelUrl } = checkoutSchema.parse(body);
|
|
100
|
-
|
|
101
|
-
const session = await paymentService.createCheckoutSession({
|
|
102
|
-
priceId,
|
|
103
|
-
customerId,
|
|
104
|
-
successUrl,
|
|
105
|
-
cancelUrl,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
return NextResponse.json({ url: session.url });
|
|
109
|
-
} catch (error) {
|
|
110
|
-
if (error instanceof z.ZodError) {
|
|
111
|
-
return NextResponse.json(
|
|
112
|
-
{ error: 'Invalid request', details: error.errors },
|
|
113
|
-
{ status: 400 }
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
return NextResponse.json(
|
|
117
|
-
{ error: error instanceof Error ? error.message : 'Failed to create checkout' },
|
|
118
|
-
{ status: 500 }
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
`,
|
|
123
|
-
webhooksRoute: `import { headers } from 'next/headers';
|
|
124
|
-
import { NextResponse } from 'next/server';
|
|
125
|
-
import { paymentService, type Stripe } from '@/services/payment.service';
|
|
126
|
-
import { STRIPE_WEBHOOK_SECRET } from '@/lib/config';
|
|
127
|
-
|
|
128
|
-
export async function POST(req: Request) {
|
|
129
|
-
const body = await req.text();
|
|
130
|
-
const headersList = await headers();
|
|
131
|
-
const signature = headersList.get('stripe-signature')!;
|
|
132
|
-
|
|
133
|
-
let event: Stripe.Event;
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
event = paymentService.constructWebhookEvent(body, signature, STRIPE_WEBHOOK_SECRET);
|
|
137
|
-
} catch (error) {
|
|
138
|
-
console.error('Webhook signature verification failed:', error);
|
|
139
|
-
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
switch (event.type) {
|
|
143
|
-
case 'checkout.session.completed': {
|
|
144
|
-
const session = event.data.object as Stripe.Checkout.Session;
|
|
145
|
-
console.log('Checkout completed:', session.id);
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
case 'customer.subscription.created':
|
|
150
|
-
case 'customer.subscription.updated': {
|
|
151
|
-
const subscription = event.data.object as Stripe.Subscription;
|
|
152
|
-
console.log('Subscription updated:', subscription.id, subscription.status);
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
case 'customer.subscription.deleted': {
|
|
157
|
-
const subscription = event.data.object as Stripe.Subscription;
|
|
158
|
-
console.log('Subscription cancelled:', subscription.id);
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
case 'invoice.payment_succeeded': {
|
|
163
|
-
const invoice = event.data.object as Stripe.Invoice;
|
|
164
|
-
console.log('Payment succeeded:', invoice.id);
|
|
165
|
-
break;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
case 'invoice.payment_failed': {
|
|
169
|
-
const invoice = event.data.object as Stripe.Invoice;
|
|
170
|
-
console.log('Payment failed:', invoice.id);
|
|
171
|
-
break;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
default:
|
|
175
|
-
console.log('Unhandled event type:', event.type);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return NextResponse.json({ received: true });
|
|
179
|
-
}
|
|
180
|
-
`,
|
|
181
|
-
portalRoute: `import { NextResponse } from 'next/server';
|
|
182
|
-
import { paymentService } from '@/services/payment.service';
|
|
183
|
-
import { z } from 'zod';
|
|
184
|
-
|
|
185
|
-
const portalSchema = z.object({
|
|
186
|
-
customerId: z.string(),
|
|
187
|
-
returnUrl: z.url(),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
export async function POST(req: Request) {
|
|
191
|
-
try {
|
|
192
|
-
const body = await req.json();
|
|
193
|
-
const { customerId, returnUrl } = portalSchema.parse(body);
|
|
194
|
-
|
|
195
|
-
const session = await paymentService.createPortalSession({
|
|
196
|
-
customerId,
|
|
197
|
-
returnUrl,
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
return NextResponse.json({ url: session.url });
|
|
201
|
-
} catch (error) {
|
|
202
|
-
if (error instanceof z.ZodError) {
|
|
203
|
-
return NextResponse.json(
|
|
204
|
-
{ error: 'Invalid request', details: error.errors },
|
|
205
|
-
{ status: 400 }
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
return NextResponse.json(
|
|
209
|
-
{ error: error instanceof Error ? error.message : 'Failed to create portal session' },
|
|
210
|
-
{ status: 500 }
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
84
|
`,
|
|
215
85
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IntegrationId, AIProviderChoice } from './types.js';
|
|
2
|
+
export declare const ALL_INTEGRATION_IDS: IntegrationId[];
|
|
3
|
+
export declare const AI_PROVIDERS: AIProviderChoice[];
|
|
4
|
+
export interface ValidationError {
|
|
5
|
+
field: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function validateProjectName(name: string): ValidationError[];
|
|
9
|
+
export declare function validateIntegrationSelection(ids: string[]): ValidationError[];
|
|
10
|
+
export declare function validateProjectConfig(config: {
|
|
11
|
+
name: string;
|
|
12
|
+
integrations: string[];
|
|
13
|
+
aiProvider?: string;
|
|
14
|
+
}): ValidationError[];
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export const ALL_INTEGRATION_IDS = [
|
|
2
|
+
'clerk',
|
|
3
|
+
'neon-drizzle',
|
|
4
|
+
'ai-sdk',
|
|
5
|
+
'resend',
|
|
6
|
+
'postmark',
|
|
7
|
+
'firecrawl',
|
|
8
|
+
'inngest',
|
|
9
|
+
'uploadthing',
|
|
10
|
+
'stripe',
|
|
11
|
+
'posthog',
|
|
12
|
+
'sentry',
|
|
13
|
+
];
|
|
14
|
+
export const AI_PROVIDERS = ['openai', 'anthropic', 'google'];
|
|
15
|
+
const EMAIL_PROVIDERS = ['resend', 'postmark'];
|
|
16
|
+
const PROJECT_NAME_REGEX = /^[a-z0-9-]+$/;
|
|
17
|
+
export function validateProjectName(name) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
if (!name.trim()) {
|
|
20
|
+
errors.push({ field: 'name', message: 'Project name is required' });
|
|
21
|
+
}
|
|
22
|
+
else if (!PROJECT_NAME_REGEX.test(name)) {
|
|
23
|
+
errors.push({
|
|
24
|
+
field: 'name',
|
|
25
|
+
message: 'Project name can only contain lowercase letters, numbers, and hyphens',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return errors;
|
|
29
|
+
}
|
|
30
|
+
export function validateIntegrationSelection(ids) {
|
|
31
|
+
const errors = [];
|
|
32
|
+
for (const id of ids) {
|
|
33
|
+
if (!ALL_INTEGRATION_IDS.includes(id)) {
|
|
34
|
+
errors.push({
|
|
35
|
+
field: 'integrations',
|
|
36
|
+
message: `Unknown integration: "${id}". Valid: ${ALL_INTEGRATION_IDS.join(', ')}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const selectedEmail = ids.filter((id) => EMAIL_PROVIDERS.includes(id));
|
|
41
|
+
if (selectedEmail.length > 1) {
|
|
42
|
+
errors.push({
|
|
43
|
+
field: 'integrations',
|
|
44
|
+
message: 'Only one email provider allowed (resend or postmark)',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return errors;
|
|
48
|
+
}
|
|
49
|
+
export function validateProjectConfig(config) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
errors.push(...validateProjectName(config.name));
|
|
52
|
+
errors.push(...validateIntegrationSelection(config.integrations));
|
|
53
|
+
if (config.integrations.includes('ai-sdk') && !config.aiProvider) {
|
|
54
|
+
errors.push({
|
|
55
|
+
field: 'aiProvider',
|
|
56
|
+
message: 'aiProvider is required when ai-sdk is selected (openai, anthropic, or google)',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (config.aiProvider && !AI_PROVIDERS.includes(config.aiProvider)) {
|
|
60
|
+
errors.push({
|
|
61
|
+
field: 'aiProvider',
|
|
62
|
+
message: `Invalid AI provider: "${config.aiProvider}". Valid: ${AI_PROVIDERS.join(', ')}`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return errors;
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-loadout",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Custom Next.js scaffolding CLI with optional SaaS integrations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-loadout": "./dist/index.js"
|
|
7
|
+
"create-loadout": "./dist/index.js",
|
|
8
|
+
"create-loadout-mcp": "./dist/mcp-server.js"
|
|
8
9
|
},
|
|
9
10
|
"files": [
|
|
10
11
|
"dist"
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
"scripts": {
|
|
13
14
|
"build": "tsc",
|
|
14
15
|
"dev": "tsx src/index.ts",
|
|
16
|
+
"create": "tsx src/index.ts",
|
|
15
17
|
"prepublishOnly": "npm run build"
|
|
16
18
|
},
|
|
17
19
|
"keywords": [
|
|
@@ -31,14 +33,17 @@
|
|
|
31
33
|
"license": "MIT",
|
|
32
34
|
"dependencies": {
|
|
33
35
|
"@inquirer/prompts": "^7.0.0",
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
34
37
|
"chalk": "^5.0.0",
|
|
38
|
+
"commander": "^13.1.0",
|
|
39
|
+
"execa": "^9.0.0",
|
|
35
40
|
"ora": "^8.0.0",
|
|
36
|
-
"
|
|
41
|
+
"zod": "^4.3.6"
|
|
37
42
|
},
|
|
38
43
|
"devDependencies": {
|
|
39
44
|
"@types/node": "^22.0.0",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
45
|
+
"tsx": "^4.0.0",
|
|
46
|
+
"typescript": "^5.0.0"
|
|
42
47
|
},
|
|
43
48
|
"engines": {
|
|
44
49
|
"node": ">=18.0.0"
|